Fleet UI: Refactor client filtered counts for cleaner rendering (#19689)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
- Cleanup count rendering fixing clientside flashing counts
|
||||
@@ -76,7 +76,6 @@ const TargetsInput = ({
|
||||
columnConfigs={searchResultsTableConfig}
|
||||
data={dropdownHosts}
|
||||
isLoading={isTargetsLoading}
|
||||
resultsTitle=""
|
||||
emptyComponent={() => (
|
||||
<div className="empty-search">
|
||||
<div className="empty-search__inner">
|
||||
@@ -107,7 +106,6 @@ const TargetsInput = ({
|
||||
columnConfigs={selectedHostsTableConifg}
|
||||
data={targetedHosts}
|
||||
isLoading={false}
|
||||
resultsTitle=""
|
||||
showMarkAllPages={false}
|
||||
isAllPagesSelected={false}
|
||||
disableCount
|
||||
|
||||
@@ -43,7 +43,7 @@ interface IDataTableProps {
|
||||
showMarkAllPages: boolean;
|
||||
isAllPagesSelected: boolean; // TODO: make dependent on showMarkAllPages
|
||||
toggleAllPagesSelected?: any; // TODO: an event type and make it dependent on showMarkAllPages
|
||||
resultsTitle: string;
|
||||
resultsTitle?: string;
|
||||
defaultPageSize: number;
|
||||
defaultPageIndex?: number;
|
||||
primarySelectAction?: IActionButtonProps;
|
||||
@@ -85,7 +85,7 @@ const DataTable = ({
|
||||
showMarkAllPages,
|
||||
isAllPagesSelected,
|
||||
toggleAllPagesSelected,
|
||||
resultsTitle,
|
||||
resultsTitle = "results",
|
||||
defaultPageSize,
|
||||
defaultPageIndex,
|
||||
primarySelectAction,
|
||||
|
||||
@@ -12,7 +12,6 @@ import Icon from "components/Icon/Icon";
|
||||
import { COLORS } from "styles/var/colors";
|
||||
|
||||
import DataTable from "./DataTable/DataTable";
|
||||
import TableContainerUtils from "./utilities/TableContainerUtils";
|
||||
import { IActionButtonProps } from "./DataTable/ActionButton/ActionButton";
|
||||
|
||||
export interface ITableQueryData {
|
||||
@@ -44,7 +43,8 @@ interface ITableContainerProps<T = any> {
|
||||
inputPlaceHolder?: string;
|
||||
disableActionButton?: boolean;
|
||||
disableMultiRowSelect?: boolean;
|
||||
resultsTitle: string;
|
||||
/** resultsTitle used in DataTable for matching results text */
|
||||
resultsTitle?: string;
|
||||
resultsHtml?: JSX.Element;
|
||||
additionalQueries?: string;
|
||||
emptyComponent: React.ElementType;
|
||||
@@ -64,10 +64,6 @@ interface ITableContainerProps<T = any> {
|
||||
primarySelectAction?: IActionButtonProps;
|
||||
/** Secondary button/s after selecting a row */
|
||||
secondarySelectActions?: IActionButtonProps[]; // TODO: Combine with primarySelectAction as these are all rendered in the same spot
|
||||
/**
|
||||
* @deprecated please use renderCount instead
|
||||
* */
|
||||
filteredCount?: number;
|
||||
searchToolTipText?: string;
|
||||
// TODO - consolidate this functionality within `filters`
|
||||
searchQueryColumn?: string;
|
||||
@@ -103,7 +99,6 @@ interface ITableContainerProps<T = any> {
|
||||
* bar and API call so TableContainer will reset its page state to 0 */
|
||||
resetPageIndex?: boolean;
|
||||
disableTableHeader?: boolean;
|
||||
show0Count?: boolean;
|
||||
}
|
||||
|
||||
const baseClass = "table-container";
|
||||
@@ -140,7 +135,6 @@ const TableContainer = <T,>({
|
||||
disableCount,
|
||||
primarySelectAction,
|
||||
secondarySelectActions,
|
||||
filteredCount,
|
||||
searchToolTipText,
|
||||
isClientSidePagination,
|
||||
onClientSidePaginationChange,
|
||||
@@ -160,7 +154,6 @@ const TableContainer = <T,>({
|
||||
setExportRows,
|
||||
resetPageIndex,
|
||||
disableTableHeader,
|
||||
show0Count,
|
||||
}: ITableContainerProps<T>) => {
|
||||
const [searchQuery, setSearchQuery] = useState(defaultSearchQuery);
|
||||
const [sortHeader, setSortHeader] = useState(defaultSortHeader || "");
|
||||
@@ -252,16 +245,6 @@ const TableContainer = <T,>({
|
||||
additionalQueries,
|
||||
]);
|
||||
|
||||
// TODO: refactor existing components relying on displayCount to use renderCount pattern
|
||||
const displayCount = useCallback((): any => {
|
||||
if (typeof filteredCount === "number") {
|
||||
return filteredCount;
|
||||
} else if (typeof clientFilterCount === "number") {
|
||||
return clientFilterCount;
|
||||
}
|
||||
return data?.length || 0;
|
||||
}, [filteredCount, clientFilterCount, data]);
|
||||
|
||||
const renderPagination = useCallback(() => {
|
||||
if (disablePagination || isClientSidePagination) {
|
||||
return null;
|
||||
@@ -309,37 +292,16 @@ const TableContainer = <T,>({
|
||||
stackControls ? "stack-table-controls" : ""
|
||||
}`}
|
||||
>
|
||||
<span className="results-count">
|
||||
{renderCount && (
|
||||
<div
|
||||
className={`${baseClass}__results-count ${
|
||||
stackControls ? "stack-table-controls" : ""
|
||||
}`}
|
||||
style={opacity}
|
||||
>
|
||||
{renderCount()}
|
||||
</div>
|
||||
)}
|
||||
{!renderCount &&
|
||||
!disableCount &&
|
||||
(isMultiColumnFilter || displayCount() || show0Count) ? (
|
||||
<div
|
||||
className={`${baseClass}__results-count ${
|
||||
stackControls ? "stack-table-controls" : ""
|
||||
}`}
|
||||
style={opacity}
|
||||
>
|
||||
{TableContainerUtils.generateResultsCountText(
|
||||
resultsTitle,
|
||||
displayCount(),
|
||||
show0Count
|
||||
)}
|
||||
{resultsHtml}
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</span>
|
||||
{renderCount && !disableCount && (
|
||||
<div
|
||||
className={`${baseClass}__results-count ${
|
||||
stackControls ? "stack-table-controls" : ""
|
||||
}`}
|
||||
style={opacity}
|
||||
>
|
||||
{renderCount()}
|
||||
</div>
|
||||
)}
|
||||
<span className="controls">
|
||||
{actionButton && !actionButton.hideButton && (
|
||||
<Button
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
|
||||
import { generateResultsCountText } from "../utilities/TableContainerUtils";
|
||||
|
||||
interface ITableCountProps {
|
||||
name: string;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
const TableCount = ({ name, count }: ITableCountProps): JSX.Element => {
|
||||
return <span>{generateResultsCountText(name, count)}</span>;
|
||||
};
|
||||
|
||||
export default TableCount;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./TableCount";
|
||||
@@ -54,9 +54,6 @@
|
||||
height: 38px; // Fixes overlap with .Select outline
|
||||
}
|
||||
}
|
||||
.results-count {
|
||||
height: 40px; // Match height of search/filters
|
||||
}
|
||||
|
||||
&__results-count {
|
||||
display: flex;
|
||||
@@ -66,6 +63,7 @@
|
||||
color: $core-fleet-black;
|
||||
margin: 0;
|
||||
height: 40px;
|
||||
gap: 12px;
|
||||
|
||||
.count-error {
|
||||
color: $ui-error;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
const DEFAULT_RESULTS_NAME = "results";
|
||||
|
||||
const generateResultsCountText = (
|
||||
export const generateResultsCountText = (
|
||||
name: string = DEFAULT_RESULTS_NAME,
|
||||
resultsCount: number,
|
||||
show0Count = false
|
||||
resultsCount?: number
|
||||
): string => {
|
||||
if (resultsCount === 0 && !show0Count) return `No ${name}`;
|
||||
if (!resultsCount || resultsCount === 0) return `0 ${name}`;
|
||||
// If there is 1 result and the last 3 letters in the result
|
||||
// name are "ies," we remove the "ies" and add "y"
|
||||
// to make the name singular
|
||||
|
||||
@@ -13,6 +13,7 @@ import CustomLink from "components/CustomLink";
|
||||
import TableContainer from "components/TableContainer";
|
||||
import LastUpdatedText from "components/LastUpdatedText";
|
||||
import { ITableQueryData } from "components/TableContainer/TableContainer";
|
||||
import TableCount from "components/TableContainer/TableCount";
|
||||
|
||||
import EmptySoftwareTable from "pages/SoftwarePage/components/EmptySoftwareTable";
|
||||
import { IOSVersionsResponse } from "services/entities/operating_systems";
|
||||
@@ -130,34 +131,19 @@ const SoftwareOSTable = ({
|
||||
router.push(path);
|
||||
};
|
||||
|
||||
const getItemsCountText = () => {
|
||||
const count = data?.count;
|
||||
if (!data?.os_versions || !count) return "";
|
||||
|
||||
return count === 1 ? `${count} item` : `${count} items`;
|
||||
};
|
||||
|
||||
const getLastUpdatedText = () => {
|
||||
if (!data?.os_versions || !data?.counts_updated_at) return "";
|
||||
return (
|
||||
<LastUpdatedText
|
||||
lastUpdatedAt={data.counts_updated_at}
|
||||
whatToRetrieve="software"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderSoftwareCount = () => {
|
||||
const itemText = getItemsCountText();
|
||||
const lastUpdatedText = getLastUpdatedText();
|
||||
|
||||
if (!itemText) return null;
|
||||
if (!data?.os_versions || !data?.count) return null;
|
||||
|
||||
return (
|
||||
<div className={`${baseClass}__count`}>
|
||||
<span>{itemText}</span>
|
||||
{lastUpdatedText}
|
||||
</div>
|
||||
<>
|
||||
<TableCount name="items" count={data?.count} />
|
||||
{data?.os_versions && data?.counts_updated_at && (
|
||||
<LastUpdatedText
|
||||
lastUpdatedAt={data.counts_updated_at}
|
||||
whatToRetrieve="vulnerabilities"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
.software-os-table {
|
||||
&__count {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hosts-cell__wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -31,6 +31,7 @@ import Slider from "components/forms/fields/Slider";
|
||||
import CustomLink from "components/CustomLink";
|
||||
import LastUpdatedText from "components/LastUpdatedText";
|
||||
import { ITableQueryData } from "components/TableContainer/TableContainer";
|
||||
import TableCount from "components/TableContainer/TableCount";
|
||||
|
||||
import EmptySoftwareTable from "pages/SoftwarePage/components/EmptySoftwareTable";
|
||||
|
||||
@@ -189,23 +190,6 @@ const SoftwareTable = ({
|
||||
isSoftwareEnabled &&
|
||||
(!!tableData || query !== "" || softwareFilter === "vulnerableSoftware");
|
||||
|
||||
const getItemsCountText = () => {
|
||||
const count = data?.count;
|
||||
if (!tableData || !count) return "";
|
||||
|
||||
return count === 1 ? `${count} item` : `${count} items`;
|
||||
};
|
||||
|
||||
const getLastUpdatedText = () => {
|
||||
if (!tableData || !data?.counts_updated_at) return "";
|
||||
return (
|
||||
<LastUpdatedText
|
||||
lastUpdatedAt={data.counts_updated_at}
|
||||
whatToRetrieve="software"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const handleShowVersionsToggle = () => {
|
||||
const queryParams: Record<string, string | number | undefined> = {
|
||||
query,
|
||||
@@ -276,16 +260,18 @@ const SoftwareTable = ({
|
||||
};
|
||||
|
||||
const renderSoftwareCount = () => {
|
||||
const itemText = getItemsCountText();
|
||||
const lastUpdatedText = getLastUpdatedText();
|
||||
|
||||
if (!itemText) return null;
|
||||
if (!tableData || !data?.count) return null;
|
||||
|
||||
return (
|
||||
<div className={`${baseClass}__count`}>
|
||||
<span>{itemText}</span>
|
||||
{lastUpdatedText}
|
||||
</div>
|
||||
<>
|
||||
<TableCount name="items" count={data?.count} />
|
||||
{tableData && data?.counts_updated_at && (
|
||||
<LastUpdatedText
|
||||
lastUpdatedAt={data.counts_updated_at}
|
||||
whatToRetrieve="software"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
.software-table {
|
||||
&__count {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__vuln_dropdown {
|
||||
.Select-menu-outer {
|
||||
width: 250px;
|
||||
@@ -39,7 +33,6 @@
|
||||
.table-container {
|
||||
&__header {
|
||||
flex-direction: column-reverse; // Search bar on top
|
||||
margin-bottom: $pad-medium;
|
||||
|
||||
@media (min-width: $table-controls-break) {
|
||||
flex-direction: row;
|
||||
|
||||
+11
-25
@@ -15,6 +15,7 @@ import CustomLink from "components/CustomLink";
|
||||
import TableContainer from "components/TableContainer";
|
||||
import LastUpdatedText from "components/LastUpdatedText";
|
||||
import { ITableQueryData } from "components/TableContainer/TableContainer";
|
||||
import TableCount from "components/TableContainer/TableCount";
|
||||
|
||||
import EmptySoftwareTable from "pages/SoftwarePage/components/EmptySoftwareTable";
|
||||
import { IVulnerabilitiesResponse } from "services/entities/vulnerabilities";
|
||||
@@ -182,34 +183,19 @@ const SoftwareVulnerabilitiesTable = ({
|
||||
router.push(path);
|
||||
};
|
||||
|
||||
const getItemsCountText = () => {
|
||||
const count = data?.count;
|
||||
if (!data?.vulnerabilities || !count) return "";
|
||||
|
||||
return count === 1 ? `${count} item` : `${count} items`;
|
||||
};
|
||||
|
||||
const getLastUpdatedText = () => {
|
||||
if (!data?.vulnerabilities || !data?.counts_updated_at) return "";
|
||||
return (
|
||||
<LastUpdatedText
|
||||
lastUpdatedAt={data.counts_updated_at}
|
||||
whatToRetrieve="vulnerabilities"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderVulnerabilityCount = () => {
|
||||
const itemText = getItemsCountText();
|
||||
const lastUpdatedText = getLastUpdatedText();
|
||||
|
||||
if (!itemText) return null;
|
||||
if (!data?.vulnerabilities || !data?.count) return null;
|
||||
|
||||
return (
|
||||
<div className={`${baseClass}__count`}>
|
||||
<span>{itemText}</span>
|
||||
{lastUpdatedText}
|
||||
</div>
|
||||
<>
|
||||
<TableCount name="items" count={data?.count} />
|
||||
{data?.vulnerabilities && data?.counts_updated_at && (
|
||||
<LastUpdatedText
|
||||
lastUpdatedAt={data.counts_updated_at}
|
||||
whatToRetrieve="vulnerabilities"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ import Dropdown from "components/forms/fields/Dropdown";
|
||||
import TableContainer from "components/TableContainer";
|
||||
import InfoBanner from "components/InfoBanner/InfoBanner";
|
||||
import { ITableQueryData } from "components/TableContainer/TableContainer";
|
||||
import TableCount from "components/TableContainer/TableCount";
|
||||
import TableDataError from "components/DataError";
|
||||
import { IActionButtonProps } from "components/TableContainer/DataTable/ActionButton/ActionButton";
|
||||
import TeamsDropdown from "components/TeamsDropdown";
|
||||
@@ -1403,18 +1404,10 @@ const ManageHostsPage = ({
|
||||
};
|
||||
|
||||
const renderHostCount = useCallback(() => {
|
||||
const count = hostsCount;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${baseClass}__count ${
|
||||
isLoadingHostsCount ? "count-loading" : ""
|
||||
}`}
|
||||
>
|
||||
{count !== undefined && (
|
||||
<span>{`${count} host${count === 1 ? "" : "s"}`}</span>
|
||||
)}
|
||||
{!!count && (
|
||||
<>
|
||||
<TableCount name="hosts" count={hostsCount} />
|
||||
{!!hostsCount && (
|
||||
<Button
|
||||
className={`${baseClass}__export-btn`}
|
||||
onClick={onExportHostsResults}
|
||||
@@ -1426,7 +1419,7 @@ const ManageHostsPage = ({
|
||||
</>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}, [isLoadingHostsCount, hostsCount]);
|
||||
|
||||
|
||||
@@ -273,14 +273,6 @@
|
||||
}
|
||||
|
||||
&__export-btn {
|
||||
margin-left: $pad-medium;
|
||||
|
||||
img {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
margin-left: 8px;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
margin-left: $pad-xsmall;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import Button from "components/buttons/Button";
|
||||
import EmptyTable from "components/EmptyTable";
|
||||
import Icon from "components/Icon";
|
||||
import TableContainer from "components/TableContainer";
|
||||
import TableCount from "components/TableContainer/TableCount";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { Row } from "react-table";
|
||||
import {
|
||||
@@ -119,15 +120,14 @@ const HQRTable = ({
|
||||
}, [lastFetched, hostName, reportClipped]);
|
||||
|
||||
const renderCount = useCallback(() => {
|
||||
const count = filteredResults.length;
|
||||
return (
|
||||
<div className={`${baseClass}__results-count-and-last-fetched`}>
|
||||
<span>{`${count} result${count === 1 ? "" : "s"}`}</span>
|
||||
<>
|
||||
<TableCount name="results" count={filteredResults.length} />
|
||||
<span className="last-fetched">
|
||||
Last fetched{" "}
|
||||
<HumanTimeDiffWithFleetLaunchCutoff timeString={lastFetched ?? ""} />
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}, [filteredResults.length, lastFetched]);
|
||||
|
||||
|
||||
@@ -1,30 +1,14 @@
|
||||
.hqr-table {
|
||||
gap: $pad-medium;
|
||||
&__results-count-and-last-fetched {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: $pad-small;
|
||||
|
||||
.last-fetched {
|
||||
font-weight: initial;
|
||||
@include grey-text;
|
||||
}
|
||||
.last-fetched {
|
||||
font-weight: initial;
|
||||
@include grey-text;
|
||||
}
|
||||
|
||||
&__results-cta {
|
||||
display: flex;
|
||||
gap: $pad-medium;
|
||||
.button {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__export-btn {
|
||||
.children-wrapper {
|
||||
align-self: flex-end;
|
||||
}
|
||||
.icon {
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
|
||||
&__query-info {
|
||||
|
||||
+3
-12
@@ -7,8 +7,10 @@ import { getNextLocationPath } from "utilities/helpers";
|
||||
|
||||
import TableContainer from "components/TableContainer";
|
||||
import { ITableQueryData } from "components/TableContainer/TableContainer";
|
||||
import { generateResultsCountText } from "components/TableContainer/utilities/TableContainerUtils";
|
||||
|
||||
import EmptySoftwareTable from "pages/SoftwarePage/components/EmptySoftwareTable";
|
||||
import TableCount from "components/TableContainer/TableCount";
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 20;
|
||||
|
||||
@@ -26,16 +28,6 @@ interface IHostSoftwareTableProps {
|
||||
pagePath: string;
|
||||
}
|
||||
|
||||
const SoftwareCount = ({ count }: { count: number }) => {
|
||||
return (
|
||||
<div className={`${baseClass}__count`}>
|
||||
<span>
|
||||
{count === 1 ? `${count} software item` : `${count} software items`}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const HostSoftwareTable = ({
|
||||
tableConfig,
|
||||
data,
|
||||
@@ -108,7 +100,7 @@ const HostSoftwareTable = ({
|
||||
|
||||
const memoizedSoftwareCount = useCallback(() => {
|
||||
const count = data?.count || data?.software.length || 0;
|
||||
return <SoftwareCount count={count} />;
|
||||
return <TableCount name="items" count={count} />;
|
||||
}, [data?.count, data?.software.length]);
|
||||
|
||||
const memoizedEmptyComponent = useCallback(() => {
|
||||
@@ -119,7 +111,6 @@ const HostSoftwareTable = ({
|
||||
<div className={baseClass}>
|
||||
<TableContainer
|
||||
renderCount={memoizedSoftwareCount}
|
||||
resultsTitle="software items"
|
||||
columnConfigs={tableConfig}
|
||||
data={data?.software || []}
|
||||
isLoading={isLoading}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from "react";
|
||||
import React, { useCallback } from "react";
|
||||
|
||||
import { IHostUser } from "interfaces/host_users";
|
||||
import TableContainer from "components/TableContainer";
|
||||
import { ITableQueryData } from "components/TableContainer/TableContainer";
|
||||
import TableCount from "components/TableContainer/TableCount";
|
||||
import EmptyTable from "components/EmptyTable";
|
||||
import CustomLink from "components/CustomLink";
|
||||
import Card from "components/Card";
|
||||
@@ -28,6 +29,10 @@ const Users = ({
|
||||
}: IUsersProps): JSX.Element => {
|
||||
const tableHeaders = generateUsersTableHeaders();
|
||||
|
||||
const renderUsersCount = useCallback(() => {
|
||||
return <TableCount name="users" count={usersState.length} />;
|
||||
}, [usersState.length]);
|
||||
|
||||
if (!hostUsersEnabled) {
|
||||
return (
|
||||
<Card
|
||||
@@ -72,7 +77,6 @@ const Users = ({
|
||||
defaultSortDirection="asc"
|
||||
inputPlaceHolder="Search users by username"
|
||||
onQueryChange={onUsersTableSearchChange}
|
||||
resultsTitle="users"
|
||||
emptyComponent={() => (
|
||||
<EmptyTable
|
||||
header="No users match your search criteria"
|
||||
@@ -83,7 +87,7 @@ const Users = ({
|
||||
isAllPagesSelected={false}
|
||||
searchable
|
||||
wideSearch
|
||||
filteredCount={usersState.length}
|
||||
renderCount={renderUsersCount}
|
||||
isClientSidePagination
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -34,6 +34,7 @@ import teamPoliciesAPI, {
|
||||
import teamsAPI, { ILoadTeamResponse } from "services/entities/teams";
|
||||
|
||||
import { ITableQueryData } from "components/TableContainer/TableContainer";
|
||||
import TableCount from "components/TableContainer/TableCount";
|
||||
import Button from "components/buttons/Button";
|
||||
// @ts-ignore
|
||||
import Dropdown from "components/forms/fields/Dropdown";
|
||||
@@ -624,19 +625,21 @@ const ManagePolicyPage = ({
|
||||
}
|
||||
|
||||
const renderPoliciesCount = (count?: number) => {
|
||||
// Show count if there is no errors AND there are policy results or a search filter
|
||||
const showCount =
|
||||
count !== undefined &&
|
||||
!policiesErrors &&
|
||||
(policyResults || searchQuery !== "");
|
||||
// Hide count if fetching count || there are errors OR there are no policy results with no a search filter
|
||||
const isFetchingCount = isAnyTeamSelected
|
||||
? isFetchingTeamCountMergeInherited
|
||||
: isFetchingGlobalCount;
|
||||
|
||||
return (
|
||||
<div className={`${baseClass}__count`}>
|
||||
{showCount && (
|
||||
<span>{`${count} polic${count === 1 ? "y" : "ies"}`}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
const hideCount =
|
||||
isFetchingCount ||
|
||||
policiesErrors ||
|
||||
(!policyResults && searchQuery === "");
|
||||
|
||||
if (hideCount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <TableCount name="policies" count={count} />;
|
||||
};
|
||||
|
||||
const renderMainTable = () => {
|
||||
@@ -658,9 +661,7 @@ const ManagePolicyPage = ({
|
||||
currentTeam={currentTeamSummary}
|
||||
currentAutomatedPolicies={currentAutomatedPolicies}
|
||||
renderPoliciesCount={() =>
|
||||
(!isFetchingTeamCountMergeInherited &&
|
||||
renderPoliciesCount(teamPoliciesCountMergeInherited)) ||
|
||||
null
|
||||
renderPoliciesCount(teamPoliciesCountMergeInherited)
|
||||
}
|
||||
isPremiumTier={isPremiumTier}
|
||||
searchQuery={searchQuery}
|
||||
@@ -683,11 +684,7 @@ const ManagePolicyPage = ({
|
||||
currentTeam={currentTeamSummary}
|
||||
currentAutomatedPolicies={currentAutomatedPolicies}
|
||||
isPremiumTier={isPremiumTier}
|
||||
renderPoliciesCount={() =>
|
||||
(!isFetchingGlobalCount &&
|
||||
renderPoliciesCount(globalPoliciesCount)) ||
|
||||
null
|
||||
}
|
||||
renderPoliciesCount={() => renderPoliciesCount(globalPoliciesCount)}
|
||||
searchQuery={searchQuery}
|
||||
sortHeader={sortHeader}
|
||||
sortDirection={sortDirection}
|
||||
|
||||
@@ -3,16 +3,6 @@
|
||||
margin: 2rem auto 1.25rem;
|
||||
}
|
||||
|
||||
&__export-btn {
|
||||
img {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
margin-left: 8px;
|
||||
position: relative;
|
||||
bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.data-table__wrapper {
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ interface IManageQueriesPageProps {
|
||||
location: {
|
||||
pathname: string;
|
||||
query: {
|
||||
platform?: string;
|
||||
platform?: SupportedPlatform;
|
||||
page?: string;
|
||||
query?: string;
|
||||
order_key?: string;
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React, { useContext, useCallback, useMemo } from "react";
|
||||
import React, {
|
||||
useContext,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
useEffect,
|
||||
} from "react";
|
||||
import { InjectedRouter } from "react-router";
|
||||
|
||||
import { AppContext } from "context/app";
|
||||
import { IEmptyTableProps } from "interfaces/empty_table";
|
||||
import { SupportedPlatform } from "interfaces/platform";
|
||||
import { IEnhancedQuery } from "interfaces/schedulable_query";
|
||||
import { ITableQueryData } from "components/TableContainer/TableContainer";
|
||||
import { IActionButtonProps } from "components/TableContainer/DataTable/ActionButton/ActionButton";
|
||||
import PATHS from "router/paths";
|
||||
import { getNextLocationPath } from "utilities/helpers";
|
||||
import { checkPlatformCompatibility } from "utilities/sql_tools";
|
||||
import Button from "components/buttons/Button";
|
||||
import TableContainer from "components/TableContainer";
|
||||
import TableCount from "components/TableContainer/TableCount";
|
||||
import CustomLink from "components/CustomLink";
|
||||
import EmptyTable from "components/EmptyTable";
|
||||
// @ts-ignore
|
||||
@@ -29,7 +38,7 @@ export interface IQueriesTableProps {
|
||||
isAnyTeamObserverPlus: boolean;
|
||||
router?: InjectedRouter;
|
||||
queryParams?: {
|
||||
platform?: string;
|
||||
platform?: SupportedPlatform;
|
||||
page?: string;
|
||||
query?: string;
|
||||
order_key?: string;
|
||||
@@ -92,6 +101,38 @@ const QueriesTable = ({
|
||||
}: IQueriesTableProps): JSX.Element | null => {
|
||||
const { currentUser } = useContext(AppContext);
|
||||
|
||||
// Client side filtering bugs fixed with bypassing TableContainer filters
|
||||
// queriesState tracks search filter and compatible platform filter
|
||||
// to correctly show filtered queries and filtered count
|
||||
// isQueryStateLoading prevents flashing of unfiltered count during clientside filtering
|
||||
const [queriesState, setQueriesState] = useState<IEnhancedQuery[]>([]);
|
||||
const [isQueriesStateLoading, setIsQueriesStateLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
setIsQueriesStateLoading(true);
|
||||
if (queriesList) {
|
||||
setQueriesState(
|
||||
queriesList.filter((query) => {
|
||||
const filterSearchQuery = queryParams?.query
|
||||
? query.name
|
||||
.toLowerCase()
|
||||
.includes(queryParams?.query.toLowerCase())
|
||||
: true;
|
||||
|
||||
const compatiblePlatforms =
|
||||
checkPlatformCompatibility(query.query).platforms || [];
|
||||
|
||||
const filterCompatiblePlatform = queryParams?.platform
|
||||
? compatiblePlatforms.includes(queryParams?.platform)
|
||||
: true;
|
||||
|
||||
return filterSearchQuery && filterCompatiblePlatform;
|
||||
}) || []
|
||||
);
|
||||
}
|
||||
setIsQueriesStateLoading(false);
|
||||
}, [queriesList, queryParams?.query]);
|
||||
|
||||
// Functions to avoid race conditions
|
||||
const initialSearchQuery = (() => queryParams?.query ?? "")();
|
||||
const initialSortHeader = (() =>
|
||||
@@ -236,6 +277,15 @@ const QueriesTable = ({
|
||||
);
|
||||
}, [platform, queryParams, router]);
|
||||
|
||||
const renderQueriesCount = useCallback(() => {
|
||||
// Fixes flashing incorrect count before clientside filtering
|
||||
if (isQueriesStateLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <TableCount name="queries" count={queriesState?.length} />;
|
||||
}, [queriesState, isQueriesStateLoading]);
|
||||
|
||||
const columnConfigs = useMemo(
|
||||
() =>
|
||||
currentUser &&
|
||||
@@ -281,14 +331,15 @@ const QueriesTable = ({
|
||||
} as IActionButtonProps),
|
||||
[onDeleteQueryClick]
|
||||
);
|
||||
|
||||
return columnConfigs && !isLoading ? (
|
||||
<div className={`${baseClass}`}>
|
||||
<TableContainer
|
||||
resultsTitle="queries"
|
||||
columnConfigs={columnConfigs}
|
||||
data={queriesList}
|
||||
filters={{ global: trimmedSearchQuery }}
|
||||
isLoading={isLoading}
|
||||
data={queriesState}
|
||||
filters={{ name: trimmedSearchQuery }}
|
||||
isLoading={isLoading || isQueriesStateLoading}
|
||||
defaultSortHeader={sortHeader || DEFAULT_SORT_HEADER}
|
||||
defaultSortDirection={sortDirection || DEFAULT_SORT_DIRECTION}
|
||||
defaultSearchQuery={trimmedSearchQuery}
|
||||
@@ -308,7 +359,7 @@ const QueriesTable = ({
|
||||
primarySelectAction={deleteQueryTableActionButtonProps}
|
||||
// TODO - consolidate this functionality within `filters`
|
||||
selectedDropdownFilter={platform}
|
||||
show0Count
|
||||
renderCount={renderQueriesCount}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -14,6 +14,8 @@ import { IQueryReport, IQueryReportResultRow } from "interfaces/query_report";
|
||||
import Button from "components/buttons/Button";
|
||||
import Icon from "components/Icon/Icon";
|
||||
import TableContainer from "components/TableContainer";
|
||||
import TableCount from "components/TableContainer/TableCount";
|
||||
import { generateResultsCountText } from "components/TableContainer/utilities/TableContainerUtils";
|
||||
import TooltipWrapper from "components/TooltipWrapper";
|
||||
import EmptyTable from "components/EmptyTable";
|
||||
|
||||
@@ -102,7 +104,7 @@ const QueryReport = ({
|
||||
|
||||
if (isClipped) {
|
||||
return (
|
||||
<div className={`${baseClass}__count `}>
|
||||
<>
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<>
|
||||
@@ -115,16 +117,13 @@ const QueryReport = ({
|
||||
</>
|
||||
}
|
||||
>
|
||||
{`${count} result${count === 1 ? "" : "s"}`}
|
||||
{generateResultsCountText("results", count)}
|
||||
</TooltipWrapper>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={`${baseClass}__count `}>
|
||||
<span>{`${count} result${count === 1 ? "" : "s"}`}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return <TableCount name="results" count={count} />;
|
||||
}, [filteredResults.length, isClipped]);
|
||||
|
||||
const renderTable = () => {
|
||||
|
||||
@@ -8,21 +8,6 @@
|
||||
margin-right: $pad-medium;
|
||||
}
|
||||
|
||||
&__export-btn {
|
||||
img {
|
||||
width: 13px;
|
||||
margin-left: 8px;
|
||||
position: relative;
|
||||
bottom: 2px;
|
||||
}
|
||||
}
|
||||
&__show-query-btn {
|
||||
img {
|
||||
width: 13px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.data-table__wrapper {
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user