FE: Followup gitops YAML code nits (#29383)

This commit is contained in:
RachelElysia
2025-05-22 15:59:15 -04:00
committed by GitHub
parent 0b6ee9392f
commit 29e937ffbf
6 changed files with 149 additions and 120 deletions
@@ -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()),
@@ -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 (
<Modal className={baseClass} title="YAML" onExit={onExit}>
@@ -147,7 +160,7 @@ const ViewYamlModal = ({
inputWrapperClass
name="filename"
label="Filename"
value={`${hyphenatedSoftwareTitle(softwareTitleName)}.yml`}
value={`${hyphenatedSoftwareTitle}.yml`}
/>
<Editor
label="Contents"
@@ -2,53 +2,7 @@ import { render, screen } from "@testing-library/react";
import { createMockSoftwarePackage } from "__mocks__/softwareMock";
import { noop } from "lodash";
import {
hyphenatedSoftwareTitle,
createPackageYaml,
renderYamlHelperText,
} from "./helpers";
describe("hyphenatedSoftwareTitle", () => {
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 {
@@ -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 (
<React.Fragment key={`and-${item.key}`}>
{elements.length > 2 ? "," : ""} and {item.element}
</React.Fragment>
);
}
return (
<React.Fragment key={`comma-${item.key}`}>
, {item.element}
</React.Fragment>
);
});
};
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 (
<React.Fragment key={`and-${item.key}`}>
{elements.length > 2 ? "," : ""} and {item.element}
</React.Fragment>
);
}
return (
<React.Fragment key={`comma-${item.key}`}>
, {item.element}
</React.Fragment>
);
});
};
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:
@@ -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"
);
});
});
});
@@ -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,