Fleet UI: Return pre-install query output in Install Details modal (#35754)

This commit is contained in:
RachelElysia 2025-11-14 11:35:03 -05:00 committed by GitHub
parent daae2c1c06
commit 4939979f55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 149 additions and 29 deletions

View file

@ -0,0 +1 @@
* Fleet UI: Return pre-install query output in Install Details modal

View file

@ -7,6 +7,8 @@ import {
getDefaultSoftwareInstallHandler,
getSoftwareInstallHandlerNoOutputs,
getSoftwareInstallHandlerOnlyInstallOutput,
getSoftwareInstallHandlerWithPreInstall,
getSoftwareInstallHandlerOnlyPreInstallOutput,
} from "test/handlers/software-handlers";
import mockServer from "test/mock-server";
import { noop } from "lodash";
@ -216,14 +218,78 @@ describe("SoftwareInstallDetailsModal", () => {
expect(detailsButton).toBeInTheDocument();
expect(
screen.queryByLabelText("Install script output:")
screen.queryByText("Pre-install query output:")
).not.toBeInTheDocument();
expect(
screen.queryByLabelText(/Post-install script output:/i)
screen.queryByText("Install script output:")
).not.toBeInTheDocument();
expect(
screen.queryByText(/Post-install script output:/i)
).not.toBeInTheDocument();
});
it("shows install and post-install outputs after clicking Details", async () => {
it("shows pre-install, install, and post-install outputs after clicking Details", async () => {
mockServer.use(getSoftwareInstallHandlerWithPreInstall);
const renderWithServer = createCustomRenderer({ withBackendMock: true });
const { user } = renderWithServer(
<SoftwareInstallDetailsModal
details={baseDetails}
hostSoftware={baseHostSoftware}
onCancel={noop}
/>
);
const detailsBtn = await screen.findByRole("button", {
name: /Details/i,
});
await user.click(detailsBtn);
// Pre-install output
expect(
await screen.getByText("Pre-install query output:")
).toBeInTheDocument();
expect(screen.getByText("Pre-install check passed")).toBeInTheDocument();
// Install output
expect(screen.getByText("Install script output:")).toBeInTheDocument();
expect(screen.getByText("Install script ran")).toBeInTheDocument();
// Post-install output
expect(
screen.getByText("Post-install script output:")
).toBeInTheDocument();
expect(screen.getByText("Post-install success")).toBeInTheDocument();
});
it("renders only pre-install output if that's the only script output present", async () => {
mockServer.use(getSoftwareInstallHandlerOnlyPreInstallOutput);
const renderWithServer = createCustomRenderer({ withBackendMock: true });
const { user } = renderWithServer(
<SoftwareInstallDetailsModal
details={baseDetails}
hostSoftware={baseHostSoftware}
onCancel={noop}
/>
);
const detailsBtn = await screen.findByRole("button", {
name: /Details/i,
});
await user.click(detailsBtn);
expect(
await screen.getByText("Pre-install query output:")
).toBeInTheDocument();
expect(screen.getByText(/pre-install only/i)).toBeInTheDocument();
expect(
screen.queryByText("Install script output:")
).not.toBeInTheDocument();
expect(
screen.queryByText(/Post-install script output:/i)
).not.toBeInTheDocument();
});
it("shows install and post-install outputs after clicking Details (no pre-install)", async () => {
mockServer.use(getDefaultSoftwareInstallHandler);
const renderWithServer = createCustomRenderer({ withBackendMock: true });
const { user } = renderWithServer(
@ -239,15 +305,48 @@ describe("SoftwareInstallDetailsModal", () => {
});
await user.click(detailsBtn);
await screen.findByText("Install script output:");
expect(
await screen.getByText("Install script output:")
).toBeInTheDocument();
expect(screen.getByText("Install script ran")).toBeInTheDocument();
expect(
screen.getByText(/Post-install script output:/i)
).toBeInTheDocument();
expect(screen.getByText("Post-install success")).toBeInTheDocument();
expect(
screen.queryByText("Pre-install query output:")
).not.toBeInTheDocument();
});
it("does not render details button if details (script outputs) are empty", async () => {
it("shows only the install output if post-install and pre-install output is empty", async () => {
mockServer.use(getSoftwareInstallHandlerOnlyInstallOutput);
const renderWithServer = createCustomRenderer({ withBackendMock: true });
const { user } = renderWithServer(
<SoftwareInstallDetailsModal
details={baseDetails}
hostSoftware={baseHostSoftware}
onCancel={noop}
/>
);
const detailsBtn = await screen.findByRole("button", {
name: /Details/i,
});
await user.click(detailsBtn);
expect(
await screen.getByText("Install script output:")
).toBeInTheDocument();
expect(screen.getByText(/install only/i)).toBeInTheDocument();
expect(
screen.queryByText("Pre-install query output:")
).not.toBeInTheDocument();
expect(
screen.queryByText(/Post-install script output:/i)
).not.toBeInTheDocument();
});
it("does not render details button if all script outputs are empty", async () => {
mockServer.use(getSoftwareInstallHandlerNoOutputs);
const renderWithServer = createCustomRenderer({ withBackendMock: true });
renderWithServer(
@ -264,28 +363,5 @@ describe("SoftwareInstallDetailsModal", () => {
})
).not.toBeInTheDocument();
});
it("shows only the install output if post-install output is empty", async () => {
mockServer.use(getSoftwareInstallHandlerOnlyInstallOutput);
const renderWithServer = createCustomRenderer({ withBackendMock: true });
const { user } = renderWithServer(
<SoftwareInstallDetailsModal
details={baseDetails}
hostSoftware={baseHostSoftware}
onCancel={noop}
/>
);
const detailsBtn = await screen.findByRole("button", {
name: /Details/i,
});
await user.click(detailsBtn);
await screen.findByText("Install script output:");
expect(screen.getByText(/install only/i)).toBeInTheDocument();
expect(
screen.queryByText(/Post-install script output:/i)
).not.toBeInTheDocument();
});
});
});

View file

@ -260,6 +260,10 @@ export const SoftwareInstallDetailsModal = ({
const renderInstallDetailsSection = () => {
const outputs = [
{
label: "Pre-install query output:",
value: swInstallResult?.pre_install_query_output,
},
{
label: "Install script output:",
value: swInstallResult?.output,
@ -273,7 +277,8 @@ export const SoftwareInstallDetailsModal = ({
// Only show details button if there's details to display
const showDetailsButton =
(!!swInstallResult?.post_install_script_output ||
!!swInstallResult?.output) &&
!!swInstallResult?.output ||
!!swInstallResult?.pre_install_query_output) &&
swInstallResult?.status !== "pending_install";
return (

View file

@ -64,6 +64,44 @@ export const getSoftwareInstallResultHandler = http.get(
}
);
// ---- Pre install query output ----
// Installed, outputs for pre-install, install, and post-install
export const getSoftwareInstallHandlerWithPreInstall = http.get(
baseUrl("/software/install/:install_uuid/results"),
({ params }) => {
return HttpResponse.json({
results: {
...createMockSoftwareInstallResult({
install_uuid: params.install_uuid as string,
status: "installed",
output: "Install script ran",
post_install_script_output: "Post-install success",
pre_install_query_output: "Pre-install check passed",
}),
},
});
}
);
// Failed install, only pre-install output
export const getSoftwareInstallHandlerOnlyPreInstallOutput = http.get(
baseUrl("/software/install/:install_uuid/results"),
({ params }) => {
return HttpResponse.json({
results: {
...createMockSoftwareInstallResult({
install_uuid: params.install_uuid as string,
status: "failed_install",
output: "",
post_install_script_output: "",
pre_install_query_output: "Pre-install only",
}),
},
});
}
);
// ---- MDM Command Handlers ----
/** This is used for testing command results of IPA custom packages */