Skip to content
This repository has been archived by the owner on Jul 2, 2024. It is now read-only.

Commit

Permalink
EVG-15463: Create the Patch Name Change Modal (#1871)
Browse files Browse the repository at this point in the history
  • Loading branch information
SupaJoon authored Jun 7, 2023
1 parent 4ee6c94 commit c044132
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 10 deletions.
37 changes: 37 additions & 0 deletions cypress/integration/version/name_change_modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
describe("Name change modal", () => {
beforeEach(() => {
cy.visit("version/5f74d99ab2373627c047c5e5");
});

it("Use the name change modal to change the name of a patch", () => {
const originalName = "main: EVG-7823 add a commit queue message (#4048)";
cy.contains(originalName);
cy.dataCy("name-change-modal-trigger").click();
const newName = "a different name";
cy.get("textarea").clear().type(newName);
cy.contains("Confirm").click();
cy.get("textarea").should("not.exist");
cy.contains(newName);
cy.validateToast("success", "Patch name was successfully updated.", true);
// revert name change
cy.dataCy("name-change-modal-trigger").click();
cy.get("textarea").clear().type(originalName);
cy.contains("Confirm").click();
cy.get("textarea").should("not.exist");
cy.validateToast("success", "Patch name was successfully updated.", true);
cy.contains(originalName);
});

it("The confirm button is disabled when the text area value is empty or greater than 300 characters", () => {
cy.dataCy("name-change-modal-trigger").click();
cy.get("textarea").clear();
cy.contains("button", "Confirm").should("be.disabled");
cy.get("textarea").type("lol");
cy.contains("button", "Confirm").should("not.be.disabled");
const over300Chars =
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
cy.get("textarea").type(over300Chars);
cy.contains("button", "Confirm").should("be.disabled");
cy.contains("should NOT be longer than 300 characters");
});
});
4 changes: 3 additions & 1 deletion src/components/PageTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ const TitleTypography: React.VFC<TitleTypographyProps> = ({
}
};

export const PageTitle: React.VFC<Props> = ({
export const PageTitle: React.FC<Props> = ({
children,
loading,
title,
badge,
Expand All @@ -54,6 +55,7 @@ export const PageTitle: React.VFC<Props> = ({
<TitleWrapper size={size}>
<TitleTypography size={size}>
<span data-cy="page-title">{title}</span>
{children}
<BadgeWrapper size={size}>{badge}</BadgeWrapper>
</TitleTypography>
</TitleWrapper>
Expand Down
25 changes: 22 additions & 3 deletions src/components/SpruceForm/Widgets/LeafyGreenWidgets.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useEffect, useRef } from "react";
import styled from "@emotion/styled";
import Banner from "@leafygreen-ui/banner";
import Checkbox from "@leafygreen-ui/checkbox";
Expand Down Expand Up @@ -330,21 +331,39 @@ const StyledRadioBox = styled(RadioBox)`
`;

export const LeafyGreenTextArea: React.VFC<SpruceWidgetProps> = ({
label,
disabled,
value,
label,
onChange,
options,
rawErrors,
readonly,
value,
}) => {
const { "data-cy": dataCy, emptyValue = "", elementWrapperCSS } = options;
const {
"data-cy": dataCy,
elementWrapperCSS,
emptyValue = "",
focusOnMount,
} = options;

const { errors, hasError } = processErrors(rawErrors);
const el = useRef<HTMLTextAreaElement>();

useEffect(() => {
if (focusOnMount) {
const textarea = el.current;
if (textarea) {
textarea.focus();
textarea.selectionStart = textarea.value.length;
textarea.selectionEnd = textarea.value.length;
}
}
}, [focusOnMount]);

return (
<ElementWrapper css={elementWrapperCSS}>
<TextArea
ref={el}
data-cy={dataCy}
label={label}
disabled={disabled || readonly}
Expand Down
7 changes: 4 additions & 3 deletions src/components/SpruceForm/Widgets/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ export interface SpruceWidgetProps extends WidgetProps {
"aria-controls": string[];
"data-cy": string;
ariaLabelledBy: string;
customLabel: string;
description: string;
elementWrapperCSS: SerializedStyles;
emptyValue: string | null;
errors: string[];
focusOnMount: boolean;
inputType: TextInputType;
showLabel: boolean;
sizeVariant: string;
tooltipDescription: string;
inputType: TextInputType;
warnings: string[];
customLabel: string;
sizeVariant: string;
}>;
}

Expand Down
25 changes: 25 additions & 0 deletions src/gql/generated/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4538,6 +4538,31 @@ export type UpdateHostStatusMutation = {
updateHostStatus: number;
};

export type UpdatePatchDescriptionMutationVariables = Exact<{
patchId: Scalars["String"];
description: Scalars["String"];
}>;

export type UpdatePatchDescriptionMutation = {
__typename?: "Mutation";
schedulePatch: {
__typename?: "Patch";
activated: boolean;
alias?: string | null;
author: string;
commitQueuePosition?: number | null;
description: string;
id: string;
status: string;
parameters: Array<{ __typename?: "Parameter"; key: string; value: string }>;
variantsTasks: Array<{
__typename?: "VariantTask";
name: string;
tasks: Array<string>;
} | null>;
};
};

export type UpdatePublicKeyMutationVariables = Exact<{
targetKeyName: Scalars["String"];
updateInfo: PublicKeyInput;
Expand Down
6 changes: 4 additions & 2 deletions src/gql/mutations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import SPAWN_VOLUME from "./spawn-volume.graphql";
import UNSCHEDULE_PATCH_TASKS from "./unschedule-patch-tasks.graphql";
import UNSCHEDULE_TASK from "./unschedule-task.graphql";
import UPDATE_HOST_STATUS from "./update-host-status.graphql";
import UPDATE_PATCH_DESCRIPTION from "./update-patch-description.graphql";
import UPDATE_PUBLIC_KEY from "./update-public-key.graphql";
import UPDATE_SPAWN_HOST_STATUS from "./update-spawn-host-status.graphql";
import UPDATE_SPAWN_VOLUME from "./update-spawn-volume.graphql";
Expand Down Expand Up @@ -73,6 +74,7 @@ export {
ENQUEUE_PATCH,
FILE_JIRA_TICKET,
FORCE_REPOTRACKER_RUN,
MIGRATE_VOLUME,
MOVE_ANNOTATION,
OVERRIDE_TASK_DEPENDENCIES,
PROMOTE_VARS_TO_REPO,
Expand All @@ -81,8 +83,8 @@ export {
REMOVE_ITEM_FROM_COMMIT_QUEUE,
REMOVE_PUBLIC_KEY,
REMOVE_VOLUME,
RESTART_JASPER,
REPROVISION_TO_NEW,
RESTART_JASPER,
RESTART_TASK,
RESTART_VERSIONS,
SAVE_PROJECT_SETTINGS_FOR_SECTION,
Expand All @@ -99,9 +101,9 @@ export {
UNSCHEDULE_PATCH_TASKS,
UNSCHEDULE_TASK,
UPDATE_HOST_STATUS,
UPDATE_PATCH_DESCRIPTION,
UPDATE_PUBLIC_KEY,
UPDATE_SPAWN_HOST_STATUS,
UPDATE_SPAWN_VOLUME,
UPDATE_USER_SETTINGS,
MIGRATE_VOLUME,
};
10 changes: 10 additions & 0 deletions src/gql/mutations/update-patch-description.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#import "../fragments/basePatch.graphql"

mutation UpdatePatchDescription($patchId: String!, $description: String!) {
schedulePatch(
patchId: $patchId
configure: { variantsTasks: [], description: $description }
) {
...BasePatch
}
}
7 changes: 6 additions & 1 deletion src/pages/Version.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
Metadata,
VersionTabs,
} from "./version/index";
import { NameChangeModal } from "./version/NameChangeModal";

export const VersionPage: React.VFC = () => {
const spruceConfig = useSpruceConfig();
Expand Down Expand Up @@ -193,7 +194,11 @@ export const VersionPage: React.VFC = () => {
loading={false}
pageTitle={pageTitle}
title={linkifiedMessage || `Version ${order}`}
/>
>
{isPatch && (
<NameChangeModal patchId={id} originalPatchName={message} />
)}
</PageTitle>
<PageLayout>
<PageSider>
<Metadata loading={false} version={version} />
Expand Down
27 changes: 27 additions & 0 deletions src/pages/version/NameChangeModal/getFormSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { SpruceFormProps } from "components/SpruceForm/types";

export const getFormSchema = (
name: string
): {
schema: SpruceFormProps["schema"];
uiSchema: SpruceFormProps["uiSchema"];
} => ({
schema: {
type: "object" as "object",
properties: {
newPatchName: {
title: "New Patch Name",
type: "string" as "string",
default: name,
maxLength: 300,
minLength: 1,
},
},
},
uiSchema: {
newPatchName: {
"ui:widget": "textarea",
"ui:focusOnMount": true,
},
},
});
86 changes: 86 additions & 0 deletions src/pages/version/NameChangeModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useState } from "react";
import { useMutation } from "@apollo/client";
import styled from "@emotion/styled";
import IconButton from "@leafygreen-ui/icon-button";
import { ConfirmationModal } from "components/ConfirmationModal";
import Icon from "components/Icon";
import { SpruceForm } from "components/SpruceForm";
import { size } from "constants/tokens";
import { useToastContext } from "context/toast";
import {
UpdatePatchDescriptionMutation,
UpdatePatchDescriptionMutationVariables,
} from "gql/generated/types";
import { UPDATE_PATCH_DESCRIPTION } from "gql/mutations";
import { getFormSchema } from "./getFormSchema";

interface NameChangeModalProps {
originalPatchName: string;
patchId: string;
}
export const NameChangeModal: React.VFC<NameChangeModalProps> = ({
originalPatchName,
patchId,
}) => {
const [formState, setFormState] = useState<{ newPatchName?: string }>({});
const [hasFormError, setHasFormError] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const { schema, uiSchema } = getFormSchema(originalPatchName);
const dispatchToast = useToastContext();
const [updateDescription, { loading }] = useMutation<
UpdatePatchDescriptionMutation,
UpdatePatchDescriptionMutationVariables
>(UPDATE_PATCH_DESCRIPTION, {
onCompleted() {
setIsOpen(false);
dispatchToast.success("Patch name was successfully updated.");
},
onError({ message }) {
dispatchToast.error(`Error updating patch name: ${message}.`);
},
refetchQueries: ["Version"],
});

const { newPatchName } = formState;

return (
<>
<StyledIconButton
onClick={() => setIsOpen(true)}
data-cy="name-change-modal-trigger"
aria-label="name-change-modal-trigger"
>
<Icon glyph="Edit" />
</StyledIconButton>
<ConfirmationModal
buttonText="Confirm"
onCancel={() => setIsOpen(false)}
onConfirm={() => {
updateDescription({
variables: { patchId, description: newPatchName },
});
}}
open={isOpen}
title="Update Patch Name"
submitDisabled={
newPatchName === originalPatchName || hasFormError || loading
}
>
<SpruceForm
schema={schema}
uiSchema={uiSchema}
formData={formState}
onChange={({ formData, errors }) => {
setHasFormError(!!errors.length);
setFormState(formData);
}}
/>
</ConfirmationModal>
</>
);
};

const StyledIconButton = styled(IconButton)`
vertical-align: top;
margin-left: ${size.xxs};
`;

0 comments on commit c044132

Please sign in to comment.