Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: unify name form fields for Renku 2.0 #3439

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import PermissionsGuard from "../../../permissionsV2/PermissionsGuard";
import type { Project } from "../../../projectsV2/api/projectV2.api";
import { usePatchProjectsByProjectIdMutation } from "../../../projectsV2/api/projectV2.enhanced-api";
import ProjectDescriptionFormField from "../../../projectsV2/fields/ProjectDescriptionFormField";
import ProjectNameFormField from "../../../projectsV2/fields/ProjectNameFormField";
// import ProjectNameFormField from "../../../projectsV2/fields/ProjectNameFormField";
import ProjectNamespaceFormField from "../../../projectsV2/fields/ProjectNamespaceFormField";
import ProjectVisibilityFormField from "../../../projectsV2/fields/ProjectVisibilityFormField";

Expand All @@ -56,6 +56,7 @@ import useProjectPermissions from "../../utils/useProjectPermissions.hook";

import ProjectPageDelete from "./ProjectDelete";
import ProjectPageSettingsMembers from "./ProjectSettingsMembers";
import NameFormField from "../../../entitiesV2/fields/NameFormField";

function notificationProjectUpdated(
notifications: NotificationsManager,
Expand Down Expand Up @@ -194,7 +195,13 @@ function ProjectSettingsEditForm({ project }: ProjectPageSettingsProps) {
)}

