From 6cdcbc8eb201c78bbf26a13c8601323a3f11c06b Mon Sep 17 00:00:00 2001 From: Thomas Zerr Date: Mon, 10 Feb 2025 13:59:00 +0100 Subject: [PATCH] Restructure global configuration forms for each repository type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 , Till-André Diegeler Reviewed-by: Philipp Ahrendt --- .../repository-type-config-forms.yaml | 10 ++ .../src/main/js/GitGlobalConfiguration.tsx | 93 +++++------- .../main/resources/locales/de/plugins.json | 30 ++-- .../main/resources/locales/en/plugins.json | 30 ++-- scm-plugins/scm-hg-plugin/package.json | 5 +- .../api/v2/resources/HgConfigResource.java | 2 +- .../src/main/js/HgConfigurationForm.tsx | 138 ------------------ .../src/main/js/HgGlobalConfiguration.tsx | 46 +++++- .../main/js/HgRepositoryConfigurationForm.tsx | 3 +- .../scm-hg-plugin/src/main/js/hooks.ts | 30 ++++ .../main/resources/locales/de/plugins.json | 27 ++-- .../main/resources/locales/en/plugins.json | 27 ++-- .../v2/resources/HgConfigResourceTest.java | 7 + .../src/main/js/SvnConfigurationForm.tsx | 78 ---------- .../src/main/js/SvnGlobalConfiguration.tsx | 41 +++++- .../main/resources/locales/de/plugins.json | 16 +- .../main/resources/locales/en/plugins.json | 16 +- scm-ui/ui-core/src/base/forms/Form.tsx | 6 +- .../src/base/forms/checkbox/Checkbox.tsx | 107 ++++++++------ .../checkbox/ControlledCheckboxField.tsx | 4 + .../base/forms/input/ControlledInputField.tsx | 4 + .../src/base/forms/input/InputField.tsx | 11 +- yarn.lock | 2 +- 23 files changed, 327 insertions(+), 406 deletions(-) create mode 100644 gradle/changelog/repository-type-config-forms.yaml delete mode 100644 scm-plugins/scm-hg-plugin/src/main/js/HgConfigurationForm.tsx delete mode 100644 scm-plugins/scm-svn-plugin/src/main/js/SvnConfigurationForm.tsx diff --git a/gradle/changelog/repository-type-config-forms.yaml b/gradle/changelog/repository-type-config-forms.yaml new file mode 100644 index 0000000000..2b9f2aef86 --- /dev/null +++ b/gradle/changelog/repository-type-config-forms.yaml @@ -0,0 +1,10 @@ +- type: added + description: Support of description text for input fields and checkboxes +- type: added + description: aria-describedby for input fields and checkboxes +- type: changed + description: Disabling git repositories is now the first setting within the configuration form +- type: changed + description: Disabling hg repositories is now the first setting within the configuration form +- type: changed + description: Disabling svn repositories is now the first setting within the configuration form diff --git a/scm-plugins/scm-git-plugin/src/main/js/GitGlobalConfiguration.tsx b/scm-plugins/scm-git-plugin/src/main/js/GitGlobalConfiguration.tsx index 9bff653d04..1395b1e198 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/GitGlobalConfiguration.tsx +++ b/scm-plugins/scm-git-plugin/src/main/js/GitGlobalConfiguration.tsx @@ -14,13 +14,11 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ -import React, { FC, useEffect } from "react"; -import { useForm } from "react-hook-form"; +import React, { FC } from "react"; import { useTranslation } from "react-i18next"; -import { useConfigLink } from "@scm-manager/ui-api"; -import { ConfigurationForm, InputField, Checkbox, validation } from "@scm-manager/ui-components"; -import { Title, useDocumentTitle } from "@scm-manager/ui-core"; +import { ConfigurationForm, Form, Title, useDocumentTitle } from "@scm-manager/ui-core"; import { HalRepresentation } from "@scm-manager/ui-types"; +import { validation } from "@scm-manager/ui-components"; type Props = { link: string; @@ -40,62 +38,41 @@ const GitGlobalConfiguration: FC = ({ link }) => { const [t] = useTranslation("plugins"); useDocumentTitle(t("scm-git-plugin.config.title")); - const { initialConfiguration, isReadOnly, update, ...formProps } = useConfigLink(link); - const { formState, handleSubmit, register, reset } = useForm({ mode: "onChange" }); - - useEffect(() => { - if (initialConfiguration) { - reset(initialConfiguration); - } - }, [initialConfiguration, reset]); - - const isValidDefaultBranch = (value: string) => { - return validation.isBranchValid(value); + const validateLfsWriteAuthorization = (value: string) => { + const authorizationTime = parseInt(value); + return Number.isInteger(authorizationTime) && authorizationTime > 0; }; return ( - - {t("scm-git-plugin.config.title")} - - - - - + link={link} translationPath={["plugins", "scm-git-plugin.config"]}> + {({ watch }) => ( + <> + {t("scm-git-plugin.config.title")} + + + + {!watch("disabled") ? ( + <> + + + + + + + + + + + + + + ) : null} + + )} ); }; diff --git a/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json index 865e1f3aec..dd59ca4053 100644 --- a/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json +++ b/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json @@ -21,18 +21,24 @@ "config": { "link": "Git", "title": "Git Konfiguration", - "gcExpression": "GC Cron Ausdruck", - "gcExpressionHelpText": "Benutze Quartz Cron Ausdrücke (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK), um git GC regelmäßig auszuführen.", - "nonFastForwardDisallowed": "Deaktiviere \"Non Fast-Forward\"", - "nonFastForwardDisallowedHelpText": "Git Pushes ablehnen, die nicht \"fast-forward\" sind, wie \"--force\".", - "defaultBranch": "Default Branch", - "defaultBranchHelpText": "Dieser Name wird bei der Initialisierung neuer Git Repositories genutzt. Er hat keine weiteren Auswirkungen (insbesondere hat er keinen Einfluss auf den Branchnamen bei leeren Repositories).", - "defaultBranchValidationError": "Dies ist kein valider Branchname", - "lfsWriteAuthorizationExpirationInMinutes": "Ablaufzeit für LFS Autorisierung", - "lfsWriteAuthorizationExpirationInMinutesHelpText": "Ablaufzeit für den Autorisierungstoken in Minuten, der für LFS Speicheranfragen ausgestellt wird. Wenn der SCM-Manager hinter einem Reverse-Proxy mit Zwischenspeicherung (z. B. Nginx) betrieben wird, sollte dieser Wert auf die Zeit gesetzt werden, die ein LFS-Upload maximal benötigen kann.", - "lfsWriteAuthorizationExpirationInMinutesValidationError": "Has to be at least 1 minute", - "disabled": "Deaktiviert", - "disabledHelpText": "Aktiviere oder deaktiviere das Git Plugin. Nur erlaubt, wenn keine Git Repositories existieren.", + "gcExpression": { + "label": "GC Cron Ausdruck", + "descriptionText": "Benutze Quartz Cron Ausdrücke (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK), um git GC regelmäßig auszuführen." + }, + "nonFastForwardDisallowed": { + "label": "Deaktiviere \"Non Fast-Forward\"", + "descriptionText": "Git Pushes ablehnen, die nicht \"fast-forward\" sind, wie \"--force\"." + }, + "defaultBranch": { + "label": "Default Branch für neu initialisierte Repositorys" + }, + "lfsWriteAuthorizationExpirationInMinutes": { + "label": "Ablaufzeit für LFS Autorisierung", + "descriptionText": "Ablaufzeit für den Autorisierungstoken in Minuten, der für LFS Speicheranfragen ausgestellt wird. Wenn der SCM-Manager hinter einem Reverse-Proxy mit Zwischenspeicherung (z. B. Nginx) betrieben wird, sollte dieser Wert auf die Zeit gesetzt werden, die ein LFS-Upload maximal benötigen kann." + }, + "disabled": { + "label": "Deaktiviere das Git Plugin. Nur erlaubt, wenn keine Git Repositories existieren." + }, "submit": "Speichern" }, "repoConfig": { diff --git a/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json index 4133189e75..4f24e0b172 100644 --- a/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json +++ b/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json @@ -21,18 +21,24 @@ "config": { "link": "Git", "title": "Git Configuration", - "gcExpression": "GC Cron Expression", - "gcExpressionHelpText": "Use Quartz Cron Expressions (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK) to run git gc in intervals.", - "nonFastForwardDisallowed": "Disallow Non Fast-Forward", - "nonFastForwardDisallowedHelpText": "Reject git pushes which are non fast-forward such as --force.", - "defaultBranch": "Default Branch", - "defaultBranchHelpText": "This name will be used for the initialization of new git repositories. It has no effect otherwise (especially this cannot change the initial branch name for empty repositories).", - "defaultBranchValidationError": "This is not a valid branch name", - "lfsWriteAuthorizationExpirationInMinutes": "LFS authorization expiration", - "lfsWriteAuthorizationExpirationInMinutesHelpText": "Expiration time of the authorization token generated for LFS put requests in minutes. If SCM-Manager is run behind a reverse proxy that buffers http requests (eg. Nginx), this should set up to the time, an LFS upload may take at maximum.", - "lfsWriteAuthorizationExpirationInMinutesValidationError": "Has to be at least 1 minute", - "disabled": "Disabled", - "disabledHelpText": "Enable or disable the Git plugin. Only allowed if no Git Repositories exist.", + "gcExpression": { + "label": "GC Cron Expression", + "descriptionText": "Use Quartz Cron Expressions (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK) to run git gc in intervals." + }, + "nonFastForwardDisallowed": { + "label": "Disallow Non Fast-Forward", + "descriptionText": "Reject git pushes which are non fast-forward such as --force." + }, + "defaultBranch": { + "label": "Default branch for newly initialized repositories" + }, + "lfsWriteAuthorizationExpirationInMinutes": { + "label": "LFS authorization expiration", + "descriptionText": "Expiration time of the authorization token generated for LFS put requests in minutes. If SCM-Manager is run behind a reverse proxy that buffers http requests (eg. Nginx), this should set up to the time, an LFS upload may take at maximum." + }, + "disabled": { + "label": "Disable the Git plugin. Only allowed if no Git Repositories exist." + }, "submit": "Submit" }, "repoConfig": { diff --git a/scm-plugins/scm-hg-plugin/package.json b/scm-plugins/scm-hg-plugin/package.json index 5112bd3b6f..3be00aad6c 100644 --- a/scm-plugins/scm-hg-plugin/package.json +++ b/scm-plugins/scm-hg-plugin/package.json @@ -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" } -} \ No newline at end of file +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java index 5d7f5d3b04..4a2c7bbbd3 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java @@ -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()) diff --git a/scm-plugins/scm-hg-plugin/src/main/js/HgConfigurationForm.tsx b/scm-plugins/scm-hg-plugin/src/main/js/HgConfigurationForm.tsx deleted file mode 100644 index 7583f433ca..0000000000 --- a/scm-plugins/scm-hg-plugin/src/main/js/HgConfigurationForm.tsx +++ /dev/null @@ -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 = ({ initialConfiguration, onConfigurationChange, readOnly }) => { - const [validationErrors, setValidationErrors] = useState([]); - 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 ( -
- setConfiguration({ ...configuration, hgBinary: value })} - validationError={hasValidationError("hgBinary")} - errorMessage={t("scm-hg-plugin.config.required")} - disabled={readOnly} - /> - setConfiguration({ ...configuration, encoding: value })} - validationError={hasValidationError("encoding")} - errorMessage={t("scm-hg-plugin.config.required")} - disabled={readOnly} - /> - setConfiguration({ ...configuration, showRevisionInId: value })} - disabled={readOnly} - /> - setConfiguration({ ...configuration, enableHttpPostArgs: value })} - disabled={readOnly} - /> - { - setConfiguration({ ...configuration, disabled: value }); - }} - disabled={readOnly || !configuration.allowDisable} - /> - -
- ); -}; - -export default HgConfigurationForm; diff --git a/scm-plugins/scm-hg-plugin/src/main/js/HgGlobalConfiguration.tsx b/scm-plugins/scm-hg-plugin/src/main/js/HgGlobalConfiguration.tsx index 6235affbd3..91a3646f8d 100644 --- a/scm-plugins/scm-hg-plugin/src/main/js/HgGlobalConfiguration.tsx +++ b/scm-plugins/scm-hg-plugin/src/main/js/HgGlobalConfiguration.tsx @@ -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 = ({ 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 ( -
- {t("scm-hg-plugin.config.title")} - } /> -
+ link={link} translationPath={["plugins", "scm-hg-plugin.config"]}> + {({ watch, getValues }) => ( + <> + {t("scm-hg-plugin.config.title")} + + + + {!watch("disabled") ? ( + <> + + + + + + + + + + + + + + + + + ) : null} + + )} + ); }; diff --git a/scm-plugins/scm-hg-plugin/src/main/js/HgRepositoryConfigurationForm.tsx b/scm-plugins/scm-hg-plugin/src/main/js/HgRepositoryConfigurationForm.tsx index 9f4cb407c3..7ad844324e 100644 --- a/scm-plugins/scm-hg-plugin/src/main/js/HgRepositoryConfigurationForm.tsx +++ b/scm-plugins/scm-hg-plugin/src/main/js/HgRepositoryConfigurationForm.tsx @@ -62,8 +62,7 @@ const HgRepositoryConfigurationForm: FC = ({ repository }) => { {updated ? {t("scm-hg-plugin.config.success")} : null} { + const queryClient = useQueryClient(); + const { mutate, isLoading } = useMutation( + (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(null); diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json index 79b24ffea3..3bd423d2a5 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json +++ b/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json @@ -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" diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json index 43df120b83..6ba73785c2 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json +++ b/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json @@ -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" diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java index ac66ed441b..eacf26773c 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java @@ -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 { diff --git a/scm-plugins/scm-svn-plugin/src/main/js/SvnConfigurationForm.tsx b/scm-plugins/scm-svn-plugin/src/main/js/SvnConfigurationForm.tsx deleted file mode 100644 index 5d9d9edcda..0000000000 --- a/scm-plugins/scm-svn-plugin/src/main/js/SvnConfigurationForm.tsx +++ /dev/null @@ -1,78 +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 { Links } from "@scm-manager/ui-types"; -import { Checkbox, Select } from "@scm-manager/ui-components"; - -type Configuration = { - disabled: boolean; - allowDisable: boolean; - compatibility: string; - enabledGZip: boolean; - _links: Links; -}; - -type Props = { - initialConfiguration: Configuration; - readOnly: boolean; - onConfigurationChange: (p1: Configuration, p2: boolean) => void; -}; - -const SvnConfigurationForm: FC = ({ initialConfiguration, readOnly, onConfigurationChange }) => { - const [t] = useTranslation("plugins"); - const [configuration, setConfiguration] = useState(initialConfiguration); - - useEffect(() => setConfiguration(initialConfiguration), [initialConfiguration]); - useEffect(() => onConfigurationChange(configuration, true), [configuration]); - - const options = ["NONE", "PRE14", "PRE15", "PRE16", "PRE17", "WITH17"].map((option: string) => ({ - value: option, - label: t("scm-svn-plugin.config.compatibility-values." + option.toLowerCase()) - })); - - return ( - <> - - - - ) : ( - - )} + ) => { + const descriptionId = descriptionText ? `checkbox-description-${name}` : undefined; + return ( + <> + {descriptionText ?

{descriptionText}

: null} + + {readOnly ? ( + <> + + + + ) : ( + + )} - {label} - {helpText ? : null} - - ) + {label} + {helpText ? : null} + + + ); + } ); export default Checkbox; diff --git a/scm-ui/ui-core/src/base/forms/checkbox/ControlledCheckboxField.tsx b/scm-ui/ui-core/src/base/forms/checkbox/ControlledCheckboxField.tsx index b295ce7b49..28f585e003 100644 --- a/scm-ui/ui-core/src/base/forms/checkbox/ControlledCheckboxField.tsx +++ b/scm-ui/ui-core/src/base/forms/checkbox/ControlledCheckboxField.tsx @@ -35,6 +35,7 @@ function ControlledInputField>({ name, label, helpText, + descriptionText, rules, testId, defaultChecked, @@ -48,6 +49,8 @@ function ControlledInputField>({ const prefixedNameWithoutIndices = prefixWithoutIndices(nameWithPrefix); const labelTranslation = label || t(`${prefixedNameWithoutIndices}.label`) || ""; const helpTextTranslation = helpText || t(`${prefixedNameWithoutIndices}.helpText`); + const descriptionTextTranslation = descriptionText || t(`${prefixedNameWithoutIndices}.descriptionText`); + return ( >({ {...field} label={labelTranslation} helpText={helpTextTranslation} + descriptionText={descriptionTextTranslation} testId={testId ?? `checkbox-${nameWithPrefix}`} /> )} diff --git a/scm-ui/ui-core/src/base/forms/input/ControlledInputField.tsx b/scm-ui/ui-core/src/base/forms/input/ControlledInputField.tsx index 39205549fd..f2fe5df2a2 100644 --- a/scm-ui/ui-core/src/base/forms/input/ControlledInputField.tsx +++ b/scm-ui/ui-core/src/base/forms/input/ControlledInputField.tsx @@ -35,6 +35,7 @@ function ControlledInputField>({ name, label, helpText, + descriptionText, rules, testId, defaultValue, @@ -48,6 +49,8 @@ function ControlledInputField>({ const prefixedNameWithoutIndices = prefixWithoutIndices(nameWithPrefix); const labelTranslation = label || t(`${prefixedNameWithoutIndices}.label`) || ""; const helpTextTranslation = helpText || t(`${prefixedNameWithoutIndices}.helpText`); + const descriptionTextTranslation = descriptionText || t(`${prefixedNameWithoutIndices}.descriptionText`); + return ( >({ form={formId} label={labelTranslation} helpText={helpTextTranslation} + descriptionText={descriptionTextTranslation} error={ fieldState.error ? fieldState.error.message || t(`${prefixedNameWithoutIndices}.error.${fieldState.error.type}`) diff --git a/scm-ui/ui-core/src/base/forms/input/InputField.tsx b/scm-ui/ui-core/src/base/forms/input/InputField.tsx index bdba7f0c6b..1bfbfa1a9f 100644 --- a/scm-ui/ui-core/src/base/forms/input/InputField.tsx +++ b/scm-ui/ui-core/src/base/forms/input/InputField.tsx @@ -26,6 +26,7 @@ import { useAriaId } from "../../helpers"; type InputFieldProps = { label: string; helpText?: string; + descriptionText?: string; error?: string; } & React.ComponentProps; @@ -33,8 +34,9 @@ type InputFieldProps = { * @see https://bulma.io/documentation/form/input/ */ const InputField = React.forwardRef( - ({ label, helpText, error, className, id, ...props }, ref) => { + ({ name, label, helpText, descriptionText, error, className, id, ...props }, ref) => { const inputId = useAriaId(id ?? props.testId); + const descriptionId = descriptionText ? `input-description-${name}` : undefined; const variant = error ? "danger" : undefined; return ( @@ -42,8 +44,13 @@ const InputField = React.forwardRef( {label} {helpText ? : null} + {descriptionText ? ( +

+ {descriptionText} +

+ ) : null} - + {error ? {error} : null}
diff --git a/yarn.lock b/yarn.lock index 39145fd5e6..1eb83fb64b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16351,7 +16351,7 @@ react-popper@^1.3.7: typed-styles "^0.0.7" warning "^4.0.2" -react-query@^3.39.2: +react-query@^3.25.1, react-query@^3.39.2: version "3.39.3" resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.3.tgz#4cea7127c6c26bdea2de5fb63e51044330b03f35" integrity sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==