Fix unreleased issues in software installers UI: Part 3 (#18972)

This commit is contained in:
Sarah Gillespie 2024-05-13 17:32:59 -05:00 committed by GitHub
parent 180a32454a
commit 0debd18673
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 81 additions and 21 deletions

View file

@ -1,9 +1,16 @@
import React, { useCallback, useContext, useState } from "react";
import endpoints from "utilities/endpoints";
import { SoftwareInstallStatus, ISoftwarePackage } from "interfaces/software";
import FileSaver from "file-saver";
import PATHS from "router/paths";
import { AppContext } from "context/app";
import { NotificationContext } from "context/notification";
import { SoftwareInstallStatus, ISoftwarePackage } from "interfaces/software";
import softwareAPI from "services/entities/software";
import { buildQueryStringFromParams } from "utilities/url";
import { internationalTimeFormat } from "utilities/helpers";
import { uploadedFromNow } from "utilities/date_format";
@ -109,6 +116,8 @@ const SoftwarePackageCard = ({
isTeamMaintainer,
} = useContext(AppContext);
const { renderFlash } = useContext(NotificationContext);
const [showAdvancedOptionsModal, setShowAdvancedOptionsModal] = useState(
false
);
@ -122,18 +131,45 @@ const SoftwarePackageCard = ({
setShowDeleteModal(true);
};
const onSuccess = useCallback(() => {
const onDeleteSuccess = useCallback(() => {
setShowDeleteModal(false);
onDelete();
}, [onDelete]);
const onDownloadClick = useCallback(async () => {
try {
const resp = await softwareAPI.downloadSoftwarePackage(
softwareId,
teamId
);
const contentLength = parseInt(resp.headers["content-length"], 10);
if (contentLength !== resp.data.size) {
throw new Error(
`Byte size (${resp.data.size}) does not match content-length header (${contentLength})`
);
}
const filename = softwarePackage.name;
const file = new File([resp.data], filename, {
type: "application/octet-stream",
});
if (file.size === 0) {
throw new Error("Downloaded file is empty");
}
if (file.size !== resp.data.size) {
throw new Error(
`File size (${file.size}) does not match expected size (${resp.data.size})`
);
}
FileSaver.saveAs(file);
} catch (e) {
console.log(e);
renderFlash("error", "Couldnt download. Please try again.");
}
}, [renderFlash, softwareId, softwarePackage.name, teamId]);
const showActions =
isGlobalAdmin || isGlobalMaintainer || isTeamAdmin || isTeamMaintainer;
const downloadUrl = `/api${endpoints.SOFTWARE_PACKAGE(
softwareId
)}?${buildQueryStringFromParams({ alt: "media", team_id: teamId })}`;
return (
<Card borderRadiusSize="large" includeShadow className={baseClass}>
<div className={`${baseClass}__main-content`}>
@ -185,13 +221,9 @@ const SoftwarePackageCard = ({
<Icon name="settings" color={"ui-fleet-black-75"} />
</Button>
{/* TODO: make a component for download icons */}
<a
className={`${baseClass}__download-icon`}
href={downloadUrl}
download
>
<Icon name="download" />
</a>
<Button variant="icon" onClick={onDownloadClick}>
<Icon name="download" color={"ui-fleet-black-75"} />
</Button>
<Button variant="icon" onClick={onDeleteClick}>
<Icon name="trash" color={"ui-fleet-black-75"} />
</Button>
@ -210,7 +242,7 @@ const SoftwarePackageCard = ({
softwareId={softwareId}
teamId={teamId}
onExit={() => setShowDeleteModal(false)}
onSuccess={onSuccess}
onSuccess={onDeleteSuccess}
/>
)}
</Card>

View file

@ -70,7 +70,6 @@ const AddSoftwareForm = ({
onCancel,
onSubmit,
}: IAddSoftwareFormProps) => {
console.log("rerender");
const [showPreInstallCondition, setShowPreInstallCondition] = useState(false);
const [showPostInstallScript, setShowPostInstallScript] = useState(false);
const [formData, setFormData] = useState<IAddSoftwareFormData>({

View file

@ -17,6 +17,7 @@ import Word from "./Word";
import Zoom from "./Zoom";
import ChromeOS from "./ChromeOS";
import LinuxOS from "./LinuxOS";
// import Falcon from "./Falcon"; // TODO: Add Falcon icon svg
// SOFTWARE_NAME_TO_ICON_MAP list "special" applications that have a defined
// icon for them, keys refer to application names, and are intended to be fuzzy
@ -33,6 +34,7 @@ export const SOFTWARE_NAME_TO_ICON_MAP = {
"visual studio code": VisualStudioCode,
"microsoft word": Word,
zoom: Zoom,
// falcon: Falcon,
darwin: MacOS,
windows: WindowsOS,
chrome: ChromeOS,

View file

@ -1,10 +1,12 @@
.software-card {
.table-container__search-input {
width: 325px; // Custom to fit placeholder text
}
.data-table-block .data-table__wrapper {
overflow-x: scroll;
}
// // TODO: Addingoverflow-x: scroll to the table clips the actions dropdown at the bottom of the
// // table. Find a solution that allows dropdown menu to be displayed over the bottom of the table
// // in the y-axis when the table is scrollable in the x-axis.
// .data-table-block .data-table__wrapper {
// overflow-x: scroll;
// }
}

View file

@ -1,3 +1,5 @@
import { AxiosResponse } from "axios";
import { snakeCase, reduce } from "lodash";
import sendRequest from "services";
@ -211,6 +213,25 @@ export default {
return sendRequest("DELETE", path);
},
downloadSoftwarePackage: (
softwareTitleId: number,
teamId: number
): Promise<AxiosResponse> => {
const path = `${endpoints.SOFTWARE_PACKAGE(
softwareTitleId
)}?${buildQueryStringFromParams({ alt: "media", team_id: teamId })}`;
return sendRequest(
"GET",
path,
undefined,
"blob",
undefined,
undefined,
true // return raw response
);
},
getSoftwareInstallResult: (installUuid: string) => {
const { SOFTWARE_INSTALL_RESULTS } = endpoints;
const path = SOFTWARE_INSTALL_RESULTS(installUuid);

View file

@ -8,7 +8,8 @@ export const sendRequest = async (
data?: unknown,
responseType: AxiosResponseType = "json",
timeout?: number,
skipParseError?: boolean
skipParseError?: boolean,
returnRaw?: boolean
) => {
const { origin } = global.window.location;
@ -27,6 +28,9 @@ export const sendRequest = async (
},
});
if (returnRaw) {
return response;
}
return response.data;
} catch (error) {
if (skipParseError) {