Fix es lint errors and warnings and enforce es lint as build breaker. (#1878)

Co-authored-by: Konstantin Schaper <konstantin.schaper@cloudogu.com>
This commit is contained in:
Eduard Heimbuch
2021-12-09 09:12:02 +01:00
committed by GitHub
parent 65d1e4ffd2
commit 289175331f
155 changed files with 905 additions and 791 deletions

View File

@@ -22,16 +22,16 @@
* SOFTWARE.
*/
import React from "react";
import { mount, shallow } from "@scm-manager/ui-tests/enzyme-router";
import "@scm-manager/ui-tests/enzyme";
import "@scm-manager/ui-tests/i18n";
import EditRepoNavLink from "./EditRepoNavLink";
import { mount, shallow } from "@scm-manager/ui-tests";
describe("GeneralNavLink", () => {
it("should render nothing, if the modify link is missing", () => {
const repository = {
_links: {},
namespace: "space",
name: "name",
type: "git",
_links: {}
};
const navLink = shallow(<EditRepoNavLink repository={repository} editUrl="" />);
@@ -40,11 +40,14 @@ describe("GeneralNavLink", () => {
it("should render the navLink", () => {
const repository = {
namespace: "space",
name: "name",
type: "git",
_links: {
update: {
href: "/repositories",
},
},
href: "/repositories"
}
}
};
const navLink = mount(<EditRepoNavLink repository={repository} editUrl="" />);

View File

@@ -21,29 +21,22 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import React, { FC } from "react";
import { useTranslation } from "react-i18next";
import { Repository } from "@scm-manager/ui-types";
import { NavLink } from "@scm-manager/ui-components";
type Props = WithTranslation & {
type Props = {
repository: Repository;
editUrl: string;
};
class EditRepoNavLink extends React.Component<Props> {
isEditable = () => {
return this.props.repository._links.update;
};
render() {
const { editUrl, t } = this.props;
if (!this.isEditable()) {
return null;
}
return <NavLink to={editUrl} label={t("repositoryRoot.menu.generalNavLink")} />;
const EditRepoNavLink: FC<Props> = ({ repository, editUrl }) => {
const [t] = useTranslation("repos");
if (!repository._links.update) {
return null;
}
}
return <NavLink to={editUrl} label={t("repositoryRoot.menu.generalNavLink")} />;
};
export default withTranslation("repos")(EditRepoNavLink);
export default EditRepoNavLink;

View File

@@ -43,7 +43,7 @@ const ImportFromBundleForm: FC<Props> = ({
setCompressed,
password,
setPassword,
disabled,
disabled
}) => {
const [t] = useTranslation("repos");
@@ -53,7 +53,7 @@ const ImportFromBundleForm: FC<Props> = ({
<div className="column is-half is-vcentered">
<LabelWithHelpIcon label={t("import.bundle.title")} helpText={t("import.bundle.helpText")} />
<FileUpload
handleFile={(file) => {
handleFile={file => {
setFile(file);
setValid(!!file);
}}
@@ -74,7 +74,7 @@ const ImportFromBundleForm: FC<Props> = ({
<div className="column is-half is-vcentered">
<InputField
value={password}
onChange={(value) => setPassword(value)}
onChange={value => setPassword(value)}
type="password"
label={t("import.bundle.password.title")}
helpText={t("import.bundle.password.helpText")}

View File

@@ -47,7 +47,7 @@ const ImportFromUrlForm: FC<Props> = ({ repository, onChange, setValid, disabled
const handleImportUrlBlur = (importUrl: string) => {
if (!repository.name) {
// If the repository name is not fill we set a name suggestion
const match = importUrl.match(/([^\/]+?)(?:.git)?$/);
const match = importUrl.match(/([^/]+?)(?:.git)?$/);
if (match && match[1]) {
onChange({ ...repository, name: match[1] });
}
@@ -71,7 +71,7 @@ const ImportFromUrlForm: FC<Props> = ({ repository, onChange, setValid, disabled
<div className="column is-half px-3">
<InputField
label={t("import.username")}
onChange={(username) => onChange({ ...repository, username })}
onChange={username => onChange({ ...repository, username })}
value={repository.username}
helpText={t("help.usernameHelpText")}
disabled={disabled}
@@ -80,7 +80,7 @@ const ImportFromUrlForm: FC<Props> = ({ repository, onChange, setValid, disabled
<div className="column is-half px-3">
<InputField
label={t("import.password")}
onChange={(password) => onChange({ ...repository, password })}
onChange={password => onChange({ ...repository, password })}
value={repository.password}
type="password"
helpText={t("help.passwordHelpText")}

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React, { FC, FormEvent, useEffect, useState } from "react";
import React, { FC, FormEvent, useCallback, useEffect, useState } from "react";
import { Repository, RepositoryCreation, RepositoryType } from "@scm-manager/ui-types";
import { ErrorNotification, Level, SubmitButton } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
@@ -42,35 +42,46 @@ const ImportFullRepository: FC<Props> = ({
setImportPending,
setImportedRepository,
nameForm: NameForm,
informationForm: InformationForm,
informationForm: InformationForm
}) => {
const [repo, setRepo] = useState<RepositoryCreation>({
name: "",
namespace: "",
type: repositoryType.name,
contact: "",
description: "",
contextEntries: [],
description: ""
});
const [password, setPassword] = useState("");
const [valid, setValid] = useState({ namespaceAndName: false, contact: true, file: false });
const [file, setFile] = useState<File | null>(null);
const [t] = useTranslation("repos");
const { importFullRepository, importedRepository, isLoading, error } = useImportFullRepository(repositoryType);
const setContactValid = useCallback((contact: boolean) => setValid(currentValid => ({ ...currentValid, contact })), [
setValid
]);
const setNamespaceAndNameValid = useCallback(
(namespaceAndName: boolean) => setValid(currentValid => ({ ...currentValid, namespaceAndName })),
[setValid]
);
const setFileValid = useCallback((file: boolean) => setValid(currentValid => ({ ...currentValid, file })), [
setValid
]);
useEffect(() => setRepo({ ...repo, type: repositoryType.name }), [repositoryType]);
useEffect(() => setImportPending(isLoading), [isLoading]);
useEffect(() => setImportPending(isLoading), [isLoading, setImportPending]);
useEffect(() => {
if (importedRepository) {
setImportedRepository(importedRepository);
}
}, [importedRepository]);
}, [importedRepository, setImportedRepository]);
const isValid = () => Object.values(valid).every((v) => v);
const isValid = () => Object.values(valid).every(v => v);
const submit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
importFullRepository(repo, file!, password);
if (!file) {
throw new Error("File is required for import");
}
importFullRepository({ ...repo, type: repositoryType.name }, file, password);
};
return (
@@ -80,20 +91,20 @@ const ImportFullRepository: FC<Props> = ({
setFile={setFile}
password={password}
setPassword={setPassword}
setValid={(file: boolean) => setValid({ ...valid, file })}
setValid={setFileValid}
/>
<hr />
<NameForm
repository={repo}
onChange={setRepo as React.Dispatch<React.SetStateAction<RepositoryCreation>>}
setValid={(namespaceAndName: boolean) => setValid({ ...valid, namespaceAndName })}
setValid={setNamespaceAndNameValid}
disabled={isLoading}
/>
<InformationForm
repository={repo}
onChange={setRepo as React.Dispatch<React.SetStateAction<RepositoryCreation>>}
disabled={isLoading}
setValid={(contact: boolean) => setValid({ ...valid, contact })}
setValid={setContactValid}
/>
<Level
right={<SubmitButton disabled={!isValid()} loading={isLoading} label={t("repositoryForm.submitImport")} />}

View File

@@ -50,7 +50,7 @@ const ImportFullRepositoryForm: FC<Props> = ({ setFile, setValid, password, setP
<div className="column is-half is-vcentered">
<InputField
value={password}
onChange={(value) => setPassword(value)}
onChange={setPassword}
type="password"
label={t("import.bundle.password.title")}
helpText={t("import.bundle.password.helpText")}

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React, { FC, FormEvent, useEffect, useState } from "react";
import React, { FC, FormEvent, useCallback, useEffect, useState } from "react";
import { Repository, RepositoryCreation, RepositoryType } from "@scm-manager/ui-types";
import { ErrorNotification, Level, SubmitButton } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
@@ -42,37 +42,49 @@ const ImportRepositoryFromBundle: FC<Props> = ({
setImportPending,
setImportedRepository,
nameForm: NameForm,
informationForm: InformationForm,
informationForm: InformationForm
}) => {
const [repo, setRepo] = useState<RepositoryCreation>({
name: "",
namespace: "",
type: repositoryType.name,
contact: "",
description: "",
contextEntries: [],
description: ""
});
const [password, setPassword] = useState("");
const [valid, setValid] = useState({ namespaceAndName: false, contact: true, file: false });
const [file, setFile] = useState<File | null>(null);
const [compressed, setCompressed] = useState(true);
const [t] = useTranslation("repos");
const { importRepositoryFromBundle, importedRepository, error, isLoading } =
useImportRepositoryFromBundle(repositoryType);
const { importRepositoryFromBundle, importedRepository, error, isLoading } = useImportRepositoryFromBundle(
repositoryType
);
const setContactValid = useCallback((contact: boolean) => setValid(currentValid => ({ ...currentValid, contact })), [
setValid
]);
const setNamespaceAndNameValid = useCallback(
(namespaceAndName: boolean) => setValid(currentValid => ({ ...currentValid, namespaceAndName })),
[setValid]
);
const setFileValid = useCallback((file: boolean) => setValid(currentValid => ({ ...currentValid, file })), [
setValid
]);
useEffect(() => setRepo({ ...repo, type: repositoryType.name }), [repositoryType]);
useEffect(() => setImportPending(isLoading), [isLoading]);
useEffect(() => setImportPending(isLoading), [isLoading, setImportPending]);
useEffect(() => {
if (importedRepository) {
setImportedRepository(importedRepository);
}
}, [importedRepository]);
}, [importedRepository, setImportedRepository]);
const isValid = () => Object.values(valid).every((v) => v);
const isValid = () => Object.values(valid).every(v => v);
const submit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
importRepositoryFromBundle(repo, file!, compressed, password);
if (!file) {
throw new Error("File is required for import");
}
importRepositoryFromBundle({ ...repo, type: repositoryType.name }, file, compressed, password);
};
return (
@@ -80,7 +92,7 @@ const ImportRepositoryFromBundle: FC<Props> = ({
<ErrorNotification error={error} />
<ImportFromBundleForm
setFile={setFile}
setValid={(file: boolean) => setValid({ ...valid, file })}
setValid={setFileValid}
compressed={compressed}
setCompressed={setCompressed}
password={password}
@@ -91,14 +103,14 @@ const ImportRepositoryFromBundle: FC<Props> = ({
<NameForm
repository={repo}
onChange={setRepo as React.Dispatch<React.SetStateAction<RepositoryCreation>>}
setValid={(namespaceAndName: boolean) => setValid({ ...valid, namespaceAndName })}
setValid={setNamespaceAndNameValid}
disabled={isLoading}
/>
<InformationForm
repository={repo}
onChange={setRepo as React.Dispatch<React.SetStateAction<RepositoryCreation>>}
disabled={isLoading}
setValid={(contact: boolean) => setValid({ ...valid, contact })}
setValid={setContactValid}
/>
<Level
right={<SubmitButton disabled={!isValid()} loading={isLoading} label={t("repositoryForm.submitImport")} />}

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React, { FC, FormEvent, useEffect, useState } from "react";
import React, { FC, FormEvent, useCallback, useEffect, useState } from "react";
import { Repository, RepositoryCreation, RepositoryType, RepositoryUrlImport } from "@scm-manager/ui-types";
import ImportFromUrlForm from "./ImportFromUrlForm";
import { ErrorNotification, Level, SubmitButton } from "@scm-manager/ui-components";
@@ -42,7 +42,7 @@ const ImportRepositoryFromUrl: FC<Props> = ({
setImportPending,
setImportedRepository,
nameForm: NameForm,
informationForm: InformationForm,
informationForm: InformationForm
}) => {
const [repo, setRepo] = useState<RepositoryUrlImport>({
name: "",
@@ -52,50 +52,54 @@ const ImportRepositoryFromUrl: FC<Props> = ({
description: "",
importUrl: "",
username: "",
password: "",
contextEntries: [],
password: ""
});
const [valid, setValid] = useState({ namespaceAndName: false, contact: true, importUrl: false });
const [t] = useTranslation("repos");
const { importRepositoryFromUrl, importedRepository, error, isLoading } = useImportRepositoryFromUrl(repositoryType);
const setContactValid = useCallback((contact: boolean) => setValid(currentValid => ({ ...currentValid, contact })), [
setValid
]);
const setNamespaceAndNameValid = useCallback(
(namespaceAndName: boolean) => setValid(currentValid => ({ ...currentValid, namespaceAndName })),
[setValid]
);
const setImportUrlValid = useCallback(
(importUrl: boolean) => setValid(currentValid => ({ ...currentValid, importUrl })),
[setValid]
);
useEffect(() => setRepo({ ...repo, type: repositoryType.name }), [repositoryType]);
useEffect(() => setImportPending(isLoading), [isLoading]);
useEffect(() => setImportPending(isLoading), [isLoading, setImportPending]);
useEffect(() => {
if (importedRepository) {
setImportedRepository(importedRepository);
}
}, [importedRepository]);
}, [importedRepository, setImportedRepository]);
const isValid = () => Object.values(valid).every((v) => v);
const isValid = () => Object.values(valid).every(v => v);
const submit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
importRepositoryFromUrl(repo);
importRepositoryFromUrl({ ...repo, type: repositoryType.name });
};
return (
<form onSubmit={submit}>
{error ? <ErrorNotification error={error} /> : null}
<ImportFromUrlForm
repository={repo}
onChange={setRepo}
setValid={(importUrl: boolean) => setValid({ ...valid, importUrl })}
disabled={isLoading}
/>
<ImportFromUrlForm repository={repo} onChange={setRepo} setValid={setImportUrlValid} disabled={isLoading} />
<hr />
<NameForm
repository={repo}
onChange={setRepo as React.Dispatch<React.SetStateAction<RepositoryCreation>>}
setValid={(namespaceAndName: boolean) => setValid({ ...valid, namespaceAndName })}
setValid={setNamespaceAndNameValid}
disabled={isLoading}
/>
<InformationForm
repository={repo}
onChange={setRepo as React.Dispatch<React.SetStateAction<RepositoryCreation>>}
disabled={isLoading}
setValid={(contact: boolean) => setValid({ ...valid, contact })}
setValid={setContactValid}
/>
<Level
right={<SubmitButton disabled={!isValid()} loading={isLoading} label={t("repositoryForm.submitImport")} />}

View File

@@ -38,11 +38,11 @@ const ImportRepositoryTypeSelect: FC<Props> = ({ repositoryTypes, repositoryType
const createSelectOptions = () => {
const options = repositoryTypes
.filter((repoType) => !!repoType._links.import)
.map((repositoryType) => {
.filter(repoType => !!repoType._links.import)
.map(repositoryType => {
return {
label: repositoryType.displayName,
value: repositoryType.name,
value: repositoryType.name
};
});
options.unshift({ label: "", value: "" });
@@ -50,7 +50,7 @@ const ImportRepositoryTypeSelect: FC<Props> = ({ repositoryTypes, repositoryType
};
const onChangeType = (type: string) => {
const repositoryType = repositoryTypes.filter((t) => t.name === type)[0];
const repositoryType = repositoryTypes.filter(t => t.name === type)[0];
setRepositoryType(repositoryType);
};

View File

@@ -68,7 +68,7 @@ const NamespaceAndNameFields: FC<Props> = ({ repository, onChange, setValid, dis
} else {
setValid(false);
}
}, [repository.name, repository.namespace]);
}, [repository.name, repository.namespace, namespaceStrategy, setValid]);
const handleNamespaceChange = (namespace: string) => {
const valid = validator.isNamespaceValid(namespace);
@@ -96,7 +96,7 @@ const NamespaceAndNameFields: FC<Props> = ({ repository, onChange, setValid, dis
errorMessage: t("validation.namespace-invalid"),
validationError: namespaceValidationError,
disabled: disabled,
informationMessage,
informationMessage
};
if (namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY) {

View File

@@ -22,15 +22,14 @@
* SOFTWARE.
*/
import React from "react";
import { mount, shallow } from "@scm-manager/ui-tests/enzyme-router";
import "@scm-manager/ui-tests/enzyme";
import "@scm-manager/ui-tests/i18n";
import { mount, shallow } from "@scm-manager/ui-tests";
import "@scm-manager/ui-tests";
import PermissionsNavLink from "./PermissionsNavLink";
describe("PermissionsNavLink", () => {
it("should render nothing, if the modify link is missing", () => {
const repository = {
_links: {},
_links: {}
};
const navLink = shallow(<PermissionsNavLink repository={repository} permissionUrl="" />);
@@ -41,9 +40,9 @@ describe("PermissionsNavLink", () => {
const repository = {
_links: {
permissions: {
href: "/permissions",
},
},
href: "/permissions"
}
}
};
const navLink = mount(<PermissionsNavLink repository={repository} permissionUrl="" />);

View File

@@ -42,7 +42,7 @@ class RepositoryDetails extends React.Component<Props> {
name="repos.repository-details.information"
renderAll={true}
props={{
repository,
repository
}}
/>
</div>

View File

@@ -58,7 +58,7 @@ const RepositoryInformationForm: FC<Props> = ({ repository, onChange, disabled,
/>
<Textarea
label={t("repository.description")}
onChange={(description) => onChange({ ...repository, description })}
onChange={description => onChange({ ...repository, description })}
value={repository ? repository.description : ""}
helpText={t("help.descriptionHelpText")}
disabled={disabled}

View File

@@ -22,8 +22,8 @@
* SOFTWARE.
*/
import React from "react";
import { mount, shallow } from "@scm-manager/ui-tests/enzyme-router";
import "@scm-manager/ui-tests/i18n";
import { mount, shallow } from "@scm-manager/ui-tests";
import "@scm-manager/ui-tests";
import RepositoryNavLink from "./RepositoryNavLink";
describe("RepositoryNavLink", () => {
@@ -32,7 +32,7 @@ describe("RepositoryNavLink", () => {
namespace: "Namespace",
name: "Repo",
type: "GIT",
_links: {},
_links: {}
};
const navLink = shallow(
@@ -54,9 +54,9 @@ describe("RepositoryNavLink", () => {
type: "GIT",
_links: {
sources: {
href: "/sources",
},
},
href: "/sources"
}
}
};
const navLink = mount(

View File

@@ -24,13 +24,14 @@
import React from "react";
import { Repository } from "@scm-manager/ui-types";
import { NavLink } from "@scm-manager/ui-components";
import { RouteProps } from "react-router-dom";
type Props = {
repository: Repository;
to: string;
label: string;
linkName: string;
activeWhenMatch?: (route: any) => boolean;
activeWhenMatch?: (route: RouteProps) => boolean;
activeOnlyWhenExact: boolean;
icon?: string;
title?: string;

View File

@@ -131,7 +131,7 @@ const ChangesetDetails: FC<Props> = ({ changeset, repository, fileControlFactory
const description = changesets.parseDescription(changeset.description);
const id = <ChangesetId repository={repository} changeset={changeset} link={false} />;
const date = <DateFromNow date={changeset.date} />;
const parents = changeset._embedded.parents?.map((parent: ParentChangeset, index: number) => (
const parents = changeset._embedded?.parents?.map((parent: ParentChangeset, index: number) => (
<ReactLink title={parent.id} to={parent.id} key={index}>
{parent.id.substring(0, 7)}
</ReactLink>
@@ -187,7 +187,7 @@ const ChangesetDetails: FC<Props> = ({ changeset, repository, fileControlFactory
<Button
color="success"
className="tag"
label={(changeset._embedded["tags"]?.length === 0 && t("changeset.tag.create")) || ""}
label={(changeset._embedded?.tags?.length === 0 && t("changeset.tag.create")) || ""}
icon="plus"
action={() => setTagCreationModalVisible(true)}
/>

View File

@@ -40,7 +40,7 @@ const ChangesetShortLink: (changeset: Changeset, value: string) => Replacement[]
const link = `/repo/${namespace}/${name}/code/changeset/${revision}`;
replacements.push({
textToReplace: m[0],
replacement: <Link to={link}>{m[0]}</Link>,
replacement: <Link to={link}>{m[0]}</Link>
});
m = regex.exec(value);
}

View File

@@ -62,6 +62,16 @@ const Contributor: FC<{ person: Person }> = ({ person }) => {
return <>{person.name}</>;
};
const getUnique = (items: string[]) =>
Object.keys(
items.reduce((result, item) => {
if (!(item in result)) {
result[item] = true;
}
return result;
}, {} as { [type: string]: boolean })
);
const ContributorTable: FC<Props> = ({ changeset }) => {
const [t] = useTranslation("plugins");
@@ -69,11 +79,11 @@ const ContributorTable: FC<Props> = ({ changeset }) => {
if (!changeset.contributors) {
return [];
}
return new Set(changeset.contributors.map((contributor) => contributor.type));
return getUnique(changeset.contributors.map(contributor => contributor.type));
};
const getPersonsByContributorType = (type: string) => {
return changeset.contributors?.filter((contributor) => contributor.type === type).map((t) => t.person);
return changeset.contributors?.filter(contributor => contributor.type === type).map(t => t.person);
};
const getContributorsByType = () => {
@@ -94,12 +104,12 @@ const ContributorTable: FC<Props> = ({ changeset }) => {
<Contributor person={changeset.author} />
</td>
</tr>
{getContributorsByType().map((contributor) => (
{getContributorsByType().map(contributor => (
<tr key={contributor.type}>
<SizedTd>{t("changeset.contributor.type." + contributor.type)}:</SizedTd>
<td className="is-ellipsis-overflow m-0">
<CommaSeparatedList>
{contributor.persons!.map((person) => (
{contributor.persons?.map(person => (
<Contributor key={person.name} person={person} />
))}
</CommaSeparatedList>

View File

@@ -37,12 +37,10 @@ type Props = {
const CreateTagModal: FC<Props> = ({ repository, changeset, onClose }) => {
const { isLoading, error, data: tags } = useTags(repository);
const {
isLoading: isLoadingCreate,
error: errorCreate,
create,
tag: createdTag,
} = useCreateTag(repository, changeset);
const { isLoading: isLoadingCreate, error: errorCreate, create, tag: createdTag } = useCreateTag(
repository,
changeset
);
const [t] = useTranslation("repos");
const [newTagName, setNewTagName] = useState("");
useEffect(() => {
@@ -51,7 +49,7 @@ const CreateTagModal: FC<Props> = ({ repository, changeset, onClose }) => {
}
}, [createdTag, onClose]);
const tagNames = tags?._embedded.tags.map((tag) => tag.name);
const tagNames = tags?._embedded.tags.map(tag => tag.name);
let validationError = "";
@@ -76,7 +74,7 @@ const CreateTagModal: FC<Props> = ({ repository, changeset, onClose }) => {
<InputField
name="name"
label={t("tags.create.form.field.name.label")}
onChange={(val) => setNewTagName(val)}
onChange={val => setNewTagName(val)}
value={newTagName}
validationError={!!validationError}
errorMessage={t(validationError)}

View File

@@ -25,7 +25,14 @@ import React, { FC, useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import classNames from "classnames";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { IndexResources, Repository, RepositoryType, CUSTOM_NAMESPACE_STRATEGY } from "@scm-manager/ui-types";
import {
CUSTOM_NAMESPACE_STRATEGY,
IndexResources,
Repository,
RepositoryBase,
RepositoryCreation,
RepositoryType
} from "@scm-manager/ui-types";
import { Checkbox, Level, Select, SubmitButton } from "@scm-manager/ui-components";
import NamespaceAndNameFields from "../NamespaceAndNameFields";
import RepositoryInformationForm from "../RepositoryInformationForm";
@@ -40,10 +47,6 @@ type Props = {
indexResources?: IndexResources;
};
type RepositoryCreation = Repository & {
contextEntries: object;
};
const RepositoryForm: FC<Props> = ({
createRepository,
modifyRepository,
@@ -51,29 +54,35 @@ const RepositoryForm: FC<Props> = ({
repositoryTypes,
namespaceStrategy,
loading,
indexResources,
indexResources
}) => {
const [repo, setRepo] = useState<Repository>({
const [repo, setRepo] = useState<RepositoryBase>({
name: "",
namespace: "",
type: "",
contact: "",
description: "",
_links: {},
description: ""
});
const [initRepository, setInitRepository] = useState(false);
const [contextEntries, setContextEntries] = useState({});
const setCreationContextEntry = useCallback(
(key: string, value: any) => {
setContextEntries((entries) => ({
(key: string, value: unknown) => {
setContextEntries(entries => ({
...entries,
[key]: value,
[key]: value
}));
},
[setContextEntries]
);
const [valid, setValid] = useState({ namespaceAndName: true, contact: true });
const [t] = useTranslation("repos");
const setContactValid = useCallback((contact: boolean) => setValid(currentValid => ({ ...currentValid, contact })), [
setValid
]);
const setNamespaceAndNameValid = useCallback(
(namespaceAndName: boolean) => setValid(currentValid => ({ ...currentValid, namespaceAndName })),
[setValid]
);
useEffect(() => {
if (repository) {
@@ -88,7 +97,7 @@ const RepositoryForm: FC<Props> = ({
const isValid = () => {
return (
!(!repo.name || (namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY && !repo.namespace)) &&
Object.values(valid).every((v) => v)
Object.values(valid).every(v => v)
);
};
@@ -98,17 +107,21 @@ const RepositoryForm: FC<Props> = ({
if (createRepository) {
createRepository({ ...repo, contextEntries }, initRepository);
} else if (modifyRepository) {
modifyRepository(repo);
if (!!repository && !!repository._links.update) {
modifyRepository({ ...repository, ...repo });
} else {
throw new Error("Repository has to be present and contain update link for modification");
}
}
}
};
const createSelectOptions = (repositoryTypes?: RepositoryType[]) => {
if (repositoryTypes) {
return repositoryTypes.map((repositoryType) => {
return repositoryTypes.map(repositoryType => {
return {
label: repositoryType.displayName,
value: repositoryType.name,
value: repositoryType.name
};
});
}
@@ -123,21 +136,21 @@ const RepositoryForm: FC<Props> = ({
const extensionProps = {
repository: repo,
setCreationContextEntry: setCreationContextEntry,
indexResources: indexResourcesWithLinks,
indexResources: indexResourcesWithLinks
};
return (
<>
<NamespaceAndNameFields
repository={repo}
onChange={setRepo}
setValid={(namespaceAndName) => setValid({ ...valid, namespaceAndName })}
setValid={setNamespaceAndNameValid}
disabled={disabled}
/>
<div className="columns">
<div className={classNames("column", "is-half")}>
<Select
label={t("repository.type")}
onChange={(type) => setRepo({ ...repo, type })}
onChange={type => setRepo({ ...repo, type })}
value={repo ? repo.type : ""}
options={createSelectOptions(repositoryTypes)}
helpText={t("help.typeHelpText")}
@@ -181,12 +194,7 @@ const RepositoryForm: FC<Props> = ({
return (
<form onSubmit={submit}>
{renderCreateOnlyFields()}
<RepositoryInformationForm
repository={repo}
onChange={setRepo}
disabled={disabled}
setValid={(contact) => setValid({ ...valid, contact })}
/>
<RepositoryInformationForm repository={repo} onChange={setRepo} disabled={disabled} setValid={setContactValid} />
{submitButton()}
</form>
);

View File

@@ -71,7 +71,7 @@ const RepositoryFormSwitcher: FC<Props> = ({ forms }) => (
<TopLevel
right={
<ButtonAddons>
{(forms || []).map((form) => (
{(forms || []).map(form => (
<RepositoryFormButton key={form.path} {...form} />
))}
</ButtonAddons>

View File

@@ -39,7 +39,7 @@ describe("repository name validation", () => {
it("should allow same names as the backend", () => {
const validPaths = ["scm", "scm.gitz", "s", "sc", ".hiddenrepo", "b.", "...", "..c", "d..", "a..c"];
validPaths.forEach((path) => expect(validator.isNameValid(path)).toBe(true));
validPaths.forEach(path => expect(validator.isNameValid(path)).toBe(true));
});
it("should deny same names as the backend", () => {
@@ -68,6 +68,7 @@ describe("repository name validation", () => {
"scm//main",
"scm\\main",
"scm/main-$HOME",
// eslint-disable-next-line no-template-curly-in-string
"scm/main-${HOME}-home",
"scm/main-%HOME-home",
"scm/main-%HOME%-home",
@@ -92,10 +93,10 @@ describe("repository name validation", () => {
"scm/main",
"scm/plugins/git-plugin",
"scm/plugins/git-plugin",
"scm.git",
"scm.git"
];
invalidPaths.forEach((path) => expect(validator.isNameValid(path)).toBe(false));
invalidPaths.forEach(path => expect(validator.isNameValid(path)).toBe(false));
});
});

View File

@@ -25,6 +25,7 @@
import { validation } from "@scm-manager/ui-components";
import { isNameValid as isUserNameValid } from "../../../users/components/userValidation";
// eslint-disable-next-line
const nameRegex = /(?!^\.\.$)(?!^\.$)(?!.*[.]git$)(?!.*[\\\[\]])^[A-Za-z0-9\.][A-Za-z0-9\.\-_]*$/;
const namespaceExceptionsRegex = /^(([0-9]{1,3})|(create)|(import))$/;

View File

@@ -50,10 +50,10 @@ class RepositoryList extends React.Component<Props> {
props={{
page,
search,
namespace,
namespace
}}
/>
{groups.map((group) => {
{groups.map(group => {
return <RepositoryGroupEntry group={group} key={group.name} />;
})}
</div>

View File

@@ -26,42 +26,42 @@ import groupByNamespace from "./groupByNamespace";
const base = {
type: "git",
_links: {},
_links: {}
};
const slartiBlueprintsFjords = {
...base,
namespace: "slarti",
name: "fjords-blueprints",
name: "fjords-blueprints"
};
const slartiFjords = {
...base,
namespace: "slarti",
name: "fjords",
name: "fjords"
};
const hitchhikerRestand = {
...base,
namespace: "hitchhiker",
name: "restand",
name: "restand"
};
const hitchhikerPuzzle42 = {
...base,
namespace: "hitchhiker",
name: "puzzle42",
name: "puzzle42"
};
const hitchhikerHeartOfGold = {
...base,
namespace: "hitchhiker",
name: "heartOfGold",
name: "heartOfGold"
};
const zaphodMarvinFirmware = {
...base,
namespace: "zaphod",
name: "marvin-firmware",
name: "marvin-firmware"
};
it("should group the repositories by their namespace", () => {
@@ -71,30 +71,30 @@ it("should group the repositories by their namespace", () => {
hitchhikerRestand,
slartiFjords,
hitchhikerHeartOfGold,
hitchhikerPuzzle42,
hitchhikerPuzzle42
];
const namespaces = {
_embedded: {
namespaces: [{ namespace: "hitchhiker" }, { namespace: "slarti" }, { namespace: "zaphod" }],
},
namespaces: [{ namespace: "hitchhiker" }, { namespace: "slarti" }, { namespace: "zaphod" }]
}
};
const expected = [
{
name: "hitchhiker",
namespace: { namespace: "hitchhiker" },
repositories: [hitchhikerHeartOfGold, hitchhikerPuzzle42, hitchhikerRestand],
repositories: [hitchhikerHeartOfGold, hitchhikerPuzzle42, hitchhikerRestand]
},
{
name: "slarti",
namespace: { namespace: "slarti" },
repositories: [slartiFjords, slartiBlueprintsFjords],
repositories: [slartiFjords, slartiBlueprintsFjords]
},
{
name: "zaphod",
namespace: { namespace: "zaphod" },
repositories: [zaphodMarvinFirmware],
},
repositories: [zaphodMarvinFirmware]
}
];
expect(groupByNamespace(repositories, namespaces)).toEqual(expected);

View File

@@ -38,7 +38,7 @@ export default function groupByNamespace(
group = {
name: groupName,
namespace: namespace,
repositories: [],
repositories: []
};
groups[groupName] = group;
}
@@ -65,5 +65,5 @@ function sortByName(a, b) {
}
function findNamespace(namespaces: NamespaceCollection, namespaceToFind: string) {
return namespaces._embedded.namespaces.find((namespace) => namespace.namespace === namespaceToFind);
return namespaces._embedded.namespaces.find(namespace => namespace.namespace === namespaceToFind);
}