diff --git a/scm-ui-components/packages/ui-components/src/Autocomplete.js b/scm-ui-components/packages/ui-components/src/Autocomplete.js index adf86e37b7..22107e75b6 100644 --- a/scm-ui-components/packages/ui-components/src/Autocomplete.js +++ b/scm-ui-components/packages/ui-components/src/Autocomplete.js @@ -4,7 +4,6 @@ import { AsyncCreatable, Async } from "react-select"; import type { AutocompleteObject, SelectValue } from "@scm-manager/ui-types"; import LabelWithHelpIcon from "./forms/LabelWithHelpIcon"; - type Props = { loadSuggestions: string => Promise, valueSelected: SelectValue => void, @@ -17,12 +16,9 @@ type Props = { creatable?: boolean }; - type State = {}; class Autocomplete extends React.Component { - - static defaultProps = { placeholder: "Type here", loadingMessage: "Loading...", @@ -34,7 +30,11 @@ class Autocomplete extends React.Component { }; // We overwrite this to avoid running into a bug (https://github.com/JedWatson/react-select/issues/2944) - isValidNewOption = (inputValue: string, selectValue: SelectValue, selectOptions: SelectValue[]) => { + isValidNewOption = ( + inputValue: string, + selectValue: SelectValue, + selectOptions: SelectValue[] + ) => { const isNotDuplicated = !selectOptions .map(option => option.label) .includes(inputValue); @@ -43,12 +43,21 @@ class Autocomplete extends React.Component { }; render() { - const { label, helpText, value, placeholder, loadingMessage, noOptionsMessage, loadSuggestions, creatable } = this.props; + const { + label, + helpText, + value, + placeholder, + loadingMessage, + noOptionsMessage, + loadSuggestions, + creatable + } = this.props; return (
- {creatable? + {creatable ? ( { }); }} /> - : + ) : ( { loadingMessage={() => loadingMessage} noOptionsMessage={() => noOptionsMessage} /> - - } + )}
); } } - export default Autocomplete; diff --git a/scm-ui-components/packages/ui-components/src/ErrorNotification.js b/scm-ui-components/packages/ui-components/src/ErrorNotification.js index b8acf89733..72173679dd 100644 --- a/scm-ui-components/packages/ui-components/src/ErrorNotification.js +++ b/scm-ui-components/packages/ui-components/src/ErrorNotification.js @@ -10,35 +10,33 @@ type Props = { error?: Error }; - class ErrorNotification extends React.Component { render() { const { t, error } = this.props; if (error) { if (error instanceof BackendError) { - return + return ; } else if (error instanceof UnauthorizedError) { return ( - {t("error-notification.prefix")}:{" "} - {t("error-notification.timeout")}{" "} + {t("errorNotification.prefix")}:{" "} + {t("errorNotification.timeout")}{" "} - {t("error-notification.loginLink")} + {t("errorNotification.loginLink")} ); } else if (error instanceof ForbiddenError) { return ( - {t("error-notification.prefix")}:{" "} - {t("error-notification.forbidden")} + {t("errorNotification.prefix")}:{" "} + {t("errorNotification.forbidden")} - ) - } else - { + ); + } else { return ( - {t("error-notification.prefix")}: {error.message} + {t("errorNotification.prefix")}: {error.message} ); } @@ -47,4 +45,4 @@ class ErrorNotification extends React.Component { } } -export default translate("commons")(ErrorNotification); +export default translate("commons")(ErrorNotification); diff --git a/scm-ui-components/packages/ui-components/src/GroupAutocomplete.js b/scm-ui-components/packages/ui-components/src/GroupAutocomplete.js new file mode 100644 index 0000000000..e39130f1d7 --- /dev/null +++ b/scm-ui-components/packages/ui-components/src/GroupAutocomplete.js @@ -0,0 +1,27 @@ +// @flow +import React from "react"; +import { translate } from "react-i18next"; +import type AutocompleteProps from "./UserGroupAutocomplete"; +import UserGroupAutocomplete from "./UserGroupAutocomplete"; + +type Props = AutocompleteProps & { + // Context props + t: string => string +}; + +class GroupAutocomplete extends React.Component { + render() { + const { t } = this.props; + return ( + + ); + } +} + +export default translate("commons")(GroupAutocomplete); diff --git a/scm-ui-components/packages/ui-components/src/UserAutocomplete.js b/scm-ui-components/packages/ui-components/src/UserAutocomplete.js new file mode 100644 index 0000000000..9ef7aaa7a7 --- /dev/null +++ b/scm-ui-components/packages/ui-components/src/UserAutocomplete.js @@ -0,0 +1,27 @@ +// @flow +import React from "react"; +import { translate } from "react-i18next"; +import type AutocompleteProps from "./UserGroupAutocomplete"; +import UserGroupAutocomplete from "./UserGroupAutocomplete"; + +type Props = AutocompleteProps & { + // Context props + t: string => string +}; + +class UserAutocomplete extends React.Component { + render() { + const { t } = this.props; + return ( + + ); + } +} + +export default translate("commons")(UserAutocomplete); diff --git a/scm-ui-components/packages/ui-components/src/UserGroupAutocomplete.js b/scm-ui-components/packages/ui-components/src/UserGroupAutocomplete.js new file mode 100644 index 0000000000..0d6e3ec46e --- /dev/null +++ b/scm-ui-components/packages/ui-components/src/UserGroupAutocomplete.js @@ -0,0 +1,52 @@ +// @flow +import React from "react"; +import type { SelectValue } from "@scm-manager/ui-types"; +import Autocomplete from "./Autocomplete"; + +export type AutocompleteProps = { + autocompleteLink: string, + valueSelected: SelectValue => void, + value?: SelectValue +}; + +type Props = AutocompleteProps & { + label: string, + noOptionsMessage: string, + loadingMessage: string, + placeholder: string +}; + +export default class UserGroupAutocomplete extends React.Component { + loadSuggestions = (inputValue: string) => { + const url = this.props.autocompleteLink; + const link = url + "?q="; + return fetch(link + inputValue) + .then(response => response.json()) + .then(json => { + return json.map(element => { + const label = element.displayName + ? `${element.displayName} (${element.id})` + : element.id; + return { + value: element, + label + }; + }); + }); + }; + + selectName = (selection: SelectValue) => { + this.props.valueSelected(selection); + }; + + render() { + return ( + + ); + } +} diff --git a/scm-ui-components/packages/ui-components/src/index.js b/scm-ui-components/packages/ui-components/src/index.js index ae6fd6f875..7b80b3bc44 100644 --- a/scm-ui-components/packages/ui-components/src/index.js +++ b/scm-ui-components/packages/ui-components/src/index.js @@ -26,6 +26,8 @@ export { default as Tooltip } from "./Tooltip"; // TODO do we need this? getPageFromMatch is already exported by urls export { getPageFromMatch } from "./urls"; export { default as Autocomplete} from "./Autocomplete"; +export { default as GroupAutocomplete} from "./GroupAutocomplete"; +export { default as UserAutocomplete} from "./UserAutocomplete"; export { default as BranchSelector } from "./BranchSelector"; export { default as Breadcrumb } from "./Breadcrumb"; export { default as MarkdownView } from "./MarkdownView"; diff --git a/scm-ui/public/locales/de/commons.json b/scm-ui/public/locales/de/commons.json index 38042c4806..3964863f46 100644 --- a/scm-ui/public/locales/de/commons.json +++ b/scm-ui/public/locales/de/commons.json @@ -19,11 +19,11 @@ "subtitle": "Ein unbekannter Fehler ist aufgetreten." } }, - "error-notification": { + "errorNotification": { "prefix": "Fehler", "loginLink": "Erneute Anmeldung", "timeout": "Die Session ist abgelaufen.", - "wrong-login-credentials": "Ungültige Anmeldedaten", + "wrongLoginCredentials": "Ungültige Anmeldedaten", "forbidden": "Sie haben nicht die Berechtigung, diesen Datensatz zu sehen" }, "loading": { @@ -40,6 +40,15 @@ "admin": "Administration" }, "filterEntries": "Einträge filtern", + "autocomplete": { + "group": "Gruppe", + "user": "Benutzer", + "noGroupOptions": "Kein Gruppenname als Vorschlag verfügbar", + "groupPlaceholder": "Gruppe eingeben", + "noUserOptions": "Kein Benutzername als Vorschlag verfügbar", + "userPlaceholder": "Benutzer eingeben", + "loading": "suche..." + }, "paginator": { "next": "Weiter", "previous": "Zurück" diff --git a/scm-ui/public/locales/de/repos.json b/scm-ui/public/locales/de/repos.json index 4ba7a725e6..4f85ce0fde 100644 --- a/scm-ui/public/locales/de/repos.json +++ b/scm-ui/public/locales/de/repos.json @@ -11,7 +11,10 @@ "validation": { "namespace-invalid": "Der Namespace des Repository ist ungültig", "name-invalid": "Der Name des Repository ist ungültig", - "contact-invalid": "Der Kontakt muss eine gültige E-Mail Adresse sein" + "contact-invalid": "Der Kontakt muss eine gültige E-Mail Adresse sein", + "branch": { + "nameInvalid": "Der Name des Branches ist ungültig" + } }, "help": { "namespaceHelpText": "Der Namespace des Repository. Dieser wird Teil der URL des Repository sein.", @@ -150,13 +153,6 @@ "roleHelpText": "READ = read; WRITE = read und write; OWNER = read, write und auch die Möglichkeit Einstellungen und Berechtigungen zu verwalten. Wenn hier nichts angezeigt wird, den Erweitert-Button benutzen, um Details zu sehen.", "permissionsHelpText": "Hier können individuelle Berechtigungen unabhängig von vordefinierten Rollen vergeben werden." }, - "autocomplete": { - "no-group-options": "Kein Gruppenname als Vorschlag verfügbar", - "group-placeholder": "Gruppe eingeben", - "no-user-options": "Kein Benutzername als Vorschlag verfügbar", - "user-placeholder": "Benutzer eingeben", - "loading": "suche..." - }, "advanced": { "dialog": { "title": "Erweiterte Berechtigungen", diff --git a/scm-ui/public/locales/en/commons.json b/scm-ui/public/locales/en/commons.json index b41d2b341e..c57ef700ef 100644 --- a/scm-ui/public/locales/en/commons.json +++ b/scm-ui/public/locales/en/commons.json @@ -19,11 +19,11 @@ "subtitle": "Unknown error occurred" } }, - "error-notification": { + "errorNotification": { "prefix": "Error", "loginLink": "You can login here again.", "timeout": "The session has expired", - "wrong-login-credentials": "Invalid credentials", + "wrongLoginCredentials": "Invalid credentials", "forbidden": "You don't have permission to view this entity" }, "loading": { @@ -40,6 +40,15 @@ "admin": "Administration" }, "filterEntries": "filter entries", + "autocomplete": { + "group": "Group", + "user": "User", + "noGroupOptions": "No group suggestion available", + "groupPlaceholder": "Enter group", + "noUserOptions": "No user suggestion available", + "userPlaceholder": "Enter user", + "loading": "Loading..." + }, "paginator": { "next": "Next", "previous": "Previous" diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 9a2e83f983..a312287a68 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -153,13 +153,6 @@ "roleHelpText": "READ = read; WRITE = read and write; OWNER = read, write and also the ability to manage the properties and permissions. If nothing is selected here, use the 'Advanced' Button to see detailed permissions.", "permissionsHelpText": "Use this to specify your own set of permissions regardless of predefined roles." }, - "autocomplete": { - "no-group-options": "No group suggestion available", - "group-placeholder": "Enter group", - "no-user-options": "No user suggestion available", - "user-placeholder": "Enter user", - "loading": "Loading..." - }, "advanced": { "dialog": { "title": "Advanced Permissions", diff --git a/scm-ui/src/containers/Login.js b/scm-ui/src/containers/Login.js index 4ae9b5a622..d14d9f5896 100644 --- a/scm-ui/src/containers/Login.js +++ b/scm-ui/src/containers/Login.js @@ -95,7 +95,7 @@ class Login extends React.Component { areCredentialsInvalid() { const { t, error } = this.props; if (error instanceof UnauthorizedError) { - return new Error(t("error-notification.wrong-login-credentials")); + return new Error(t("errorNotification.wrongLoginCredentials")); } else { return error; } diff --git a/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js b/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js index 112ecd1c20..efd92914d3 100644 --- a/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js +++ b/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js @@ -9,11 +9,12 @@ import type { } from "@scm-manager/ui-types"; import { Subtitle, - Autocomplete, SubmitButton, Button, LabelWithHelpIcon, - Radio + Radio, + GroupAutocomplete, + UserAutocomplete } from "@scm-manager/ui-components"; import * as validator from "../components/permissionValidation"; import RoleSelector from "../components/RoleSelector"; @@ -26,8 +27,8 @@ type Props = { createPermission: (permission: PermissionCreateEntry) => void, loading: boolean, currentPermissions: PermissionCollection, - groupAutoCompleteLink: string, - userAutoCompleteLink: string, + groupAutocompleteLink: string, + userAutocompleteLink: string, // Context props t: string => string @@ -68,65 +69,27 @@ class CreatePermissionForm extends React.Component { }); }; - loadUserAutocompletion = (inputValue: string) => { - return this.loadAutocompletion(this.props.userAutoCompleteLink, inputValue); - }; - - loadGroupAutocompletion = (inputValue: string) => { - return this.loadAutocompletion( - this.props.groupAutoCompleteLink, - inputValue - ); - }; - - loadAutocompletion(url: string, inputValue: string) { - const link = url + "?q="; - return fetch(link + inputValue) - .then(response => response.json()) - .then(json => { - return json.map(element => { - const label = element.displayName - ? `${element.displayName} (${element.id})` - : element.id; - return { - value: element, - label - }; - }); - }); - } - renderAutocompletionField = () => { - const { t } = this.props; - if (this.state.groupPermission) { + const group = this.state.groupPermission; + if (group) { return ( - ); } return ( - ); }; - groupOrUserSelected = (value: SelectValue) => { + selectName = (value: SelectValue) => { this.setState({ value, name: value.value.id, @@ -150,15 +113,17 @@ class CreatePermissionForm extends React.Component { ) : null; return ( -
+ <>
- + {advancedDialog}
@@ -201,7 +166,7 @@ class CreatePermissionForm extends React.Component { />
@@ -217,16 +182,14 @@ class CreatePermissionForm extends React.Component { - + ); } - handleDetailedPermissionsPressed = () => { - this.setState({ showAdvancedDialog: true }); - }; - - closeAdvancedPermissionsDialog = () => { - this.setState({ showAdvancedDialog: false }); + toggleAdvancedPermissionsDialog = () => { + this.setState(prevState => ({ + showAdvancedDialog: !prevState.showAdvancedDialog + })); }; submitAdvancedPermissionsDialog = (newVerbs: string[]) => { diff --git a/scm-ui/src/repos/permissions/containers/Permissions.js b/scm-ui/src/repos/permissions/containers/Permissions.js index 39bd9e37c1..ea467a56da 100644 --- a/scm-ui/src/repos/permissions/containers/Permissions.js +++ b/scm-ui/src/repos/permissions/containers/Permissions.js @@ -60,8 +60,8 @@ type Props = { repositoryRolesLink: string, repositoryVerbsLink: string, permissionsLink: string, - groupAutoCompleteLink: string, - userAutoCompleteLink: string, + groupAutocompleteLink: string, + userAutocompleteLink: string, //dispatch functions fetchAvailablePermissionsIfNeeded: ( @@ -129,8 +129,8 @@ class Permissions extends React.Component { repoName, loadingCreatePermission, hasPermissionToCreate, - userAutoCompleteLink, - groupAutoCompleteLink + userAutocompleteLink, + groupAutocompleteLink } = this.props; if (error) { return ( @@ -153,8 +153,8 @@ class Permissions extends React.Component { createPermission={permission => this.createPermission(permission)} loading={loadingCreatePermission} currentPermissions={permissions} - userAutoCompleteLink={userAutoCompleteLink} - groupAutoCompleteLink={groupAutoCompleteLink} + userAutocompleteLink={userAutocompleteLink} + groupAutocompleteLink={groupAutocompleteLink} /> ) : null; @@ -228,8 +228,8 @@ const mapStateToProps = (state, ownProps) => { const repositoryRolesLink = getRepositoryRolesLink(state); const repositoryVerbsLink = getRepositoryVerbsLink(state); const permissionsLink = getPermissionsLink(state, namespace, repoName); - const groupAutoCompleteLink = getGroupAutoCompleteLink(state); - const userAutoCompleteLink = getUserAutoCompleteLink(state); + const groupAutocompleteLink = getGroupAutoCompleteLink(state); + const userAutocompleteLink = getUserAutoCompleteLink(state); const availablePermissions = getAvailablePermissions(state); const availableRepositoryRoles = getAvailableRepositoryRoles(state); const availableVerbs = getAvailableRepositoryVerbs(state); @@ -248,8 +248,8 @@ const mapStateToProps = (state, ownProps) => { hasPermissionToCreate, loadingCreatePermission, permissionsLink, - groupAutoCompleteLink, - userAutoCompleteLink + groupAutocompleteLink, + userAutocompleteLink }; };