update host details and my device page to show users card for android devices (#32975)

resolves #32356

This updates the host details and my device pages to show the users card
that will show the idp info on android devices

this also adds some tests for the various rendering states of the users
card component

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
This commit is contained in:
Gabriel Hernandez 2025-09-17 18:00:59 +01:00 committed by GitHub
parent 3d0a0639f6
commit 697a0bdd0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 227 additions and 269 deletions

View file

@ -0,0 +1 @@
- update host details and my device UI to show the users card for android hosts

View file

@ -1,4 +1,4 @@
import { IHost } from "interfaces/host";
import { IHost, IHostEndUser } from "interfaces/host";
import { IHostMdmProfile } from "interfaces/mdm";
import { pick } from "lodash";
@ -240,4 +240,19 @@ export const createMockGetHostSoftwareResponse = (
};
};
export const DEFAULT_HOST_END_USER_MOCK: IHostEndUser = {
idp_department: "Engineering",
idp_info_updated_at: "2025-09-15T12:00:00Z",
idp_username: "jdoe",
idp_full_name: "John Doe",
idp_groups: ["GroupA", "GroupB"],
other_emails: [{ email: "other@example.com", source: "chrome" }],
};
export const createMockHostEndUser = (
overrides?: Partial<IHostEndUser>
): IHostEndUser => {
return { ...DEFAULT_HOST_END_USER_MOCK, ...overrides };
};
export default createMockHost;

View file

