From d6eee33decdbcc002f2f6d39a680e62d93dd579b Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Wed, 21 Nov 2018 18:08:21 +0100 Subject: [PATCH] Implemented adding of not (yet) existing groups/users in autocomplete --- scm-ui/src/containers/Autocomplete.js | 37 +++-- .../AutocompleteAddEntryToTableField.js | 28 ++-- scm-ui/src/groups/components/GroupForm.js | 9 +- scm-ui/src/groups/containers/EditGroup.js | 2 +- .../components/CreatePermissionForm.js | 155 +++++++++++++----- .../permissions/containers/Permissions.js | 22 ++- 6 files changed, 178 insertions(+), 75 deletions(-) diff --git a/scm-ui/src/containers/Autocomplete.js b/scm-ui/src/containers/Autocomplete.js index 8a87f02ffd..36487242e8 100644 --- a/scm-ui/src/containers/Autocomplete.js +++ b/scm-ui/src/containers/Autocomplete.js @@ -1,54 +1,63 @@ // @flow import React from "react"; -import AsyncSelect from "react-select/lib/Async"; import { LabelWithHelpIcon } from "@scm-manager/ui-components"; +import { AsyncCreatable } from "react-select"; export type AutocompleteObject = { id: string, displayName: string }; -type SelectValue = { +export type SelectValue = { value: AutocompleteObject, label: string }; type Props = { loadSuggestions: string => Promise, - valueSelected: AutocompleteObject => void, + valueSelected: SelectValue => void, label: string, helpText?: string, - value?: AutocompleteObject + value?: SelectValue }; type State = {}; class Autocomplete extends React.Component { handleInputChange = (newValue: SelectValue) => { - this.props.valueSelected(newValue.value); + this.props.valueSelected(newValue); + }; + + isValidNewOption = (inputValue, selectValue, selectOptions) => { + //TODO: types + const isNotDuplicated = !selectOptions + .map(option => option.label) + .includes(inputValue); + const isNotEmpty = inputValue !== ""; + return isNotEmpty && isNotDuplicated; }; render() { const { label, helpText, value } = this.props; - let selectValue = null; - if (value) { - selectValue = { - value, - label: value.displayName - }; - } return (
- <>Loading...} // TODO: i18n noOptionsMessage={() => <>No suggestion available} // TODO: i18n + isValidNewOption={this.isValidNewOption} + onCreateOption={value => { + this.handleInputChange({ + label: value, + value: { id: value, displayName: value } + }); + }} />
diff --git a/scm-ui/src/groups/components/AutocompleteAddEntryToTableField.js b/scm-ui/src/groups/components/AutocompleteAddEntryToTableField.js index 36dcebf777..405370d53c 100644 --- a/scm-ui/src/groups/components/AutocompleteAddEntryToTableField.js +++ b/scm-ui/src/groups/components/AutocompleteAddEntryToTableField.js @@ -3,10 +3,13 @@ import React from "react"; import { AddButton } from "@scm-manager/ui-components"; import Autocomplete from "../../containers/Autocomplete"; -import type { AutocompleteObject } from "../../containers/Autocomplete"; +import type { + AutocompleteObject, + SelectValue +} from "../../containers/Autocomplete"; type Props = { - addEntry: string => void, + addEntry: SelectValue => void, disabled: boolean, buttonLabel: string, fieldLabel: string, @@ -15,18 +18,18 @@ type Props = { }; type State = { - entryToAdd?: AutocompleteObject + selectedValue?: SelectValue }; class AutocompleteAddEntryToTableField extends React.Component { constructor(props: Props) { super(props); - this.state = { entryToAdd: undefined }; + this.state = { selectedValue: undefined }; } render() { const { disabled, buttonLabel, fieldLabel, helpText } = this.props; - const { entryToAdd } = this.state; + const { selectedValue } = this.state; return (
{ loadSuggestions={this.props.loadSuggestions} valueSelected={this.handleAddEntryChange} helpText={helpText} - value={entryToAdd} + value={selectedValue} /> { }; appendEntry = () => { - const { entryToAdd } = this.state; - if (!entryToAdd) { + const { selectedValue } = this.state; + if (!selectedValue) { return; } - this.setState({ ...this.state, entryToAdd: undefined }, () => - this.props.addEntry(entryToAdd.id) + // $FlowFixMe null is needed to clear the selection; undefined does not work + this.setState({ ...this.state, selectedValue: null }, () => + this.props.addEntry(selectedValue) ); }; - handleAddEntryChange = (selection: AutocompleteObject) => { + handleAddEntryChange = (selection: SelectValue) => { this.setState({ ...this.state, - entryToAdd: selection + selectedValue: selection }); }; } diff --git a/scm-ui/src/groups/components/GroupForm.js b/scm-ui/src/groups/components/GroupForm.js index f04ddc1f5a..77f0314c92 100644 --- a/scm-ui/src/groups/components/GroupForm.js +++ b/scm-ui/src/groups/components/GroupForm.js @@ -7,6 +7,7 @@ import type { Group } from "@scm-manager/ui-types"; import * as validator from "./groupValidation"; import MemberNameTable from "./MemberNameTable"; import AutocompleteAddEntryToTableField from "./AutocompleteAddEntryToTableField"; +import type { SelectValue } from "../../containers/Autocomplete"; type Props = { t: string => string, @@ -94,7 +95,7 @@ class GroupForm extends React.Component { helpText={t("group-form.help.descriptionHelpText")} /> @@ -125,8 +126,8 @@ class GroupForm extends React.Component { }); }; - addMember = (membername: string) => { - if (this.isMember(membername)) { + addMember = (value: SelectValue) => { + if (this.isMember(value.value.id)) { return; } @@ -134,7 +135,7 @@ class GroupForm extends React.Component { ...this.state, group: { ...this.state.group, - members: [...this.state.group.members, membername] + members: [...this.state.group.members, value.value.id] } }); }; diff --git a/scm-ui/src/groups/containers/EditGroup.js b/scm-ui/src/groups/containers/EditGroup.js index 223ea1eef6..e063f53838 100644 --- a/scm-ui/src/groups/containers/EditGroup.js +++ b/scm-ui/src/groups/containers/EditGroup.js @@ -46,7 +46,7 @@ class EditGroup extends React.Component { .then(json => { return json.map(element => { return { - value: element, + value: element.id, label: `${element.displayName} (${element.id})` }; }); diff --git a/scm-ui/src/repos/permissions/components/CreatePermissionForm.js b/scm-ui/src/repos/permissions/components/CreatePermissionForm.js index a674f6b41f..35476c2ff1 100644 --- a/scm-ui/src/repos/permissions/components/CreatePermissionForm.js +++ b/scm-ui/src/repos/permissions/components/CreatePermissionForm.js @@ -1,23 +1,31 @@ // @flow import React from "react"; -import {translate} from "react-i18next"; -import {Checkbox, InputField, SubmitButton} from "@scm-manager/ui-components"; +import { translate } from "react-i18next"; +import { SubmitButton } from "@scm-manager/ui-components"; import TypeSelector from "./TypeSelector"; -import type {PermissionCollection, PermissionCreateEntry} from "@scm-manager/ui-types"; +import type { + PermissionCollection, + PermissionCreateEntry +} from "@scm-manager/ui-types"; import * as validator from "./permissionValidation"; +import Autocomplete from "../../../containers/Autocomplete"; +import type { SelectValue } from "../../../containers/Autocomplete"; type Props = { t: string => string, createPermission: (permission: PermissionCreateEntry) => void, loading: boolean, - currentPermissions: PermissionCollection + currentPermissions: PermissionCollection, + groupAutoCompleteLink: string, + userAutoCompleteLink: string }; type State = { name: string, type: string, groupPermission: boolean, - valid: boolean + valid: boolean, + value?: SelectValue }; class CreatePermissionForm extends React.Component { @@ -28,12 +36,88 @@ class CreatePermissionForm extends React.Component { name: "", type: "READ", groupPermission: false, - valid: true + valid: true, + value: undefined }; } + permissionScopeChanged = event => { + const groupPermission = event.target.value === "GROUP_PERMISSION"; + this.setState({ + groupPermission: groupPermission, + valid: validator.isPermissionValid( + this.state.name, + groupPermission, + this.props.currentPermissions + ) + }); + this.setState({ ...this.state, groupPermission }); + }; + + loadUserAutocompletion = (inputValue: string) => { + const url = this.props.userAutoCompleteLink + "?q="; + return fetch(url + inputValue) + .then(response => response.json()) + .then(json => { + return json.map(element => { + return { + value: element, + label: `${element.displayName} (${element.id})` + }; + }); + }); + }; + + loadGroupAutocompletion = (inputValue: string) => { + const url = this.props.groupAutoCompleteLink + "?q="; + return fetch(url + inputValue) + .then(response => response.json()) + .then(json => { + return json.map(element => { + return { + value: element, + label: `${element.displayName} (${element.id})` + }; + }); + }); + }; + renderAutocompletionField = () => { + if (this.state.groupPermission) { + return ( + + ); + } + return ( + + ); + }; + + groupOrUserSelected = (value: SelectValue) => { + console.log(value); + this.setState({ + value, + name: value.value.id, + valid: validator.isPermissionValid( + value.value.id, + this.state.groupPermission, + this.props.currentPermissions + ) + }); + }; + render() { const { t, loading } = this.props; + const { name, type, groupPermission } = this.state; return ( @@ -42,20 +126,30 @@ class CreatePermissionForm extends React.Component { {t("permission.add-permission.add-permission-heading")}
- - +
+ + +
+ {this.renderAutocompletionField()} + { type: type }); }; - - handleNameChange = (name: string) => { - this.setState({ - name: name, - valid: validator.isPermissionValid( - name, - this.state.groupPermission, - this.props.currentPermissions - ) - }); - }; - handleGroupPermissionChange = (groupPermission: boolean) => { - this.setState({ - groupPermission: groupPermission, - valid: validator.isPermissionValid( - this.state.name, - groupPermission, - this.props.currentPermissions - ) - }); - }; } export default translate("repos")(CreatePermissionForm); diff --git a/scm-ui/src/repos/permissions/containers/Permissions.js b/scm-ui/src/repos/permissions/containers/Permissions.js index ee9ac281a5..1a8d6b9f3a 100644 --- a/scm-ui/src/repos/permissions/containers/Permissions.js +++ b/scm-ui/src/repos/permissions/containers/Permissions.js @@ -27,6 +27,10 @@ import SinglePermission from "./SinglePermission"; import CreatePermissionForm from "../components/CreatePermissionForm"; import type { History } from "history"; import { getPermissionsLink } from "../../modules/repos"; +import { + getGroupAutoCompleteLink, + getUserAutoCompleteLink +} from "../../../modules/indexResource"; type Props = { namespace: string, @@ -37,6 +41,8 @@ type Props = { hasPermissionToCreate: boolean, loadingCreatePermission: boolean, permissionsLink: string, + groupAutoCompleteLink: string, + userAutoCompleteLink: string, //dispatch functions fetchPermissions: (link: string, namespace: string, repoName: string) => void, @@ -92,7 +98,9 @@ class Permissions extends React.Component { namespace, repoName, loadingCreatePermission, - hasPermissionToCreate + hasPermissionToCreate, + userAutoCompleteLink, + groupAutoCompleteLink } = this.props; if (error) { return ( @@ -113,6 +121,8 @@ class Permissions extends React.Component { createPermission={permission => this.createPermission(permission)} loading={loadingCreatePermission} currentPermissions={permissions} + userAutoCompleteLink={userAutoCompleteLink} + groupAutoCompleteLink={groupAutoCompleteLink} /> ) : null; @@ -165,6 +175,8 @@ const mapStateToProps = (state, ownProps) => { ); const hasPermissionToCreate = hasCreatePermission(state, namespace, repoName); const permissionsLink = getPermissionsLink(state, namespace, repoName); + const groupAutoCompleteLink = getGroupAutoCompleteLink(state); + const userAutoCompleteLink = getUserAutoCompleteLink(state); return { namespace, repoName, @@ -173,7 +185,9 @@ const mapStateToProps = (state, ownProps) => { permissions, hasPermissionToCreate, loadingCreatePermission, - permissionsLink + permissionsLink, + groupAutoCompleteLink, + userAutoCompleteLink }; }; @@ -189,7 +203,9 @@ const mapDispatchToProps = dispatch => { repoName: string, callback?: () => void ) => { - dispatch(createPermission(link, permission, namespace, repoName, callback)); + dispatch( + createPermission(link, permission, namespace, repoName, callback) + ); }, createPermissionReset: (namespace: string, repoName: string) => { dispatch(createPermissionReset(namespace, repoName));