mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-02 11:35:57 +01:00
Restructure global configuration forms for each repository type
Squash commits of branch feature/repository-type-configuration: - Refactor hg configuration form - Add support of description text for checkbox and input fields - Refactor git configuration form - Refactor svn configuration form - Add aria-describedby for checkbox and input fields - Change hgBinary can also be null - Fix naming of test - Fix spelling - Change logic of successfull notification to only be shown if the config rest api returns without an error Reviewed-by: Philipp Ahrendt <philipp.ahrendt@cloudogu.com>, Till-André Diegeler <till-andre.diegeler@cloudogu.com> Reviewed-by: Philipp Ahrendt <philipp.ahrendt@cloudogu.com>
This commit is contained in:
@@ -10,7 +10,8 @@
|
||||
"typecheck": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@scm-manager/ui-plugins": "3.7.2-SNAPSHOT"
|
||||
"@scm-manager/ui-plugins": "3.7.2-SNAPSHOT",
|
||||
"react-query": "^3.25.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scm-manager/babel-preset": "^2.13.1",
|
||||
@@ -32,4 +33,4 @@
|
||||
"eslintConfig": {
|
||||
"extends": "@scm-manager/eslint-config"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ public class HgConfigResource {
|
||||
HgGlobalConfig config = dtoToConfigMapper.map(configDto);
|
||||
ConfigurationPermissions.write(config).check();
|
||||
|
||||
if (config.getHgBinary() != null) {
|
||||
if (!config.isDisabled() && config.getHgBinary() != null) {
|
||||
HgVerifier.HgVerifyStatus verifyStatus = new HgVerifier().verify(config.getHgBinary());
|
||||
doThrow()
|
||||
.violation(verifyStatus.getDescription())
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import React, { FC, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, Links } from "@scm-manager/ui-types";
|
||||
import { apiClient, Button, Checkbox, InputField } from "@scm-manager/ui-components";
|
||||
|
||||
type Configuration = {
|
||||
disabled: boolean;
|
||||
allowDisable: boolean;
|
||||
hgBinary: string;
|
||||
encoding: string;
|
||||
showRevisionInId: boolean;
|
||||
enableHttpPostArgs: boolean;
|
||||
_links: Links;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
initialConfiguration: Configuration;
|
||||
readOnly: boolean;
|
||||
onConfigurationChange: (p1: Configuration, p2: boolean) => void;
|
||||
};
|
||||
|
||||
const HgConfigurationForm: FC<Props> = ({ initialConfiguration, onConfigurationChange, readOnly }) => {
|
||||
const [validationErrors, setValidationErrors] = useState<string[]>([]);
|
||||
const [configuration, setConfiguration] = useState(initialConfiguration);
|
||||
const [t] = useTranslation("plugins");
|
||||
|
||||
useEffect(() => setConfiguration(initialConfiguration), [initialConfiguration]);
|
||||
useEffect(() => onConfigurationChange(configuration, updateValidationStatus()), [configuration]);
|
||||
|
||||
const updateValidationStatus = () => {
|
||||
const errors = [];
|
||||
if (!configuration.hgBinary) {
|
||||
errors.push("hgBinary");
|
||||
}
|
||||
if (!configuration.encoding) {
|
||||
errors.push("encoding");
|
||||
}
|
||||
|
||||
setValidationErrors(errors);
|
||||
return errors.length === 0;
|
||||
};
|
||||
|
||||
const hasValidationError = (name: string) => {
|
||||
return validationErrors.indexOf(name) >= 0;
|
||||
};
|
||||
|
||||
const triggerAutoConfigure = () => {
|
||||
apiClient
|
||||
.put(
|
||||
(initialConfiguration._links.autoConfiguration as Link).href,
|
||||
{ ...initialConfiguration, hgBinary: configuration.hgBinary },
|
||||
"application/vnd.scmm-hgConfig+json;v=2"
|
||||
)
|
||||
.then(() =>
|
||||
apiClient
|
||||
.get((initialConfiguration._links.self as Link).href)
|
||||
.then(r => r.json())
|
||||
.then((config: Configuration) => setConfiguration({ ...configuration, hgBinary: config.hgBinary }))
|
||||
)
|
||||
.then(() => onConfigurationChange(configuration, updateValidationStatus()));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="is-flex is-flex-direction-column">
|
||||
<InputField
|
||||
name="hgBinary"
|
||||
label={t("scm-hg-plugin.config.hgBinary")}
|
||||
helpText={t("scm-hg-plugin.config.hgBinaryHelpText")}
|
||||
value={configuration.hgBinary}
|
||||
onChange={value => setConfiguration({ ...configuration, hgBinary: value })}
|
||||
validationError={hasValidationError("hgBinary")}
|
||||
errorMessage={t("scm-hg-plugin.config.required")}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
<InputField
|
||||
name="encoding"
|
||||
label={t("scm-hg-plugin.config.encoding")}
|
||||
helpText={t("scm-hg-plugin.config.encodingHelpText")}
|
||||
value={configuration.encoding}
|
||||
onChange={value => setConfiguration({ ...configuration, encoding: value })}
|
||||
validationError={hasValidationError("encoding")}
|
||||
errorMessage={t("scm-hg-plugin.config.required")}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
<Checkbox
|
||||
name="showRevisionInId"
|
||||
label={t("scm-hg-plugin.config.showRevisionInId")}
|
||||
helpText={t("scm-hg-plugin.config.showRevisionInIdHelpText")}
|
||||
checked={configuration.showRevisionInId}
|
||||
onChange={value => setConfiguration({ ...configuration, showRevisionInId: value })}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
<Checkbox
|
||||
name="enableHttpPostArgs"
|
||||
label={t("scm-hg-plugin.config.enableHttpPostArgs")}
|
||||
helpText={t("scm-hg-plugin.config.enableHttpPostArgsHelpText")}
|
||||
checked={configuration.enableHttpPostArgs}
|
||||
onChange={value => setConfiguration({ ...configuration, enableHttpPostArgs: value })}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
<Checkbox
|
||||
name="disabled"
|
||||
label={t("scm-hg-plugin.config.disabled")}
|
||||
helpText={t("scm-hg-plugin.config.disabledHelpText")}
|
||||
checked={configuration.disabled}
|
||||
onChange={value => {
|
||||
setConfiguration({ ...configuration, disabled: value });
|
||||
}}
|
||||
disabled={readOnly || !configuration.allowDisable}
|
||||
/>
|
||||
<Button
|
||||
className="is-align-self-flex-end"
|
||||
disabled={!initialConfiguration?._links?.autoConfiguration}
|
||||
action={() => triggerAutoConfigure()}
|
||||
>
|
||||
{t("scm-hg-plugin.config.autoConfigure")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HgConfigurationForm;
|
||||
@@ -16,23 +16,55 @@
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Configuration } from "@scm-manager/ui-components";
|
||||
import { Title, useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import HgConfigurationForm from "./HgConfigurationForm";
|
||||
import { Button, ConfigurationForm, Form, Title, useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import { SmallLoadingSpinner, validation } from "@scm-manager/ui-components";
|
||||
import { HgGlobalConfigurationDto, useHgAutoConfiguration } from "./hooks";
|
||||
|
||||
type Props = {
|
||||
link: string;
|
||||
};
|
||||
|
||||
const HgGlobalConfiguration: FC<Props> = ({ link }) => {
|
||||
const { mutate: triggerAutoConfiguration, isLoading: isAutoConfigLoading } = useHgAutoConfiguration(link);
|
||||
const [t] = useTranslation("plugins");
|
||||
useDocumentTitle(t("scm-hg-plugin.config.title"));
|
||||
|
||||
const isHgBinaryValid = (hgBinaryPath: string | undefined | null) => {
|
||||
return !hgBinaryPath || validation.isPathValid(hgBinaryPath);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Title>{t("scm-hg-plugin.config.title")}</Title>
|
||||
<Configuration link={link} render={(props: any) => <HgConfigurationForm {...props} />} />
|
||||
</div>
|
||||
<ConfigurationForm<HgGlobalConfigurationDto> link={link} translationPath={["plugins", "scm-hg-plugin.config"]}>
|
||||
{({ watch, getValues }) => (
|
||||
<>
|
||||
<Title>{t("scm-hg-plugin.config.title")}</Title>
|
||||
<Form.Row>
|
||||
<Form.Checkbox name="disabled" readOnly={!watch("allowDisable")} />
|
||||
</Form.Row>
|
||||
{!watch("disabled") ? (
|
||||
<>
|
||||
<Form.Row>
|
||||
<Form.Input name="hgBinary" rules={{ validate: isHgBinaryValid }} />
|
||||
</Form.Row>
|
||||
<Form.Row>
|
||||
<Form.Input name="encoding" />
|
||||
</Form.Row>
|
||||
<Form.Row>
|
||||
<Form.Checkbox name="showRevisionInId" />
|
||||
</Form.Row>
|
||||
<Form.Row>
|
||||
<Form.Checkbox name="enableHttpPostArgs" />
|
||||
</Form.Row>
|
||||
<Form.Row className="is-justify-content-flex-end">
|
||||
<Button className="mr-2" onClick={() => triggerAutoConfiguration(getValues())}>
|
||||
{isAutoConfigLoading ? <SmallLoadingSpinner /> : t("scm-hg-plugin.config.autoConfigure")}
|
||||
</Button>
|
||||
</Form.Row>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</ConfigurationForm>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -62,8 +62,7 @@ const HgRepositoryConfigurationForm: FC<Props> = ({ repository }) => {
|
||||
{updated ? <Notification type="info">{t("scm-hg-plugin.config.success")}</Notification> : null}
|
||||
<InputField
|
||||
name="encoding"
|
||||
label={t("scm-hg-plugin.config.encoding")}
|
||||
helpText={t("scm-hg-plugin.config.encodingHelpText")}
|
||||
label={t("scm-hg-plugin.config.encoding.label")}
|
||||
value={configuration.encoding || ""}
|
||||
onChange={encodingChanged}
|
||||
disabled={readOnly}
|
||||
|
||||
@@ -17,11 +17,41 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
import { HalRepresentation, Link, Repository } from "@scm-manager/ui-types";
|
||||
import { useMutation, useQueryClient } from "react-query";
|
||||
|
||||
export type HgRepositoryConfiguration = HalRepresentation & {
|
||||
encoding?: string;
|
||||
};
|
||||
|
||||
export type HgGlobalConfigurationDto = HalRepresentation & {
|
||||
disabled: boolean;
|
||||
allowDisable: boolean;
|
||||
encoding: string;
|
||||
hgBinary?: string | null;
|
||||
showRevisionInId: boolean;
|
||||
enableHttpPostArgs: boolean;
|
||||
};
|
||||
|
||||
export const useHgAutoConfiguration = (configLink: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate, isLoading } = useMutation<unknown, Error, HgGlobalConfigurationDto>(
|
||||
(config) => apiClient.put(requiredLink(config, "autoConfiguration"), config),
|
||||
{
|
||||
onSuccess: () => {
|
||||
return queryClient.invalidateQueries(["configLink", configLink]);
|
||||
},
|
||||
}
|
||||
);
|
||||
return { mutate, isLoading };
|
||||
};
|
||||
|
||||
const requiredLink = (halObject: HalRepresentation, linkName: string): string => {
|
||||
if (!halObject._links[linkName]) {
|
||||
throw new Error("Could not find link: " + linkName);
|
||||
}
|
||||
return (halObject._links[linkName] as Link).href;
|
||||
};
|
||||
|
||||
export const useHgRepositoryConfiguration = (repository: Repository) => {
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<HgRepositoryConfiguration | null>(null);
|
||||
|
||||
@@ -11,17 +11,22 @@
|
||||
"config": {
|
||||
"link": "Mercurial",
|
||||
"title": "Mercurial Konfiguration",
|
||||
"hgBinary": "HG Binary",
|
||||
"hgBinaryHelpText": "Pfad des Mercurial Binary",
|
||||
"encoding": "Encoding",
|
||||
"encodingHelpText": "Repository Encoding",
|
||||
"showRevisionInId": "Revision anzeigen",
|
||||
"showRevisionInIdHelpText": "Die Revision als Teil der Node ID anzeigen",
|
||||
"enableHttpPostArgs": "HttpPostArgs Protocol aktivieren",
|
||||
"enableHttpPostArgsHelpText": "Aktiviert das experimentelle HttpPostArgs Protokoll von Mercurial. Das HttpPostArgs Protokoll verwendet den Post Request Body anstatt des HTTP Headers um Meta Informationen zu versenden. Dieses Vorgehen reduziert die Header Größe der Mercurial Requests. HttpPostArgs wird seit Mercurial 3.8 unterstützt.",
|
||||
"disabled": "Deaktiviert",
|
||||
"disabledHelpText": "Aktiviert oder deaktiviert das Mercurial Plugin. Nur erlaubt, wenn keine Mercurial Repositories existieren.",
|
||||
"required": "Dieser Konfigurationswert wird benötigt",
|
||||
"disabled": {
|
||||
"label": "Deaktiviert das Mercurial Plugin. Nur erlaubt, wenn keine Mercurial Repositories existieren."
|
||||
},
|
||||
"hgBinary": {
|
||||
"label": "Pfad des Mercurial Binary"
|
||||
},
|
||||
"encoding": {
|
||||
"label": "Repository Encoding"
|
||||
},
|
||||
"showRevisionInId": {
|
||||
"label": "Die Revision als Teil der Node ID anzeigen"
|
||||
},
|
||||
"enableHttpPostArgs": {
|
||||
"label": "HttpPostArgs Protocol aktivieren",
|
||||
"descriptionText": "Aktiviert das experimentelle HttpPostArgs Protokoll von Mercurial. Das HttpPostArgs Protokoll verwendet den Post Request Body anstatt des HTTP Headers um Meta Informationen zu versenden. Dieses Vorgehen reduziert die Header Größe der Mercurial Requests. HttpPostArgs wird seit Mercurial 3.8 unterstützt."
|
||||
},
|
||||
"submit": "Speichern",
|
||||
"success": "Einstellungen wurden erfolgreich geändert",
|
||||
"autoConfigure": "Mercurial automatisch konfigurieren"
|
||||
|
||||
@@ -11,17 +11,22 @@
|
||||
"config": {
|
||||
"link": "Mercurial",
|
||||
"title": "Mercurial Configuration",
|
||||
"hgBinary": "HG Binary",
|
||||
"hgBinaryHelpText": "Location of Mercurial binary",
|
||||
"encoding": "Encoding",
|
||||
"encodingHelpText": "Repository Encoding",
|
||||
"showRevisionInId": "Show Revision",
|
||||
"showRevisionInIdHelpText": "Show revision as part of the node id",
|
||||
"enableHttpPostArgs": "Enable HttpPostArgs Protocol",
|
||||
"enableHttpPostArgsHelpText": "Enables the experimental HttpPostArgs Protocol of mercurial. The HttpPostArgs Protocol uses the body of post requests to send the meta information instead of http headers. This helps to reduce the header size of mercurial requests. HttpPostArgs is supported since mercurial 3.8.",
|
||||
"disabled": "Disabled",
|
||||
"disabledHelpText": "Enable or disable the Mercurial plugin. Only allowed if no Mercurial repositories exist.",
|
||||
"required": "This configuration value is required",
|
||||
"disabled": {
|
||||
"label": "Disable the Mercurial plugin. Only allowed if no Mercurial repositories exist."
|
||||
},
|
||||
"hgBinary": {
|
||||
"label": "Path to the mercurial binary"
|
||||
},
|
||||
"encoding": {
|
||||
"label": "Repository encoding"
|
||||
},
|
||||
"showRevisionInId": {
|
||||
"label": "Show revision as part of the node id"
|
||||
},
|
||||
"enableHttpPostArgs": {
|
||||
"label": "Enable HttpPostArgs Protocol",
|
||||
"descriptionText": "Enables the experimental HttpPostArgs Protocol of mercurial. The HttpPostArgs Protocol uses the body of post requests to send the meta information instead of http headers. This helps to reduce the header size of mercurial requests. HttpPostArgs is supported since mercurial 3.8."
|
||||
},
|
||||
"submit": "Submit",
|
||||
"success": "Configuration changed successfully",
|
||||
"autoConfigure": "Configure Mercurial Automatically"
|
||||
|
||||
@@ -161,6 +161,13 @@ public class HgConfigResourceTest {
|
||||
assertEquals(400, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldUpdateInvalidBinaryIfConfigIsDisabled() throws URISyntaxException {
|
||||
MockHttpResponse response = put("{\"disabled\": true, \"hgBinary\":\"3.2.1\"}");
|
||||
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, UnsupportedEncodingException {
|
||||
|
||||
Reference in New Issue
Block a user