@ -2,7 +2,7 @@ import React from "react";
import { screen, waitFor } from "@testing-library/react";
import { IDeviceUserResponse, IHostDevice } from "interfaces/host";
import createMockHost from "__mocks__/hostMock";
import createMockHost, { createMockHostEndUser } from "__mocks__/hostMock";
import mockServer from "test/mock-server";
import { createCustomRenderer, createMockRouter } from "test/test-utils";
import createMockLicense from "__mocks__/licenseMock";
@ -106,6 +106,34 @@ describe("Device User Page", () => {
expect(screen.queryByText(/Certificates/)).not.toBeInTheDocument();
});
it("hides the user card if the device is not apple or android device", async () => {
const host = createMockHost() as IHostDevice;
host.platform = "windows";
host.end_users = [];
mockServer.use(customDeviceHandler({ host }));
mockServer.use(defaultDeviceCertificatesHandler);
mockServer.use(emptySetupExperienceHandler);
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(/Details/);
expect(screen.queryByText(/User/)).not.toBeInTheDocument();
});
describe("Setup experience software installation", () => {
const REGULAR_DUP_MATCHER = /Last fetched/;
const SETTING_UP_YOUR_DEVICE_MATCHER = /Setting up your device/;
@ -268,269 +296,4 @@ describe("Device User Page", () => {
expect(btn).toBeNull();
});
});
// // FIXME: revisit these tests when we have a better way to test modals
// describe("AutoEnrollMDMModal", () => {
// it("shows the pre-Sonoma body when the host is pre-Sonoma", async () => {
// const host = createMockHost() as IHostDevice;
// host.platform = "darwin";
// host.os_version = "macOS 13.1.1";
// host.dep_assigned_to_fleet = true;
// mockServer.use(
// customDeviceHandler({
// host,
// global_config: {
// mdm: { enabled_and_configured: true },
// features: { enable_software_inventory: false },
// },
// })
// );
// const render = createCustomRenderer({
// withBackendMock: true,
// });
// const { user } = render(
// <DeviceUserPage
// router={mockRouter}
// params={{ device_auth_token: "testToken" }}
// location={mockLocation}
// />
// );
// // waiting for the device data to render
// await screen.findByText("About");
// // open the modal
// await user.click(screen.getByRole("button", { name: "Turn on MDM" }));
// // waiting for the modal to render
// await screen.findByText("To turn on MDM,");
// // autoenroll-specific copy
// expect(
// screen.getByText("sudo profiles renew -type enrollment")
// ).toBeInTheDocument();
// // version-specific copy
// expect(screen.getByText("notification center")).toBeInTheDocument();
// });
// it("shows the Sonoma-and-above body when the host is Sonoma", async () => {
// const host = createMockHost() as IHostDevice;
// host.platform = "darwin";
// host.os_version = "macOS 14.7";
// host.dep_assigned_to_fleet = true;
// mockServer.use(
// customDeviceHandler({
// host,
// global_config: {
// mdm: { enabled_and_configured: true },
// features: { enable_software_inventory: false },
// },
// })
// );
// const render = createCustomRenderer({
// withBackendMock: true,
// });
// const { user } = render(
// <DeviceUserPage
// router={mockRouter}
// params={{ device_auth_token: "testToken" }}
// location={mockLocation}
// />
// );
// // waiting for the device data to render
// await screen.findByText("About");
// // open the modal
// await user.click(screen.getByRole("button", { name: "Turn on MDM" }));
// // waiting for the modal to render
// await screen.findByText("To turn on MDM,");
// // autoenroll-specific copy
// expect(
// screen.getByText("sudo profiles renew -type enrollment")
// ).toBeInTheDocument();
// // version-specific copy
// expect(screen.getByText("System Settings")).toBeInTheDocument();
// });
// it("shows the Sonoma-and-above body when the host is post-Sonoma", async () => {
// const host = createMockHost() as IHostDevice;
// host.platform = "darwin";
// host.os_version = "macOS 15.3";
// host.dep_assigned_to_fleet = true;
// mockServer.use(
// customDeviceHandler({
// host,
// global_config: {
// mdm: { enabled_and_configured: true },
// features: { enable_software_inventory: false },
// },
// })
// );
// const render = createCustomRenderer({
// withBackendMock: true,
// });
// const { user } = render(
// <DeviceUserPage
// router={mockRouter}
// params={{ device_auth_token: "testToken" }}
// location={mockLocation}
// />
// );
// // waiting for the device data to render
// await screen.findByText("About");
// // open the modal
// await user.click(screen.getByRole("button", { name: "Turn on MDM" }));
// // waiting for the modal to render
// await screen.findByText("To turn on MDM,");
// // autoenroll-specific copy
// expect(
// screen.getByText("sudo profiles renew -type enrollment")
// ).toBeInTheDocument();
// // version-specific copy
// expect(screen.getByText("System Settings")).toBeInTheDocument();
// });
// });
// // FIXME: revisit these tests when we have a better way to test modals
// describe("ManualEnrollMDMModal", () => {
// it("shows the pre-Seqouia body when the host is pre-Seqouia", async () => {
// const host = createMockHost() as IHostDevice;
// host.platform = "darwin";
// host.os_version = "macOS 14.1.1";
// mockServer.use(
// customDeviceHandler({
// host,
// global_config: {
// mdm: { enabled_and_configured: false },
// features: { enable_software_inventory: true },
// },
// })
// );
// const render = createCustomRenderer({
// withBackendMock: true,
// });
// const { user } = render(
// <DeviceUserPage
// router={mockRouter}
// params={{ device_auth_token: "testToken" }}
// location={mockLocation}
// />
// );
// // waiting for the device data to render
// await screen.findByText("About");
// // open the modal
// await user.click(screen.getByRole("button", { name: "Turn on MDM" }));
// // waiting for the modal to render
// await screen.findByText("To turn on MDM,");
// // manualenroll-specific copy
// expect(screen.getByText("Download your profile.")).toBeInTheDocument();
// // version-specific copy
// expect(screen.getByText("In the search bar")).toBeInTheDocument();
// });
// it("shows the Sequoia-and-above body when the host is Sequoia", async () => {
// const host = createMockHost() as IHostDevice;
// host.platform = "darwin";
// host.os_version = "macOS 15.3";
// mockServer.use(
// customDeviceHandler({
// host,
// global_config: {
// mdm: { enabled_and_configured: false },
// features: { enable_software_inventory: true },
// },
// })
// );
// const render = createCustomRenderer({
// withBackendMock: true,
// });
// const { user } = render(
// <DeviceUserPage
// router={mockRouter}
// params={{ device_auth_token: "testToken" }}
// location={mockLocation}
// />
// );
// // waiting for the device data to render
// await screen.findByText("About");
// // open the modal
// await user.click(screen.getByRole("button", { name: "Turn on MDM" }));
// // waiting for the modal to render
// await screen.findByText("To turn on MDM,");
// // manualenroll-specific copy
// expect(screen.getByText("Download your profile.")).toBeInTheDocument();
// // version-specific copy
// expect(screen.getByText("In the sidebar menu")).toBeInTheDocument();
// });
// it("shows the Sequoia-and-above body when the host is post-Sequoia", async () => {
// const host = createMockHost() as IHostDevice;
// host.platform = "darwin";
// host.os_version = "macOS 16.0";
// mockServer.use(
// customDeviceHandler({
// host,
// global_config: {
// mdm: { enabled_and_configured: false },
// features: { enable_software_inventory: true },
// },
// })
// );
// const render = createCustomRenderer({
// withBackendMock: true,
// });
// const { user } = render(
// <DeviceUserPage
// router={mockRouter}
// params={{ device_auth_token: "testToken" }}
// location={mockLocation}
// />
// );
// // waiting for the device data to render
// await screen.findByText("About");
// // open the modal
// await user.click(screen.getByRole("button", { name: "Turn on MDM" }));
// // waiting for the modal to render
// await screen.findByText("To turn on MDM,");
// // manual-specific copy
// expect(screen.getByText("Download your profile.")).toBeInTheDocument();
// // version-specific copy
// expect(screen.getByText("In the sidebar menu")).toBeInTheDocument();
// });
// });
});

