mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-11 07:55:47 +01:00
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:
@@ -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="" />);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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")}
|
||||
|
||||
@@ -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")}
|
||||
|
||||
@@ -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")} />}
|
||||
|
||||
@@ -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")}
|
||||
|
||||
@@ -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")} />}
|
||||
|
||||
@@ -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")} />}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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="" />);
|
||||
|
||||
@@ -42,7 +42,7 @@ class RepositoryDetails extends React.Component<Props> {
|
||||
name="repos.repository-details.information"
|
||||
renderAll={true}
|
||||
props={{
|
||||
repository,
|
||||
repository
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -71,7 +71,7 @@ const RepositoryFormSwitcher: FC<Props> = ({ forms }) => (
|
||||
<TopLevel
|
||||
right={
|
||||
<ButtonAddons>
|
||||
{(forms || []).map((form) => (
|
||||
{(forms || []).map(form => (
|
||||
<RepositoryFormButton key={form.path} {...form} />
|
||||
))}
|
||||
</ButtonAddons>
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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))$/;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user