From 29e937ffbfc3abe915affb8d3c2ead5243909c97 Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Thu, 22 May 2025 15:59:15 -0400 Subject: [PATCH] FE: Followup gitops YAML code nits (#29383) --- .../EditSoftwareModal/EditSoftwareModal.tsx | 14 ++- .../ViewYamlModal/ViewYamlModal.tsx | 97 +++++++++++-------- .../ViewYamlModal/helpers.tests.ts | 48 +-------- .../ViewYamlModal/helpers.tsx | 50 +++++----- .../utilities/strings/stringUtils.tests.ts | 51 ++++++++++ frontend/utilities/strings/stringUtils.ts | 9 ++ 6 files changed, 149 insertions(+), 120 deletions(-) diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/EditSoftwareModal.tsx b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/EditSoftwareModal.tsx index 844b0689f8..7139a3192f 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/EditSoftwareModal.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/EditSoftwareModal.tsx @@ -5,7 +5,11 @@ import paths from "router/paths"; import classnames from "classnames"; import { ILabelSummary } from "interfaces/label"; -import { IAppStoreApp, ISoftwarePackage } from "interfaces/software"; +import { + IAppStoreApp, + ISoftwarePackage, + isSoftwarePackage, +} from "interfaces/software"; import mdmAppleAPI from "services/entities/mdm_apple"; import { NotificationContext } from "context/notification"; @@ -187,14 +191,16 @@ const EditSoftwareModal = ({ }, }); - if ("title_id" in software && software.title_id && gitOpsModeEnabled) { + if ( + isSoftwarePackage(software) && + software.title_id && + gitOpsModeEnabled + ) { // No longer refetch as we open YAML modal if editing with gitOpsModeEnabled const newQueryParams: QueryParams = { team_id: teamId, gitops_yaml: "true", }; - // Should have title_id so can return to the same page - // TODO: Make it so YAML reopens! It's pushing to the same URL so it might need a refresh. router.push( getPathWithQueryParams( paths.SOFTWARE_TITLE_DETAILS(software.title_id.toString()), diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/ViewYamlModal/ViewYamlModal.tsx b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/ViewYamlModal/ViewYamlModal.tsx index 4cf85c9dcd..6a357fc34a 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/ViewYamlModal/ViewYamlModal.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/ViewYamlModal/ViewYamlModal.tsx @@ -15,11 +15,8 @@ import CustomLink from "components/CustomLink"; import InputField from "components/forms/fields/InputField"; import Editor from "components/Editor"; -import { - createPackageYaml, - hyphenatedSoftwareTitle, - renderYamlHelperText, -} from "./helpers"; +import { hyphenateString } from "utilities/strings/stringUtils"; +import { createPackageYaml, renderYamlHelperText } from "./helpers"; const baseClass = "view-yaml-modal"; @@ -29,6 +26,14 @@ interface IViewYamlModalProps { onExit: () => void; } +interface HandleDownloadParams { + evt: React.MouseEvent; + content?: string; + filename: string; + filetype: string; + errorMsg: string; +} + const ViewYamlModal = ({ softwareTitleName, softwarePackage, @@ -61,13 +66,13 @@ const ViewYamlModal = ({ }); // Generic download handler - const handleDownload = ( - evt: React.MouseEvent, - content: string | undefined, - filename: string, - filetype: string, - errorMsg: string - ) => { + const handleDownload = ({ + evt, + content, + filename, + filetype, + errorMsg, + }: HandleDownloadParams) => { evt.preventDefault(); if (content) { @@ -79,43 +84,51 @@ const ViewYamlModal = ({ return false; }; - const hyphenatedTitle = hyphenatedSoftwareTitle(softwareTitleName); + const hyphenatedSoftwareTitle = hyphenateString(softwareTitleName); - const onDownloadPreInstallQuery = (evt: React.MouseEvent) => - handleDownload( + const onDownloadPreInstallQuery = (evt: React.MouseEvent) => { + handleDownload({ evt, - preInstallQuery, - `pre-install-query-${hyphenatedTitle}.sh`, - "text/yml", - "Your pre-install query could not be downloaded. Please create YAML file (.yml) manually." - ); + content: preInstallQuery, + filename: `pre-install-query-${hyphenatedSoftwareTitle}.sh`, + filetype: "text/yml", + errorMsg: + "Your pre-install query could not be downloaded. Please create YAML file (.yml) manually.", + }); + }; - const onDownloadPostInstallScript = (evt: React.MouseEvent) => - handleDownload( + const onDownloadPostInstallScript = (evt: React.MouseEvent) => { + handleDownload({ evt, - postInstallScript, - `post-install-script-${hyphenatedTitle}.sh`, - "text/sh", - "Your post-install script could not be downloaded. Please create script file (.sh) manually." - ); + content: postInstallScript, + filename: `post-install-script-${hyphenatedSoftwareTitle}.sh`, + filetype: "text/sh", + errorMsg: + "Your post-install script could not be downloaded. Please create script file (.sh) manually.", + }); + }; - const onDownloadInstallScript = (evt: React.MouseEvent) => - handleDownload( + const onDownloadInstallScript = (evt: React.MouseEvent) => { + handleDownload({ evt, - installScript, - `install-script-${hyphenatedTitle}.sh`, - "text/sh", - "Your install script could not be downloaded. Please create script file (.sh) manually." - ); + content: installScript, + filename: `install-script-${hyphenatedSoftwareTitle}.sh`, + filetype: "text/sh", + errorMsg: + "Your install script could not be downloaded. Please create script file (.sh) manually.", + }); + }; - const onDownloadUninstallScript = (evt: React.MouseEvent) => - handleDownload( + const onDownloadUninstallScript = (evt: React.MouseEvent) => { + handleDownload({ evt, - uninstallScript, - `uninstall-script-${hyphenatedTitle}.sh`, - "text/sh", - "Your uninstall script could not be downloaded. Please create script file (.sh) manually." - ); + content: uninstallScript, + filename: `uninstall-script-${hyphenatedSoftwareTitle}.sh`, + filetype: "text/sh", + errorMsg: + "Your uninstall script could not be downloaded. Please create script file (.sh) manually.", + }); + }; return ( @@ -147,7 +160,7 @@ const ViewYamlModal = ({ inputWrapperClass name="filename" label="Filename" - value={`${hyphenatedSoftwareTitle(softwareTitleName)}.yml`} + value={`${hyphenatedSoftwareTitle}.yml`} /> { - it("converts spaces to hyphens and lowercases", () => { - expect(hyphenatedSoftwareTitle("My Cool App")).toBe("my-cool-app"); - }); - - it("trims leading and trailing spaces", () => { - expect(hyphenatedSoftwareTitle(" Leading and trailing ")).toBe( - "leading-and-trailing" - ); - }); - - it("collapses multiple spaces into one hyphen", () => { - expect(hyphenatedSoftwareTitle("Multiple spaces here")).toBe( - "multiple-spaces-here" - ); - }); - - it("returns empty string for empty input", () => { - expect(hyphenatedSoftwareTitle("")).toBe(""); - }); - - it("handles already hyphenated and lowercase input", () => { - expect(hyphenatedSoftwareTitle("already-hyphenated-title")).toBe( - "already-hyphenated-title" - ); - }); - - it("handles single word", () => { - expect(hyphenatedSoftwareTitle("Word")).toBe("word"); - }); - - it("handles all uppercase", () => { - expect(hyphenatedSoftwareTitle("ALL UPPERCASE")).toBe("all-uppercase"); - }); - - it("handles mixed case and spaces", () => { - expect(hyphenatedSoftwareTitle(" MixED CaSe App ")).toBe( - "mixed-case-app" - ); - }); -}); +import { createPackageYaml, renderYamlHelperText } from "./helpers"; describe("createPackageYaml", () => { const { diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/ViewYamlModal/helpers.tsx b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/ViewYamlModal/helpers.tsx index 2dc21774e4..f58a5118c9 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/ViewYamlModal/helpers.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/ViewYamlModal/helpers.tsx @@ -1,6 +1,7 @@ import React, { MouseEvent } from "react"; import Button from "components/buttons/Button"; +import { hyphenateString } from "utilities/strings/stringUtils"; interface RenderYamlHelperText { installScript?: string; @@ -13,6 +14,27 @@ interface RenderYamlHelperText { onClickUninstallScript?: (evt: MouseEvent) => void; } +// Helper to join items with commas and Oxford comma before "and" +const joinWithCommasAnd = ( + elements: { key: string; element: JSX.Element }[] +) => { + return elements.map((item, idx) => { + if (idx === 0) return item.element; + if (idx === elements.length - 1) { + return ( + + {elements.length > 2 ? "," : ""} and {item.element} + + ); + } + return ( + + , {item.element} + + ); + }); +}; + export const renderYamlHelperText = ({ preInstallQuery, installScript, @@ -80,27 +102,6 @@ export const renderYamlHelperText = ({ if (items.length === 0) return <>; - // Helper to join items with commas and Oxford comma before "and" - const joinWithCommasAnd = ( - elements: { key: string; element: JSX.Element }[] - ) => { - return elements.map((item, idx) => { - if (idx === 0) return item.element; - if (idx === elements.length - 1) { - return ( - - {elements.length > 2 ? "," : ""} and {item.element} - - ); - } - return ( - - , {item.element} - - ); - }); - }; - return ( <> Next, download your {joinWithCommasAnd(items)} and add{" "} @@ -110,11 +111,6 @@ export const renderYamlHelperText = ({ ); }; -/** Hyphenate the name for file paths */ -export const hyphenatedSoftwareTitle = (softwareTitle: string): string => { - return softwareTitle.trim().toLowerCase().replace(/\s+/g, "-"); -}; - interface CreatePackageYamlParams { softwareTitle: string; packageName: string; @@ -151,7 +147,7 @@ export const createPackageYaml = ({ `; } - const hyphenatedSWTitle = hyphenatedSoftwareTitle(softwareTitle); + const hyphenatedSWTitle = hyphenateString(softwareTitle); if (preInstallQuery) { yaml += `pre_install_query: diff --git a/frontend/utilities/strings/stringUtils.tests.ts b/frontend/utilities/strings/stringUtils.tests.ts index 4ebc69c632..93b5ef4f66 100644 --- a/frontend/utilities/strings/stringUtils.tests.ts +++ b/frontend/utilities/strings/stringUtils.tests.ts @@ -4,6 +4,7 @@ import { strToBool, stripQuotes, isIncompleteQuoteQuery, + hyphenateString, } from "./stringUtils"; describe("string utilities", () => { @@ -91,4 +92,54 @@ describe("string utilities", () => { expect(isIncompleteQuoteQuery("")).toBe(false); }); }); + + describe("hyphenatedTitle", () => { + it("converts spaces to hyphens and lowercases", () => { + expect(hyphenateString("My Cool App")).toBe("my-cool-app"); + }); + + it("trims leading and trailing spaces", () => { + expect(hyphenateString(" Leading and trailing ")).toBe( + "leading-and-trailing" + ); + }); + + it("collapses multiple spaces into one hyphen", () => { + expect(hyphenateString("Multiple spaces here")).toBe( + "multiple-spaces-here" + ); + }); + + it("returns empty string for empty input", () => { + expect(hyphenateString("")).toBe(""); + }); + + it("handles already hyphenated and lowercase input", () => { + expect(hyphenateString("already-hyphenated-title")).toBe( + "already-hyphenated-title" + ); + }); + + it("handles single word", () => { + expect(hyphenateString("Word")).toBe("word"); + }); + + it("handles all uppercase", () => { + expect(hyphenateString("ALL UPPERCASE")).toBe("all-uppercase"); + }); + + it("handles mixed case and spaces", () => { + expect(hyphenateString(" MixED CaSe App ")).toBe("mixed-case-app"); + }); + + it("handles numbers separated by spaces", () => { + expect(hyphenateString("Numbered App 3")).toBe("numbered-app-3"); + }); + + it("handles numbers attached to words", () => { + expect(hyphenateString("Attached Numbered App3")).toBe( + "attached-numbered-app3" + ); + }); + }); }); diff --git a/frontend/utilities/strings/stringUtils.ts b/frontend/utilities/strings/stringUtils.ts index 7a679b933b..fc6c1ad541 100644 --- a/frontend/utilities/strings/stringUtils.ts +++ b/frontend/utilities/strings/stringUtils.ts @@ -94,6 +94,15 @@ export const isIncompleteQuoteQuery = (str: string) => { return pattern.test(str); }; +/** + * Hyphenates the words of the string passed in. + * e.g. The name of an app to be used in a file name + * @param str un-capitalized string + */ +export const hyphenateString = (str: string): string => { + return str.trim().toLowerCase().replace(/\s+/g, "-"); +}; + export default { capitalize, capitalizeRole,