diff --git a/changes/issue-32356-update-ui-android-user-card b/changes/issue-32356-update-ui-android-user-card new file mode 100644 index 0000000000..f768d2170b --- /dev/null +++ b/changes/issue-32356-update-ui-android-user-card @@ -0,0 +1 @@ +- update host details and my device UI to show the users card for android hosts diff --git a/frontend/__mocks__/hostMock.ts b/frontend/__mocks__/hostMock.ts index c4d512857f..97cc5d0956 100644 --- a/frontend/__mocks__/hostMock.ts +++ b/frontend/__mocks__/hostMock.ts @@ -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 => { + return { ...DEFAULT_HOST_END_USER_MOCK, ...overrides }; +}; + export default createMockHost; diff --git a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tests.tsx b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tests.tsx index ce10f85107..227498307d 100644 --- a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tests.tsx +++ b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tests.tsx @@ -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( + + ); + + // 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( - // - // ); - - // // 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( - // - // ); - - // // 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( - // - // ); - - // // 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( - // - // ); - - // // 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( - // - // ); - - // // 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( - // - // ); - - // // 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(); - // }); - // }); }); diff --git a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx index c9decf9f65..c6bd13ba79 100644 --- a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx +++ b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx @@ -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; diff --git a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx index 298c2915b3..629f52ad64 100644 --- a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx +++ b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx @@ -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; diff --git a/frontend/pages/hosts/details/cards/User/User.tests.tsx b/frontend/pages/hosts/details/cards/User/User.tests.tsx new file mode 100644 index 0000000000..b82e986544 --- /dev/null +++ b/frontend/pages/hosts/details/cards/User/User.tests.tsx @@ -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( + + ); + + 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( + + ); + + 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( + + ); + + 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( + + ); + + 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( + + ); + + 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( + + ); + + 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( + + ); + + 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( + + ); + + 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( + + ); + + 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( + + ); + + expect(screen.getByText("Department (IdP)")).toBeInTheDocument(); + expect(screen.getByText("Engineering")).toBeInTheDocument(); + }); +}); diff --git a/frontend/pages/hosts/details/cards/User/User.tsx b/frontend/pages/hosts/details/cards/User/User.tsx index bbd4478a60..0c40f0935d 100644 --- a/frontend/pages/hosts/details/cards/User/User.tsx +++ b/frontend/pages/hosts/details/cards/User/User.tsx @@ -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;