Skip to content

Commit

Permalink
Fleet UI: Surface download URL for Fleet-maintained app when adding (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
RachelElysia authored Jan 27, 2025
1 parent fcf4f97 commit 9b70a2c
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 19 deletions.
1 change: 1 addition & 0 deletions changes/23116-fma-dl-url
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fleet UI: Surfaced download URL for Fleet-maintained app when adding the software to Fleet
1 change: 1 addition & 0 deletions frontend/__mocks__/softwareMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ const DEFAULT_FLEET_MAINTAINED_APP_DETAILS_MOCK: IFleetMaintainedAppDetails = {
post_install_script: 'echo "Installed"',
uninstall_script:
"#!/bin/sh\n\n# Fleet extracts and saves package IDs\npkg_ids=$PACKAGE_ID",
url: "http://www.testurl1234abcd.com/testapp",
};

export const createMockFleetMaintainedAppDetails = (
Expand Down
3 changes: 2 additions & 1 deletion frontend/components/DataSet/_styles.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.data-set {
font-size: $x-small;
min-width: max-content;
max-width: min-content;
overflow: hidden;

// ff only
@-moz-document url-prefix() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import React from "react";
import { render, screen } from "@testing-library/react";

import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants";

import { ScheduledQueryablePlatform } from "interfaces/platform";
import PlatformCell from "./PlatformCell";

Expand Down
1 change: 1 addition & 0 deletions frontend/interfaces/software.ts
Original file line number Diff line number Diff line change
Expand Up @@ -463,4 +463,5 @@ export interface IFleetMaintainedAppDetails {
install_script: string;
post_install_script: string; // TODO: is this needed?
uninstall_script: string;
url: string;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from "react";
import { noop } from "lodash";

import Modal from "components/Modal";
import Spinner from "components/Spinner";
import { noop } from "lodash";
import React from "react";

const baseClass = "add-fleet-app-software-modal";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from "react";
import { screen } from "@testing-library/react";
import { noop } from "lodash";
import { createCustomRenderer } from "test/test-utils";

import FleetAppDetailsModal from "./FleetAppDetailsModal";

describe("FleetAppDetailsModal", () => {
const defaultProps = {
name: "Test App",
platform: "macOS",
version: "1.0.0",
url: "https://example.com/app",
onCancel: noop,
};

it("renders modal with correct title", () => {
const render = createCustomRenderer();

render(<FleetAppDetailsModal {...defaultProps} />);

const modalTitle = screen.getByText("Software details");
expect(modalTitle).toBeInTheDocument();
});

it("displays correct app details", () => {
const render = createCustomRenderer();

render(<FleetAppDetailsModal {...defaultProps} />);

expect(screen.getByText("Name")).toBeInTheDocument();
expect(screen.getByText("Test App")).toBeInTheDocument();
expect(screen.getByText("Platform")).toBeInTheDocument();
expect(screen.getByText("macOS")).toBeInTheDocument();
expect(screen.getByText("Version")).toBeInTheDocument();
expect(screen.getByText("1.0.0")).toBeInTheDocument();
expect(screen.getByText("URL")).toBeInTheDocument();
expect(
screen.getAllByText("https://example.com/app").length
).toBeGreaterThan(0); // Tooltip renders text twice causing use of toBeInTheDocument to fail
});

it("does not render URL field when url prop is not provided", () => {
const render = createCustomRenderer();
const propsWithoutUrl = { ...defaultProps, url: undefined };

render(<FleetAppDetailsModal {...propsWithoutUrl} />);

expect(screen.queryByText("URL")).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from "react";

import Modal from "components/Modal";
import DataSet from "components/DataSet";
import TooltipWrapper from "components/TooltipWrapper";
import TooltipTruncatedText from "components/TooltipTruncatedText";
import Button from "components/buttons/Button";

const baseClass = "fleet-app-details-modal";

interface IFleetAppDetailsModalProps {
name: string;
platform: string;
version: string;
url?: string;
onCancel: () => void;
}

const TOOLTIP_MESSAGE =
"Fleet downloads the package from the URL and stores it. Hosts download it from Fleet before install.";

const FleetAppDetailsModal = ({
name,
platform,
version,
url,
onCancel,
}: IFleetAppDetailsModalProps) => {
return (
<Modal className={baseClass} title="Software details" onExit={onCancel}>
<>
<div className={`${baseClass}__modal-content`}>
<DataSet title="Name" value={name} />
<DataSet title="Platform" value={platform} />
<DataSet title="Version" value={version} />
{url && (
<DataSet
title={
<TooltipWrapper tipContent={TOOLTIP_MESSAGE}>
URL
</TooltipWrapper>
}
value={<TooltipTruncatedText value={url} />}
/>
)}
</div>
<div className="modal-cta-wrap">
<Button onClick={onCancel} variant="brand">
Done
</Button>
</div>
</>
</Modal>
);
};

export default FleetAppDetailsModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.fleet-app-details-modal {
&__modal-content {
display: flex;
column-gap: $pad-xxlarge;
row-gap: $pad-xlarge;
flex-wrap: wrap;
}

.react-tooltip {
min-width: 120px;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./FleetAppDetailsModal";
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ import SidePanelContent from "components/SidePanelContent";
import QuerySidePanel from "components/side_panels/QuerySidePanel";
import PremiumFeatureMessage from "components/PremiumFeatureMessage";
import Card from "components/Card";

import SoftwareIcon from "pages/SoftwarePage/components/icons/SoftwareIcon";

import Button from "components/buttons/Button";
import Icon from "components/Icon";
import FleetAppDetailsForm from "./FleetAppDetailsForm";
import { IFleetMaintainedAppFormData } from "./FleetAppDetailsForm/FleetAppDetailsForm";

import AddFleetAppSoftwareModal from "./AddFleetAppSoftwareModal";
import FleetAppDetailsModal from "./FleetAppDetailsModal";

import {
getErrorMessage,
Expand All @@ -48,35 +50,49 @@ const AUTOMATIC_POLICY_ERROR_MESSAGE =

const baseClass = "fleet-maintained-app-details-page";

interface ISoftwareSummaryProps {
interface IFleetAppSummaryProps {
name: string;
platform: string;
version: string;
onClickShowAppDetails: (event: MouseEvent) => void;
}

const FleetAppSummary = ({
name,
platform,
version,
}: ISoftwareSummaryProps) => {
onClickShowAppDetails,
}: IFleetAppSummaryProps) => {
return (
<Card
className={`${baseClass}__fleet-app-summary`}
borderRadiusSize="medium"
color="gray"
>
<SoftwareIcon name={name} size="medium" />
<div className={`${baseClass}__fleet-app-summary--details`}>
<div className={`${baseClass}__fleet-app-summary--title`}>{name}</div>
<div className={`${baseClass}__fleet-app-summary--info`}>
<div className={`${baseClass}__fleet-app-summary--details--platform`}>
{PLATFORM_DISPLAY_NAMES[platform as Platform]}
</div>
&bull;
<div className={`${baseClass}__fleet-app-summary--details--version`}>
{version}
<div className={`${baseClass}__fleet-app-summary--left`}>
<SoftwareIcon name={name} size="medium" />
<div className={`${baseClass}__fleet-app-summary--details`}>
<div className={`${baseClass}__fleet-app-summary--title`}>{name}</div>
<div className={`${baseClass}__fleet-app-summary--info`}>
<div
className={`${baseClass}__fleet-app-summary--details--platform`}
>
{PLATFORM_DISPLAY_NAMES[platform as Platform]}
</div>
&bull;
<div
className={`${baseClass}__fleet-app-summary--details--version`}
>
{version}
</div>
</div>
</div>
</div>
<div className={`${baseClass}__fleet-app-summary--show-details`}>
<Button variant="text-icon" onClick={onClickShowAppDetails}>
<Icon name="info" /> Show details
</Button>
</div>
</Card>
);
};
Expand Down Expand Up @@ -123,6 +139,7 @@ const FleetMaintainedAppDetailsPage = ({
showAddFleetAppSoftwareModal,
setShowAddFleetAppSoftwareModal,
] = useState(false);
const [showAppDetailsModal, setShowAppDetailsModal] = useState(false);

const {
data: fleetApp,
Expand Down Expand Up @@ -159,6 +176,10 @@ const FleetMaintainedAppDetailsPage = ({
setSelectedOsqueryTable(tableName);
};

const onClickShowAppDetails = () => {
setShowAppDetailsModal(true);
};

const backToAddSoftwareUrl = `${
PATHS.SOFTWARE_ADD_FLEET_MAINTAINED
}?${buildQueryStringFromParams({ team_id: teamId })}`;
Expand Down Expand Up @@ -288,6 +309,7 @@ const FleetMaintainedAppDetailsPage = ({
name={fleetApp.name}
platform={fleetApp.platform}
version={fleetApp.version}
onClickShowAppDetails={onClickShowAppDetails}
/>
<FleetAppDetailsForm
labels={labels || []}
Expand Down Expand Up @@ -324,6 +346,15 @@ const FleetMaintainedAppDetailsPage = ({
</SidePanelContent>
)}
{showAddFleetAppSoftwareModal && <AddFleetAppSoftwareModal />}
{showAppDetailsModal && fleetApp && (
<FleetAppDetailsModal
name={fleetApp.name}
platform={fleetApp.platform}
version={fleetApp.version}
url={fleetApp.url}
onCancel={() => setShowAppDetailsModal(false)}
/>
)}
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
}

&__fleet-app-summary {
display: flex;
justify-content: space-between;
}

&__fleet-app-summary--left {
display: flex;
gap: $pad-medium;
}
Expand Down

0 comments on commit 9b70a2c

Please sign in to comment.