Use server-side sort for host certificates (#26898)

This commit is contained in:
Sarah Gillespie
2025-03-07 12:28:07 -06:00
committed by GitHub
parent 44a985d06e
commit e75ef8bc1c
8 changed files with 134 additions and 48 deletions
+7
View File
@@ -1,3 +1,5 @@
import { IListSort } from "./list_options";
export interface IHostCertificate {
id: number;
not_valid_after: string;
@@ -22,3 +24,8 @@ export interface IHostCertificate {
common_name: string;
};
}
export const CERTIFICATES_DEFAULT_SORT: IListSort = {
order_key: "common_name",
order_direction: "asc",
} as const;
+13
View File
@@ -0,0 +1,13 @@
/**
* Represents query params used as list options by the Fleet API
*/
export interface IListOptions {
page: number;
per_page: number;
order_key: string;
order_direction: string;
}
export type IListSort = Pick<IListOptions, "order_key" | "order_direction">;
export type IListPagination = Pick<IListOptions, "page" | "per_page">;
@@ -6,7 +6,9 @@ import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
import { pick, findIndex } from "lodash";
import { NotificationContext } from "context/notification";
import deviceUserAPI, {
IGetDeviceCertsRequestParams,
IGetDeviceCertificatesResponse,
} from "services/entities/device_user";
import diskEncryptionAPI from "services/entities/disk_encryption";
@@ -16,10 +18,14 @@ import {
IDeviceUserResponse,
IHostDevice,
} from "interfaces/host";
import { IListSort } from "interfaces/list_options";
import { IHostPolicy } from "interfaces/policy";
import { IDeviceGlobalConfig } from "interfaces/config";
import { IHostSoftware } from "interfaces/software";
import { IHostCertificate } from "interfaces/certificates";
import {
IHostCertificate,
CERTIFICATES_DEFAULT_SORT,
} from "interfaces/certificates";
import { isAppleDevice } from "interfaces/platform";
import DeviceUserError from "components/DeviceUserError";
@@ -137,6 +143,9 @@ const DeviceUserPage = ({
const [certificatePage, setCertificatePage] = useState(
DEFAULT_CERTIFICATES_PAGE
);
const [sortCerts, setSortCerts] = useState<IListSort>({
...CERTIFICATES_DEFAULT_SORT,
});
const { data: deviceMapping, refetch: refetchDeviceMapping } = useQuery(
["deviceMapping", deviceAuthToken],
@@ -174,24 +183,27 @@ const DeviceUserPage = ({
IGetDeviceCertificatesResponse,
Error,
IGetDeviceCertificatesResponse,
Array<{ scope: string; token: string; page: number; perPage: number }>
Array<IGetDeviceCertsRequestParams & { scope: "device-certificates" }>
>(
[
{
scope: "device-certificates",
token: deviceAuthToken,
page: certificatePage,
perPage: DEFAULT_CERTIFICATES_PAGE_SIZE,
per_page: DEFAULT_CERTIFICATES_PAGE_SIZE,
order_key: sortCerts.order_key,
order_direction: sortCerts.order_direction,
},
],
({ queryKey: [{ token, page, perPage }] }) =>
deviceUserAPI.getDeviceCertificates(token, page, perPage),
({ queryKey }) => deviceUserAPI.getDeviceCertificates(queryKey[0]),
{
...DEFAULT_USE_QUERY_OPTIONS,
// FIXME: is it worth disabling for unsupported platforms? we'd have to workaround the a
// catch-22 where we need to know the platform to know if it's supported but we also need to
// be able to include the cert refetch in the hosts query hook.
enabled: !!deviceUserAPI,
keepPreviousData: true,
staleTime: 15000,
}
);
@@ -486,12 +498,15 @@ const DeviceUserPage = ({
isError={isErrorDeviceCertificates}
page={certificatePage}
pageSize={DEFAULT_CERTIFICATES_PAGE_SIZE}
sortHeader={sortCerts.order_key}
sortDirection={sortCerts.order_direction}
hostPlatform={host.platform}
onSelectCertificate={onSelectCertificate}
onNextPage={() => setCertificatePage(certificatePage + 1)}
onPreviousPage={() =>
setCertificatePage(certificatePage - 1)
}
onSortChange={setSortCerts}
/>
)}
</TabPanel>
@@ -15,7 +15,10 @@ import activitiesAPI, {
IHostPastActivitiesResponse,
IHostUpcomingActivitiesResponse,
} from "services/entities/activities";
import hostAPI, { IGetHostCertificatesResponse } from "services/entities/hosts";
import hostAPI, {
IGetHostCertificatesResponse,
IGetHostCertsRequestParams,
} from "services/entities/hosts";
import teamAPI, { ILoadTeamsResponse } from "services/entities/teams";
import {
@@ -27,12 +30,16 @@ import {
IPackStats,
} from "interfaces/host";
import { ILabel } from "interfaces/label";
import { IListSort } from "interfaces/list_options";
import { IHostPolicy } from "interfaces/policy";
import { IQueryStats } from "interfaces/query_stats";
import { IHostSoftware } from "interfaces/software";
import { ITeam } from "interfaces/team";
import { IHostUpcomingActivity } from "interfaces/activity";
import { IHostCertificate } from "interfaces/certificates";
import {
IHostCertificate,
CERTIFICATES_DEFAULT_SORT,
} from "interfaces/certificates";
import { normalizeEmptyValues, wrapFleetHelper } from "utilities/helpers";
import permissions from "utilities/permissions";
@@ -225,6 +232,9 @@ const HostDetailsPage = ({
const [certificatePage, setCertificatePage] = useState(
DEFAULT_CERTIFICATES_PAGE
);
const [sortCerts, setSortCerts] = useState<IListSort>({
...CERTIFICATES_DEFAULT_SORT,
});
const { data: teams } = useQuery<ILoadTeamsResponse, Error, ITeam[]>(
"teams",
@@ -283,31 +293,33 @@ const HostDetailsPage = ({
const {
data: hostCertificates,
isLoading: isLoadingHostCertificates,
isError: isErrorHostCertificates,
refetch: refetchHostCertificates,
} = useQuery<
IGetHostCertificatesResponse,
Error,
IGetHostCertificatesResponse,
Array<{ scope: string; hostId: number; page: number; perPage: number }>
Array<IGetHostCertsRequestParams & { scope: "host-certificates" }>
>(
[
{
scope: "host-certificates",
hostId: hostIdFromURL,
host_id: hostIdFromURL,
page: certificatePage,
perPage: DEFAULT_CERTIFICATES_PAGE_SIZE,
per_page: DEFAULT_CERTIFICATES_PAGE_SIZE,
order_key: sortCerts.order_key,
order_direction: sortCerts.order_direction,
},
],
({ queryKey: [{ hostId, page, perPage }] }) =>
hostAPI.getHostCertificates(hostId, page, perPage),
({ queryKey }) => hostAPI.getHostCertificates(queryKey[0]),
{
...DEFAULT_USE_QUERY_OPTIONS,
// FIXME: is it worth disabling for unsupported platforms? we'd have to workaround the a
// catch-22 where we need to know the platform to know if it's supported but we also need to
// be able to include the cert refetch in the hosts query hook.
enabled: !!hostIdFromURL,
keepPreviousData: true,
staleTime: 15000,
}
);
@@ -786,8 +798,7 @@ const HostDetailsPage = ({
!host ||
isLoadingHost ||
pastActivitiesIsLoading ||
upcomingActivitiesIsLoading ||
isLoadingHostCertificates
upcomingActivitiesIsLoading
) {
return <Spinner />;
}
@@ -980,6 +991,9 @@ const HostDetailsPage = ({
onPreviousPage={() =>
setCertificatePage(certificatePage - 1)
}
sortDirection={sortCerts.order_direction}
sortHeader={sortCerts.order_key}
onSortChange={setSortCerts}
/>
)}
</TabPanel>
@@ -1,9 +1,11 @@
import React from "react";
import { IHostCertificate } from "interfaces/certificates";
import { HostPlatform } from "interfaces/platform";
import { IGetHostCertificatesResponse } from "services/entities/hosts";
import { IHostCertificate } from "interfaces/certificates";
import { IListSort } from "interfaces/list_options";
import { HostPlatform } from "interfaces/platform";
import Card from "components/Card";
import DataError from "components/DataError";
@@ -16,11 +18,14 @@ interface ICertificatesProps {
hostPlatform: HostPlatform;
page: number;
pageSize: number;
sortHeader: string;
sortDirection: string;
isError: boolean;
isMyDevicePage?: boolean;
onSelectCertificate: (certificate: IHostCertificate) => void;
onNextPage: () => void;
onPreviousPage: () => void;
onSortChange: ({ order_key, order_direction }: IListSort) => void;
}
const CertificatesCard = ({
@@ -29,10 +34,13 @@ const CertificatesCard = ({
isError,
page,
pageSize,
sortHeader,
sortDirection,
isMyDevicePage = false,
onSelectCertificate,
onNextPage,
onPreviousPage,
onSortChange,
}: ICertificatesProps) => {
const renderContent = () => {
if (isError) return <DataError />;
@@ -43,6 +51,9 @@ const CertificatesCard = ({
showHelpText={!isMyDevicePage && hostPlatform === "darwin"}
page={page}
pageSize={pageSize}
sortDirection={sortDirection}
sortHeader={sortHeader}
onSortChange={onSortChange}
onSelectCertificate={onSelectCertificate}
onNextPage={onNextPage}
onPreviousPage={onPreviousPage}
@@ -2,6 +2,7 @@ import React, { useCallback } from "react";
import { IHostCertificate } from "interfaces/certificates";
import { IGetHostCertificatesResponse } from "services/entities/hosts";
import { IListSort } from "interfaces/list_options";
import TableContainer from "components/TableContainer";
import CustomLink from "components/CustomLink";
@@ -17,9 +18,12 @@ interface ICertificatesTableProps {
showHelpText: boolean;
page: number;
pageSize: number;
sortHeader: string;
sortDirection: string;
onSelectCertificate: (certificate: IHostCertificate) => void;
onNextPage: () => void;
onPreviousPage: () => void;
onSortChange: ({ order_key, order_direction }: IListSort) => void;
}
const CertificatesTable = ({
@@ -27,9 +31,12 @@ const CertificatesTable = ({
showHelpText,
page,
pageSize,
sortDirection,
sortHeader,
onSelectCertificate,
onNextPage,
onPreviousPage,
onSortChange,
}: ICertificatesTableProps) => {
const tableConfig = generateTableConfig();
@@ -38,18 +45,23 @@ const CertificatesTable = ({
};
const onQueryChange = useCallback(
async (newTableQuery: ITableQueryData) => {
console.log(newTableQuery);
if (page === newTableQuery.pageIndex) return;
if (newTableQuery.pageIndex > page) {
onNextPage();
} else {
onPreviousPage();
(newQuery: ITableQueryData) => {
switch (true) {
case newQuery.pageIndex > page:
return onNextPage();
case newQuery.pageIndex < page:
return onPreviousPage();
case newQuery.sortHeader !== sortHeader ||
newQuery.sortDirection !== sortDirection:
return onSortChange({
order_key: newQuery.sortHeader,
order_direction: newQuery.sortDirection || "asc",
});
default:
return undefined; // noop
}
},
[onNextPage, onPreviousPage, page]
[onNextPage, onPreviousPage, onSortChange, page, sortDirection, sortHeader]
);
const helpText = showHelpText ? (
@@ -81,8 +93,11 @@ const CertificatesTable = ({
)}
pageSize={pageSize}
defaultPageIndex={page}
defaultSortHeader={sortHeader}
defaultSortDirection={sortDirection}
onQueryChange={onQueryChange}
disableNextPage={data?.meta.has_next_results === false}
manualSortBy
/>
);
};
+16 -10
View File
@@ -1,10 +1,10 @@
import { IDeviceUserResponse } from "interfaces/host";
import { IListOptions } from "interfaces/list_options";
import { IDeviceSoftware } from "interfaces/software";
import { IHostCertificate } from "interfaces/certificates";
import sendRequest from "services";
import endpoints from "utilities/endpoints";
import { buildQueryStringFromParams } from "utilities/url";
import { createMockGetHostCertificatesResponse } from "__mocks__/certificatesMock";
import { IHostSoftwareQueryParams } from "./hosts";
@@ -38,6 +38,10 @@ export interface IGetDeviceCertificatesResponse {
};
}
export interface IGetDeviceCertsRequestParams extends IListOptions {
token: string;
}
export default {
loadHostDetails: ({
token,
@@ -86,17 +90,19 @@ export default {
return sendRequest("POST", path);
},
getDeviceCertificates: (
deviceToken: string,
page = 0,
perPage = 10
): Promise<IGetDeviceCertificatesResponse> => {
getDeviceCertificates: ({
token,
page,
per_page,
order_key,
order_direction,
}: IGetDeviceCertsRequestParams): Promise<IGetDeviceCertificatesResponse> => {
const { DEVICE_CERTIFICATES } = endpoints;
const path = `${DEVICE_CERTIFICATES(
deviceToken
)}?${buildQueryStringFromParams({
const path = `${DEVICE_CERTIFICATES(token)}?${buildQueryStringFromParams({
page,
per_page: perPage,
per_page,
order_key,
order_direction,
})}`;
return sendRequest("GET", path);
+16 -11
View File
@@ -24,10 +24,7 @@ import {
import { IMunkiIssuesAggregate } from "interfaces/macadmins";
import { PlatformValueOptions, PolicyResponse } from "utilities/constants";
import { IHostCertificate } from "interfaces/certificates";
import {
createMockGetHostCertificatesResponse,
createMockHostCertificate,
} from "__mocks__/certificatesMock";
import { IListOptions } from "interfaces/list_options";
export interface ISortOption {
key: string;
@@ -176,6 +173,10 @@ export interface IHostSoftwareQueryKey extends IHostSoftwareQueryParams {
softwareUpdatedAt?: string;
}
export interface IGetHostCertsRequestParams extends IListOptions {
host_id: number;
}
export interface IGetHostCertificatesResponse {
certificates: IHostCertificate[];
meta: {
@@ -601,15 +602,19 @@ export default {
);
},
getHostCertificates: (
hostId: number,
page = 0,
perPage = 10
): Promise<IGetHostCertificatesResponse> => {
getHostCertificates: ({
host_id,
page,
per_page,
order_key,
order_direction,
}: IGetHostCertsRequestParams): Promise<IGetHostCertificatesResponse> => {
const { HOST_CERTIFICATES } = endpoints;
const path = `${HOST_CERTIFICATES(hostId)}?${buildQueryStringFromParams({
const path = `${HOST_CERTIFICATES(host_id)}?${buildQueryStringFromParams({
page,
per_page: perPage,
per_page,
order_key,
order_direction,
})}`;
return sendRequest("GET", path);