<Form noValidate onSubmit={handleSubmit(onSubmit)}>
<ProjectNameFormField name="name" control={control} errors={errors} />
{/* <ProjectNameFormField name="name" control={control} errors={errors} /> */}
<NameFormField
className="mb-3"
control={control}
entityType="project"
name="name"
/>
<PermissionsGuard
disabled={
<ProjectReadOnlyNamespaceField namespace={project.namespace} />
Expand Down
19 changes: 19 additions & 0 deletions client/src/features/entitiesV2/entities.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*!
* Copyright 2024 - Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export type RenkuEntityType = "group" | "user" | "project" | "data-connector";
26 changes: 26 additions & 0 deletions client/src/features/entitiesV2/entities.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*!
* Copyright 2024 - Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { RenkuEntityType } from "./entities.types";

export function displayEntityType(t: RenkuEntityType): string {
if (t === "data-connector") {
return "data connector";
}
return t;
}
73 changes: 73 additions & 0 deletions client/src/features/entitiesV2/fields/GenericInputText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*!
* Copyright 2024 - Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import cx from "classnames";
import type { FieldValues } from "react-hook-form";
import { Controller } from "react-hook-form";

import { FormText, Input, Label } from "reactstrap";
import type { GenericFormFieldProps } from "./forms.types";

export default function GenericInputText<T extends FieldValues>({
className,
control,
dataCy,
defaultErrorMessage,
defaultValue,
disabled,
name,
helpText,
inputDataCy,
inputId,
label,
rules,
shouldUnregister, // eslint-disable-line spellcheck/spell-checker
}: GenericFormFieldProps<T>) {
const inputHelpId = `${inputId}-help`;

return (
<div className={className} data-cy={dataCy}>
{label && <Label for={inputId}>{label}</Label>}
<Controller
control={control}
defaultValue={defaultValue}
disabled={disabled}
name={name}
render={({ field: { ref, ...rest }, fieldState: { error } }) => (
<>
<Input
aria-describedby={helpText ? inputHelpId : undefined}
className={cx(error && "is-invalid")}
data-cy={inputDataCy}
id={inputId}
type="text"
innerRef={ref}
{...rest}
/>
<div className="invalid-feedback">
{error?.message ?? defaultErrorMessage}
</div>
</>
)}
rules={rules}
shouldUnregister={shouldUnregister} // eslint-disable-line spellcheck/spell-checker
/>
{helpText && <FormText id={inputHelpId}>{helpText}</FormText>}
</div>
);
}
64 changes: 64 additions & 0 deletions client/src/features/entitiesV2/fields/NameFormField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*!
* Copyright 2024 - Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { useMemo } from "react";
import type { FieldValues } from "react-hook-form";

import { displayEntityType } from "../entities.utils";
import type { EntityFormFieldProps } from "./forms.types";
import GenericInputText from "./GenericInputText";

export default function NameFormField<T extends FieldValues>({
entityType,
defaultErrorMessage: defaultErrorMessage_,
helpText: helpText_,
inputId: inputId_,
inputIdPrefix,
label: label_,
...props
}: EntityFormFieldProps<T>) {
const inputId = useMemo(
() =>
inputId_ ||
(inputIdPrefix
? `${inputIdPrefix}-${entityType}-${props.name}`
: `$${entityType}-${props.name}`),
[entityType, inputIdPrefix, inputId_, props.name]
);
const defaultErrorMessage = useMemo(
() => defaultErrorMessage_ ?? "Please provide a valid name.",
[defaultErrorMessage_]
);
const helpText = useMemo(
() =>
helpText_ ??
`The name you will use to refer to the ${displayEntityType(entityType)}.`,
[entityType, helpText_]
);
const label = useMemo(() => label_ ?? "Name", [label_]);

return (
<GenericInputText
defaultErrorMessage={defaultErrorMessage}
helpText={helpText}
inputId={inputId}
label={label}
{...props}
/>
);
}
46 changes: 46 additions & 0 deletions client/src/features/entitiesV2/fields/forms.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*!
* Copyright 2024 - Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { ReactNode } from "react";
import type { FieldValues, UseControllerProps } from "react-hook-form";
import { RenkuEntityType } from "../entities.types";

/** Most generic type for a form field in the Renku 2.0 UI. */
export interface GenericFormFieldProps<T extends FieldValues>
extends UseControllerProps<T> {
className?: string;
dataCy?: string;
defaultErrorMessage?: ReactNode;
helpText?: ReactNode;
inputDataCy?: string;
inputId: string;
label?: ReactNode;
}

type GenericFormFieldPropsWithOptionalInputId<T extends FieldValues> = Omit<
GenericFormFieldProps<T>,
"inputId"
> &
Partial<Pick<GenericFormFieldProps<T>, "inputId">>;

/** Type for an entity's form field in the Renku 2.0 UI. */
export interface EntityFormFieldProps<T extends FieldValues>
extends GenericFormFieldPropsWithOptionalInputId<T> {
entityType: RenkuEntityType;
inputIdPrefix?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ import {
usePatchGroupsByGroupSlugMutation,
} from "../../projectsV2/api/projectV2.enhanced-api";
import DescriptionFormField from "../../projectsV2/fields/DescriptionFormField";
import NameFormField from "../../projectsV2/fields/NameFormField";
// import NameFormField from "../../projectsV2/fields/NameFormField";
import SlugFormField from "../../projectsV2/fields/SlugFormField";
import NameFormField from "../../entitiesV2/fields/NameFormField";

type GroupMetadata = Omit<GroupPatchRequest, "repositories">;

Expand Down Expand Up @@ -166,9 +167,13 @@ export default function GroupMetadataForm({ group }: GroupMetadataFormProps) {
<GroupDeleteConfirmation isOpen={isOpen} group={group} toggle={toggle} />
<Form noValidate onSubmit={handleSubmit(onSubmit)}>
<NameFormField
// control={control}
// entityName="group"
// errors={errors}
// name="name"
className="mb-3"
control={control}
entityName="group"
errors={errors}
entityType="group"
name="name"
/>
<SlugFormField
Expand Down
7 changes: 4 additions & 3 deletions client/src/features/projectsV2/new/GroupNew.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ import LoginAlert from "../../../components/loginAlert/LoginAlert";
import type { GroupPostRequest } from "../api/namespace.api";
import { usePostGroupsMutation } from "../api/projectV2.enhanced-api";
import DescriptionFormField from "../fields/DescriptionFormField";
import NameFormField from "../fields/NameFormField";
// import NameFormField from "../fields/NameFormField";
import SlugFormField from "../fields/SlugFormField";
import WipBadge from "../shared/WipBadge";
import NameFormField from "../../entitiesV2/fields/NameFormField";

function GroupNewHeader() {
return (
Expand Down Expand Up @@ -128,9 +129,9 @@ function GroupMetadataForm() {
<Form noValidate onSubmit={handleSubmit(onSubmit)}>
<NameFormField
control={control}
entityName="group"
errors={errors}
entityType="group"
name="name"
className="mb-3"
/>
<SlugFormField
control={control}
Expand Down
11 changes: 9 additions & 2 deletions client/src/features/projectsV2/new/ProjectV2NewForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import useAppSelector from "../../../utils/customHooks/useAppSelector.hook";
import { slugFromTitle } from "../../../utils/helpers/HelperFunctions";

import ProjectDescriptionFormField from "../fields/ProjectDescriptionFormField";
import ProjectNameFormField from "../fields/ProjectNameFormField";
// import ProjectNameFormField from "../fields/ProjectNameFormField";
import ProjectNamespaceFormField from "../fields/ProjectNamespaceFormField";
import ProjectRepositoryFormField from "../fields/ProjectRepositoryFormField";
import ProjectSlugFormField from "../fields/ProjectSlugFormField";
Expand All @@ -42,6 +42,7 @@ import {
setMetadata,
} from "./projectV2New.slice";
import { PlusLg } from "react-bootstrap-icons";
import NameFormField from "../../entitiesV2/fields/NameFormField";

interface ProjectV2NewFormProps {
currentStep: NewProjectV2State["currentStep"];
Expand Down Expand Up @@ -136,7 +137,13 @@ function ProjectV2NewMetadataStepForm({ currentStep }: ProjectV2NewFormProps) {
<>
<h4 className={cx("d-none", "d-md-block")}>Describe the project</h4>
<Form noValidate onSubmit={handleSubmit(onSubmit)}>
<ProjectNameFormField control={control} errors={errors} name="name" />
{/* <ProjectNameFormField control={control} errors={errors} name="name" /> */}
<NameFormField
className="mb-3"
control={control}
entityType="project"
name="name"
/>
<ProjectSlugFormField control={control} errors={errors} name="slug" />
<ProjectNamespaceFormField
control={control}
Expand Down
Loading