Skip to content

Commit

Permalink
Feat UI finish cert api integration (#26598)
Browse files Browse the repository at this point in the history
For #25464

UI work to integrate the host certificates UI with the finished API
endpoints


- [x] Manual QA must be performed in the three main OSs, macOS, Windows
and Linux.
  • Loading branch information
ghernandez345 authored Feb 26, 2025
1 parent 0adf67e commit f49a45f
Show file tree
Hide file tree
Showing 10 changed files with 340 additions and 164 deletions.
2 changes: 1 addition & 1 deletion frontend/__mocks__/certificatesMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const DEFAULT_HOST_CERTIFICATE_MOCK: IHostCertificate = {
key_algorithm: "rsaEncryption",
key_strength: 2048,
key_usage: "CRL Sign, Key Cert Sign",
serial: 1,
serial: "123",
signing_algorithm: "sha256WithRSAEncryption",
subject: {
country: "US",
Expand Down
2 changes: 1 addition & 1 deletion frontend/interfaces/certificates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface IHostCertificate {
key_algorithm: string;
key_strength: number;
key_usage: string;
serial: number;
serial: string;
signing_algorithm: string;
subject: {
country: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { IDeviceUserResponse, IHostDevice } from "interfaces/host";
import createMockHost from "__mocks__/hostMock";
import mockServer from "test/mock-server";
import { createCustomRenderer } from "test/test-utils";
import { customDeviceHandler } from "test/handlers/device-handler";
import {
customDeviceHandler,
defaultDeviceCertificatesHandler,
defaultDeviceHandler,
} from "test/handlers/device-handler";
import DeviceUserPage from "./DeviceUserPage";

const mockRouter = {
Expand Down Expand Up @@ -34,12 +38,14 @@ const mockLocation = {

describe("Device User Page", () => {
it("hides the software tab if the device has no software", async () => {
mockServer.use(defaultDeviceHandler);
mockServer.use(defaultDeviceCertificatesHandler);

const render = createCustomRenderer({
withBackendMock: true,
});

// TODO: fix return type from render
const { user } = render(
render(
<DeviceUserPage
router={mockRouter}
params={{ device_auth_token: "testToken" }}
Expand All @@ -51,14 +57,61 @@ describe("Device User Page", () => {
await screen.findByText("About");

expect(screen.queryByText(/Software/)).not.toBeInTheDocument();
});

it("hides the certificates card if the device has no certificates", async () => {
mockServer.use(defaultDeviceHandler);
mockServer.use(defaultDeviceCertificatesHandler);

const render = createCustomRenderer({
withBackendMock: true,
});

render(
<DeviceUserPage
router={mockRouter}
params={{ device_auth_token: "testToken" }}
location={mockLocation}
/>
);

// waiting for the device data to render
await screen.findByText("About");

expect(screen.queryByText(/Certificates/)).not.toBeInTheDocument();
});

it("hides the certificates card if the device is not an apple device (mac, iphone, ipad)", async () => {
const host = createMockHost() as IHostDevice;
host.mdm.enrollment_status = "On (manual)";
host.platform = "windows";
host.dep_assigned_to_fleet = false;

mockServer.use(customDeviceHandler({ host }));
mockServer.use(defaultDeviceCertificatesHandler);

const render = createCustomRenderer({
withBackendMock: true,
});

render(
<DeviceUserPage
router={mockRouter}
params={{ device_auth_token: "testToken" }}
location={mockLocation}
/>
);

// waiting for the device data to render
await screen.findByText("About");

// TODO: Fix this to the new copy
// expect(screen.getByText("No software detected")).toBeInTheDocument();
expect(screen.queryByText(/Certificates/)).not.toBeInTheDocument();
});

describe("MDM enrollment", () => {
const setupTest = async (overrides: Partial<IDeviceUserResponse>) => {
mockServer.use(customDeviceHandler(overrides));
mockServer.use(defaultDeviceCertificatesHandler);

const render = createCustomRenderer({
withBackendMock: true,
Expand Down
59 changes: 32 additions & 27 deletions frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { IHostPolicy } from "interfaces/policy";
import { IDeviceGlobalConfig } from "interfaces/config";
import { IHostSoftware } from "interfaces/software";
import { IHostCertificate } from "interfaces/certificates";
import { isAppleDevice } from "interfaces/platform";

import DeviceUserError from "components/DeviceUserError";
// @ts-ignore
Expand Down Expand Up @@ -74,6 +75,9 @@ const FREE_TAB_PATHS = [
PATHS.DEVICE_USER_DETAILS_SOFTWARE,
] as const;

const DEFAULT_CERTIFICATES_PAGE_SIZE = 500;
const DEFAULT_CERTIFICATES_PAGE = 0;

interface IDeviceUserPageProps {
location: {
pathname: string;
Expand Down Expand Up @@ -153,19 +157,6 @@ const DeviceUserPage = ({
}
);

const {
data: deviceCertificates,
isLoading: isLoadingDeviceCertificates,
isError: isErrorDeviceCertificates,
} = useQuery(
["hostCertificates", deviceAuthToken],
() => deviceUserAPI.getDeviceCertificates(deviceAuthToken),
{
...DEFAULT_USE_QUERY_OPTIONS,
enabled: !!deviceUserAPI,
}
);

const refetchExtensions = () => {
deviceMapping !== null && refetchDeviceMapping();
};
Expand Down Expand Up @@ -262,6 +253,25 @@ const DeviceUserPage = ({
self_service: hasSelfService = false,
} = dupResponse || {};
const isPremiumTier = license?.tier === "premium";
const isAppleHost = host && isAppleDevice(host.platform);

const {
data: deviceCertificates,
isLoading: isLoadingDeviceCertificates,
isError: isErrorDeviceCertificates,
} = useQuery(
["hostCertificates", deviceAuthToken],
() =>
deviceUserAPI.getDeviceCertificates(
deviceAuthToken,
DEFAULT_CERTIFICATES_PAGE,
DEFAULT_CERTIFICATES_PAGE_SIZE
),
{
...DEFAULT_USE_QUERY_OPTIONS,
enabled: !!deviceUserAPI && isAppleHost,
}
);

const summaryData = normalizeEmptyValues(pick(host, HOST_SUMMARY_DATA));

Expand Down Expand Up @@ -377,13 +387,9 @@ const DeviceUserPage = ({
const isSoftwareEnabled = !!globalConfig?.features
?.enable_software_inventory;

const isDarwinHost = host?.platform === "darwin";
const isIosOrIpadosHost =
host?.platform === "ios" || host?.platform === "ipados";

return (
<div className="core-wrapper">
{!host || isLoadingHost ? (
{!host || isLoadingHost || isLoadingDeviceCertificates ? (
<Spinner />
) : (
<div className={`${baseClass} main-content`}>
Expand Down Expand Up @@ -447,15 +453,14 @@ const DeviceUserPage = ({
deviceMapping={deviceMapping}
munki={deviceMacAdminsData?.munki}
/>
{(isIosOrIpadosHost || isDarwinHost) &&
deviceCertificates?.certificates.length && (
<CertificatesCard
isMyDevicePage
data={deviceCertificates}
hostPlatform={host.platform}
onSelectCertificate={onSelectCertificate}
/>
)}
{isAppleHost && deviceCertificates?.certificates.length && (
<CertificatesCard
isMyDevicePage
data={deviceCertificates}
hostPlatform={host.platform}
onSelectCertificate={onSelectCertificate}
/>
)}
</TabPanel>
{isPremiumTier && isSoftwareEnabled && hasSelfService && (
<TabPanel>
Expand Down
36 changes: 28 additions & 8 deletions frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import activitiesAPI, {
IHostPastActivitiesResponse,
IHostUpcomingActivitiesResponse,
} from "services/entities/activities";
import hostAPI from "services/entities/hosts";
import hostAPI, { IGetHostCertificatesResponse } from "services/entities/hosts";
import teamAPI, { ILoadTeamsResponse } from "services/entities/teams";

import {
Expand Down Expand Up @@ -132,6 +132,8 @@ interface IHostDetailsSubNavItem {
}

const DEFAULT_ACTIVITY_PAGE_SIZE = 8;
const DEFAULT_CERTIFICATES_PAGE_SIZE = 500;
const DEFAULT_CERTIFICATES_PAGE = 0;

const HostDetailsPage = ({
router,
Expand Down Expand Up @@ -461,12 +463,30 @@ const HostDetailsPage = ({
data: hostCertificates,
isLoading: isLoadingHostCertificates,
isError: isErrorHostCertificates,
} = useQuery(
["hostCertificates", host_id],
() => hostAPI.getHostCertificates(hostIdFromURL),
} = useQuery<
IGetHostCertificatesResponse,
Error,
IGetHostCertificatesResponse
>(
[
"host-certificates",
host_id,
DEFAULT_CERTIFICATES_PAGE,
DEFAULT_CERTIFICATES_PAGE_SIZE,
],
() =>
hostAPI.getHostCertificates(
hostIdFromURL,
DEFAULT_CERTIFICATES_PAGE,
DEFAULT_CERTIFICATES_PAGE_SIZE
),
{
...DEFAULT_USE_QUERY_OPTIONS,
enabled: !!hostIdFromURL,
enabled:
!!hostIdFromURL &&
(host?.platform === "darwin" ||
host?.platform === "ios" ||
host?.platform === "ipados"),
}
);

Expand Down Expand Up @@ -756,7 +776,8 @@ const HostDetailsPage = ({
!host ||
isLoadingHost ||
pastActivitiesIsLoading ||
upcomingActivitiesIsLoading
upcomingActivitiesIsLoading ||
isLoadingHostCertificates
) {
return <Spinner />;
}
Expand Down Expand Up @@ -828,8 +849,7 @@ const HostDetailsPage = ({
};

const isDarwinHost = host.platform === "darwin";
const isIosOrIpadosHost =
host.platform === "ios" || host.platform === "ipados";
const isIosOrIpadosHost = isIPadOrIPhone(host.platform);

const detailsPanelClass = classNames(`${baseClass}__details-panel`, {
[`${baseClass}__details-panel--ios-grid`]: isIosOrIpadosHost,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { IHostCertificate } from "interfaces/certificates";

import TableContainer from "components/TableContainer";
import CustomLink from "components/CustomLink";
import TableCount from "components/TableContainer/TableCount";

import generateTableConfig from "./CertificatesTableConfig";

Expand Down Expand Up @@ -50,6 +51,8 @@ const CertificatesTable = ({
isLoading={false}
onClickRow={onClickTableRow}
renderTableHelpText={() => helpText}
renderCount={() => <TableCount name="certificates" count={data.length} />}
disablePagination
/>
);
};
Expand Down
Loading

0 comments on commit f49a45f

Please sign in to comment.