View file

@ -469,6 +469,7 @@ const DeviceUserPage = ({
const showUsersCard =
host?.platform === "darwin" ||
host?.platform === "android" ||
generateChromeProfilesValues(host?.end_users ?? []).length > 0 ||
generateOtherEmailsValues(host?.end_users ?? []).length > 0;

View file

@ -964,6 +964,7 @@ const HostDetailsPage = ({
const showUsersCard =
isAppleDevice(host.platform) ||
isAndroidHost ||
generateChromeProfilesValues(host.end_users ?? []).length > 0 ||
generateOtherEmailsValues(host.end_users ?? []).length > 0;
const showActivityCard = !isAndroidHost;

View file

@ -0,0 +1,177 @@
import React from "react";
import { screen, render } from "@testing-library/react";
import { noop } from "lodash";
import { createMockHostEndUser } from "__mocks__/hostMock";
import User from ".";
describe("User card", () => {
it("renders the username field when the platform is Apple", () => {
const endUsers = [createMockHostEndUser()];
render(
<User
platform="darwin"
endUsers={endUsers}
enableAddEndUser={false}
onAddEndUser={noop}
/>
);
expect(screen.getByText("Username (IdP)")).toBeInTheDocument();
expect(screen.getByText("jdoe")).toBeInTheDocument();
});
it("renders the username field when the platform is android", () => {
const endUsers = [createMockHostEndUser()];
render(
<User
platform="android"
endUsers={endUsers}
enableAddEndUser={false}
onAddEndUser={noop}
/>
);
expect(screen.getByText("Username (IdP)")).toBeInTheDocument();
expect(screen.getByText("jdoe")).toBeInTheDocument();
});
it("renders the full name field when the platform is Apple and has full name values", () => {
const endUsers = [createMockHostEndUser()];
render(
<User
platform="darwin"
endUsers={endUsers}
enableAddEndUser={false}
onAddEndUser={noop}
/>
);
expect(screen.getByText("Full name (IdP)")).toBeInTheDocument();
expect(screen.getByText("John Doe")).toBeInTheDocument();
});
it("renders the full name field when the platform is Android and has full name values", () => {
const endUsers = [createMockHostEndUser()];
render(
<User
platform="darwin"
endUsers={endUsers}
enableAddEndUser={false}
onAddEndUser={noop}
/>
);
expect(screen.getByText("Full name (IdP)")).toBeInTheDocument();
expect(screen.getByText("John Doe")).toBeInTheDocument();
});
it("renders the groups field when the platform is Apple and has groups values", () => {
const endUsers = [createMockHostEndUser()];
render(
<User
platform="darwin"
endUsers={endUsers}
enableAddEndUser={false}
onAddEndUser={noop}
/>
);
expect(screen.getByText("Groups (IdP)")).toBeInTheDocument();
expect(screen.getByText("GroupA")).toBeInTheDocument();
expect(screen.getByText("+ 1 more")).toBeInTheDocument();
});
it("renders the groups field when the platform is Android and has groups values", () => {
const endUsers = [createMockHostEndUser()];
render(
<User
platform="darwin"
endUsers={endUsers}
enableAddEndUser={false}
onAddEndUser={noop}
/>
);
expect(screen.getByText("Groups (IdP)")).toBeInTheDocument();
expect(screen.getByText("GroupA")).toBeInTheDocument();
expect(screen.getByText("+ 1 more")).toBeInTheDocument();
});
it("renders the chrome profiles field when has chrome profile values", () => {
const endUsers = [
createMockHostEndUser({
other_emails: [
{ email: "Profile1", source: "google_chrome_profiles" },
{ email: "Profile2", source: "google_chrome_profiles" },
],
}),
];
render(
<User
platform="windows"
endUsers={endUsers}
enableAddEndUser={false}
onAddEndUser={noop}
/>
);
expect(screen.getByText("Google Chrome profiles")).toBeInTheDocument();
expect(screen.getByText("Profile1")).toBeInTheDocument();
expect(screen.getByText("+ 1 more")).toBeInTheDocument();
});
it("renders other emails field when has other email values", () => {
const endUsers = [
createMockHostEndUser({
other_emails: [
{ email: "other1@example.com", source: "custom" },
{ email: "other2@example.com", source: "custom" },
],
}),
];
render(
<User
platform="windows"
endUsers={endUsers}
enableAddEndUser={false}
onAddEndUser={noop}
/>
);
expect(screen.getByText("Other emails")).toBeInTheDocument();
expect(screen.getByText("other1@example.com")).toBeInTheDocument();
expect(screen.getByText("+ 1 more")).toBeInTheDocument();
});
it("renders the department field when the platform is Apple and it has department value", () => {
const endUsers = [createMockHostEndUser()];
render(
<User
platform="darwin"
endUsers={endUsers}
enableAddEndUser={false}
onAddEndUser={noop}
/>
);
expect(screen.getByText("Department (IdP)")).toBeInTheDocument();
expect(screen.getByText("Engineering")).toBeInTheDocument();
});
it("renders the department field when the platform is Android and it has department value", () => {
const endUsers = [createMockHostEndUser()];
render(
<User
platform="android"
endUsers={endUsers}
enableAddEndUser={false}
onAddEndUser={noop}
/>
);
expect(screen.getByText("Department (IdP)")).toBeInTheDocument();
expect(screen.getByText("Engineering")).toBeInTheDocument();
});
});

View file

@ -3,7 +3,7 @@ import classnames from "classnames";
import { noop } from "lodash";
import { IHostEndUser } from "interfaces/host";
import { HostPlatform, isAppleDevice } from "interfaces/platform";
import { HostPlatform, isAndroid, isAppleDevice } from "interfaces/platform";
import Card from "components/Card";
import CardHeader from "components/CardHeader";
@ -50,7 +50,7 @@ const User = ({
const otherEmailsDisplayValues = generateOtherEmailsValues(endUsers);
const endUser = endUsers[0];
const showUsername = isAppleDevice(platform);
const showUsername = isAppleDevice(platform) || isAndroid(platform);
const showFullName = showUsername && userNameDisplayValues.length > 0;
const showGroups = showUsername && userNameDisplayValues.length > 0;
const showChromeProfiles = chromeProfilesDisplayValues.length > 0;