mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-17 18:51:10 +01:00
Merged heads
This commit is contained in:
@@ -29,15 +29,15 @@
|
|||||||
"edit-group-button": {
|
"edit-group-button": {
|
||||||
"label": "Edit"
|
"label": "Edit"
|
||||||
},
|
},
|
||||||
"add-user-button": {
|
"add-member-button": {
|
||||||
"label": "Add user"
|
"label": "Add member"
|
||||||
},
|
},
|
||||||
"remove-user-button": {
|
"remove-member-button": {
|
||||||
"label": "Remove user"
|
"label": "Remove member"
|
||||||
},
|
},
|
||||||
"add-user-textfield": {
|
"add-member-textfield": {
|
||||||
"label": "Add user",
|
"label": "Add member",
|
||||||
"error": "Username invalid"
|
"error": "Invalid member name"
|
||||||
},
|
},
|
||||||
"group-form": {
|
"group-form": {
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
|
|||||||
@@ -8,19 +8,19 @@ import { isMemberNameValid } from "./groupValidation"
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
t: string => string,
|
t: string => string,
|
||||||
addUser: string => void
|
addMember: string => void
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
userToAdd: string,
|
memberToAdd: string,
|
||||||
validationError: boolean
|
validationError: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
class AddUserField extends React.Component<Props, State> {
|
class AddMemberField extends React.Component<Props, State> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
userToAdd: "",
|
memberToAdd: "",
|
||||||
validationError: false
|
validationError: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -30,16 +30,15 @@ class AddUserField extends React.Component<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<InputField
|
<InputField
|
||||||
label={t("add-user-textfield.label")}
|
label={t("add-member-textfield.label")}
|
||||||
errorMessage={t("add-user-textfield.error")}
|
errorMessage={t("add-member-textfield.error")}
|
||||||
onChange={this.handleAddUserChange}
|
onChange={this.handleAddMemberChange}
|
||||||
validationError={this.state.validationError}
|
validationError={this.state.validationError} //TODO: validate member name
|
||||||
value={this.state.userToAdd}
|
value={this.state.memberToAdd}
|
||||||
onReturnPressed={this.appendMember}
|
onReturnPressed={this.appendMember}
|
||||||
/>
|
/>
|
||||||
<AddButton
|
<AddButton
|
||||||
|
label={t("add-member-button.label")}
|
||||||
label={t("add-user-button.label")}
|
|
||||||
action={this.addButtonClicked}
|
action={this.addButtonClicked}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,13 +52,14 @@ class AddUserField extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
appendMember = () => {
|
appendMember = () => {
|
||||||
this.props.addUser(this.state.userToAdd);
|
this.props.addMember(this.state.memberToAdd);
|
||||||
this.setState({ ...this.state, userToAdd: "" });
|
this.setState({ ...this.state, memberToAdd: "" });
|
||||||
}
|
};
|
||||||
|
|
||||||
handleAddUserChange = (username: string) => {
|
|
||||||
this.setState({ ...this.state, userToAdd: username, validationError: !isMemberNameValid(username)});
|
handleAddMemberChange = (membername: string) => {
|
||||||
|
this.setState({ ...this.state, memberToAdd: membername, validationError: !isMemberNameValid(membername) });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translate("groups")(AddUserField);
|
export default translate("groups")(AddMemberField);
|
||||||
@@ -6,8 +6,8 @@ import { SubmitButton } from "../../components/buttons";
|
|||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import type { Group } from "../types/Group";
|
import type { Group } from "../types/Group";
|
||||||
import * as validator from "./groupValidation";
|
import * as validator from "./groupValidation";
|
||||||
import AddUserField from "./AddUserField";
|
import AddMemberField from "./AddMemberField";
|
||||||
import UserNameTable from "./UserNameTable";
|
import MemberNameTable from "./MemberNameTable";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
t: string => string,
|
t: string => string,
|
||||||
@@ -95,11 +95,11 @@ class GroupForm extends React.Component<Props, State> {
|
|||||||
value={group.description}
|
value={group.description}
|
||||||
validationError={false}
|
validationError={false}
|
||||||
/>
|
/>
|
||||||
<UserNameTable
|
<MemberNameTable
|
||||||
users={this.state.group.members}
|
members={this.state.group.members}
|
||||||
userListChanged={this.userListChanged}
|
memberListChanged={this.memberListChanged}
|
||||||
/>
|
/>
|
||||||
<AddUserField addUser={this.addUser} />
|
<AddMemberField addMember={this.addMember} />
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
disabled={!this.isValid()}
|
disabled={!this.isValid()}
|
||||||
label={t("group-form.submit")}
|
label={t("group-form.submit")}
|
||||||
@@ -109,19 +109,18 @@ class GroupForm extends React.Component<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
userListChanged = usernames => {
|
memberListChanged = membernames => {
|
||||||
this.setState({
|
this.setState({
|
||||||
...this.state,
|
...this.state,
|
||||||
group: {
|
group: {
|
||||||
...this.state.group,
|
...this.state.group,
|
||||||
members: usernames
|
members: membernames
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
|
addMember = (membername: string) => {
|
||||||
addUser = (username: string) => {
|
if (this.isMember(membername)) {
|
||||||
if (this.isMember(username)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,13 +128,13 @@ class GroupForm extends React.Component<Props, State> {
|
|||||||
...this.state,
|
...this.state,
|
||||||
group: {
|
group: {
|
||||||
...this.state.group,
|
...this.state.group,
|
||||||
members: [...this.state.group.members, username]
|
members: [...this.state.group.members, membername]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
isMember = (username: string) => {
|
isMember = (membername: string) => {
|
||||||
return this.state.group.members.includes(username);
|
return this.state.group.members.includes(membername);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleGroupNameChange = (name: string) => {
|
handleGroupNameChange = (name: string) => {
|
||||||
|
|||||||
47
scm-ui/src/groups/components/MemberNameTable.js
Normal file
47
scm-ui/src/groups/components/MemberNameTable.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import RemoveMemberButton from "./buttons/RemoveMemberButton";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
members: string[],
|
||||||
|
t: string => string,
|
||||||
|
memberListChanged: (string[]) => void
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {};
|
||||||
|
|
||||||
|
class MemberNameTable extends React.Component<Props, State> {
|
||||||
|
render() {
|
||||||
|
const { t } = this.props;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<label className="label">{t("group.members")}</label>
|
||||||
|
<table className="table is-hoverable is-fullwidth">
|
||||||
|
<tbody>
|
||||||
|
{this.props.members.map((member, index) => {
|
||||||
|
return (
|
||||||
|
<tr key={member}>
|
||||||
|
<td key={member}>{member}</td>
|
||||||
|
<td>
|
||||||
|
<RemoveMemberButton
|
||||||
|
membername={member}
|
||||||
|
removeMember={this.removeMember}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMember = (membername: string) => {
|
||||||
|
const newMembers = this.props.members.filter(name => name !== membername);
|
||||||
|
this.props.memberListChanged(newMembers);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate("groups")(MemberNameTable);
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
//@flow
|
|
||||||
import React from "react";
|
|
||||||
import { translate } from "react-i18next"
|
|
||||||
import RemoveUserButton from "./buttons/RemoveUserButton";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
users: string[];
|
|
||||||
t: string => string,
|
|
||||||
userListChanged: (string[]) => void
|
|
||||||
};
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class UserNameTable extends React.Component<Props, State> {
|
|
||||||
render() {
|
|
||||||
const { t } = this.props;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<label className="label">{t("group.members")}</label>
|
|
||||||
<table className="table is-hoverable is-fullwidth">
|
|
||||||
<tbody>
|
|
||||||
{this.props.users.map((user, index) => {
|
|
||||||
return (
|
|
||||||
<tr key={user}>
|
|
||||||
<td key={user}>{user}</td>
|
|
||||||
<td>
|
|
||||||
<RemoveUserButton username={user} removeUser={this.removeUser} />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeUser = (username: string) => {
|
|
||||||
const newUsers = this.props.users.filter(name => name !== username);
|
|
||||||
this.props.userListChanged(newUsers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default translate("groups")(UserNameTable);
|
|
||||||
@@ -6,24 +6,24 @@ import classNames from "classnames";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
t: string => string,
|
t: string => string,
|
||||||
username: string,
|
membername: string,
|
||||||
removeUser: string => void
|
removeMember: string => void
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {};
|
type State = {};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RemoveUserButton extends React.Component<Props, State> {
|
class RemoveMemberButton extends React.Component<Props, State> {
|
||||||
render() {
|
render() {
|
||||||
const { t , username, removeUser} = this.props;
|
const { t , membername, removeMember} = this.props;
|
||||||
return (
|
return (
|
||||||
<div className={classNames("is-pulled-right")}>
|
<div className={classNames("is-pulled-right")}>
|
||||||
<DeleteButton
|
<DeleteButton
|
||||||
label={t("remove-user-button.label")}
|
label={t("remove-member-button.label")}
|
||||||
action={(event: Event) => {
|
action={(event: Event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
removeUser(username);
|
removeMember(membername);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -31,4 +31,4 @@ class RemoveUserButton extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translate("groups")(RemoveUserButton);
|
export default translate("groups")(RemoveMemberButton);
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import type { User } from "../../../users/types/User";
|
import type { Member } from "../../types/Group";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
member: User
|
member: Member
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class GroupMember extends React.Component<Props> {
|
export default class GroupMember extends React.Component<Props> {
|
||||||
@@ -12,7 +12,7 @@ export default class GroupMember extends React.Component<Props> {
|
|||||||
return <Link to={to}>{label}</Link>;
|
return <Link to={to}>{label}</Link>;
|
||||||
}
|
}
|
||||||
|
|
||||||
showName(to: any, member: User) {
|
showName(to: any, member: Member) {
|
||||||
if (member._links.self) {
|
if (member._links.self) {
|
||||||
return this.renderLink(to, member.name);
|
return this.renderLink(to, member.name);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @flow
|
||||||
import { apiClient } from "../../apiclient";
|
import { apiClient } from "../../apiclient";
|
||||||
import { isPending } from "../../modules/pending";
|
import { isPending } from "../../modules/pending";
|
||||||
import { getFailure } from "../../modules/failure";
|
import { getFailure } from "../../modules/failure";
|
||||||
@@ -5,7 +6,7 @@ import * as types from "../../modules/types";
|
|||||||
import { combineReducers, Dispatch } from "redux";
|
import { combineReducers, Dispatch } from "redux";
|
||||||
import type { Action } from "../../types/Action";
|
import type { Action } from "../../types/Action";
|
||||||
import type { PagedCollection } from "../../types/Collection";
|
import type { PagedCollection } from "../../types/Collection";
|
||||||
import type { Groups } from "../types/Groups";
|
import type { Group } from "../types/Group";
|
||||||
|
|
||||||
export const FETCH_GROUPS = "scm/groups/FETCH_GROUPS";
|
export const FETCH_GROUPS = "scm/groups/FETCH_GROUPS";
|
||||||
export const FETCH_GROUPS_PENDING = `${FETCH_GROUPS}_${types.PENDING_SUFFIX}`;
|
export const FETCH_GROUPS_PENDING = `${FETCH_GROUPS}_${types.PENDING_SUFFIX}`;
|
||||||
@@ -139,10 +140,11 @@ export function createGroup(group: Group, callback?: () => void) {
|
|||||||
return apiClient
|
return apiClient
|
||||||
.postWithContentType(GROUPS_URL, group, CONTENT_TYPE_GROUP)
|
.postWithContentType(GROUPS_URL, group, CONTENT_TYPE_GROUP)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
dispatch(createGroupSuccess())
|
dispatch(createGroupSuccess());
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
}})
|
}
|
||||||
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
dispatch(
|
dispatch(
|
||||||
createGroupFailure(
|
createGroupFailure(
|
||||||
@@ -175,24 +177,29 @@ export function createGroupFailure(error: Error) {
|
|||||||
export function createGroupReset() {
|
export function createGroupReset() {
|
||||||
return {
|
return {
|
||||||
type: CREATE_GROUP_RESET
|
type: CREATE_GROUP_RESET
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// modify group
|
// modify group
|
||||||
export function modifyGroup(group: Group, callback?: () => void) {
|
export function modifyGroup(group: Group, callback?: () => void) {
|
||||||
return function(dispatch: Dispatch) {
|
return function(dispatch: Dispatch) {
|
||||||
dispatch(modifyGroupPending(group));
|
dispatch(modifyGroupPending(group));
|
||||||
return apiClient
|
return apiClient
|
||||||
.putWithContentType(group._links.update.href, group, CONTENT_TYPE_GROUP)
|
.putWithContentType(group._links.update.href, group, CONTENT_TYPE_GROUP)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
dispatch(modifyGroupSuccess(group))
|
dispatch(modifyGroupSuccess(group));
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback()
|
callback();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(cause => {
|
.catch(cause => {
|
||||||
dispatch(modifyGroupFailure(group, new Error(`could not modify group ${group.name}: ${cause.message}`)))
|
dispatch(
|
||||||
})
|
modifyGroupFailure(
|
||||||
|
group,
|
||||||
|
new Error(`could not modify group ${group.name}: ${cause.message}`)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +208,7 @@ export function modifyGroupPending(group: Group): Action {
|
|||||||
type: MODIFY_GROUP_PENDING,
|
type: MODIFY_GROUP_PENDING,
|
||||||
payload: group,
|
payload: group,
|
||||||
itemId: group.name
|
itemId: group.name
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function modifyGroupSuccess(group: Group): Action {
|
export function modifyGroupSuccess(group: Group): Action {
|
||||||
@@ -209,7 +216,7 @@ export function modifyGroupSuccess(group: Group): Action {
|
|||||||
type: MODIFY_GROUP_SUCCESS,
|
type: MODIFY_GROUP_SUCCESS,
|
||||||
payload: group,
|
payload: group,
|
||||||
itemId: group.name
|
itemId: group.name
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function modifyGroupFailure(group: Group, error: Error): Action {
|
export function modifyGroupFailure(group: Group, error: Error): Action {
|
||||||
@@ -220,7 +227,7 @@ export function modifyGroupFailure(group: Group, error: Error): Action {
|
|||||||
group
|
group
|
||||||
},
|
},
|
||||||
itemId: group.name
|
itemId: group.name
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//delete group
|
//delete group
|
||||||
@@ -274,7 +281,7 @@ export function deleteGroupFailure(group: Group, error: Error): Action {
|
|||||||
|
|
||||||
//reducer
|
//reducer
|
||||||
function extractGroupsByNames(
|
function extractGroupsByNames(
|
||||||
groups: Groups[],
|
groups: Group[],
|
||||||
groupNames: string[],
|
groupNames: string[],
|
||||||
oldGroupsByNames: Object
|
oldGroupsByNames: Object
|
||||||
) {
|
) {
|
||||||
@@ -332,14 +339,14 @@ function listReducer(state: any = {}, action: any = {}) {
|
|||||||
};
|
};
|
||||||
// Delete single group actions
|
// Delete single group actions
|
||||||
case DELETE_GROUP_SUCCESS:
|
case DELETE_GROUP_SUCCESS:
|
||||||
const newGroupEntries = deleteGroupInEntries(
|
const newGroupEntries = deleteGroupInEntries(
|
||||||
state.entries,
|
state.entries,
|
||||||
action.payload.name
|
action.payload.name
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
entries: newGroupEntries
|
entries: newGroupEntries
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@@ -357,7 +364,7 @@ function byNamesReducer(state: any = {}, action: any = {}) {
|
|||||||
};
|
};
|
||||||
case FETCH_GROUP_SUCCESS:
|
case FETCH_GROUP_SUCCESS:
|
||||||
return reducerByName(state, action.payload.name, action.payload);
|
return reducerByName(state, action.payload.name, action.payload);
|
||||||
case MODIFY_GROUP_SUCCESS:
|
case MODIFY_GROUP_SUCCESS:
|
||||||
return reducerByName(state, action.payload.name, action.payload);
|
return reducerByName(state, action.payload.name, action.payload);
|
||||||
case DELETE_GROUP_SUCCESS:
|
case DELETE_GROUP_SUCCESS:
|
||||||
const newGroupByNames = deleteGroupInGroupsByNames(
|
const newGroupByNames = deleteGroupInGroupsByNames(
|
||||||
@@ -436,11 +443,11 @@ export function getCreateGroupFailure(state: Object) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isModifyGroupPending(state: Object, name: string) {
|
export function isModifyGroupPending(state: Object, name: string) {
|
||||||
return(isPending(state, MODIFY_GROUP, name))
|
return isPending(state, MODIFY_GROUP, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getModifyGroupFailure(state: Object, name: string) {
|
export function getModifyGroupFailure(state: Object, name: string) {
|
||||||
return(getFailure(state, MODIFY_GROUP, name))
|
return getFailure(state, MODIFY_GROUP, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getGroupByName(state: Object, name: string) {
|
export function getGroupByName(state: Object, name: string) {
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
//@flow
|
//@flow
|
||||||
|
import type { Collection } from "../../types/Collection";
|
||||||
import type { Links } from "../../types/hal";
|
import type { Links } from "../../types/hal";
|
||||||
import type { User } from "../../users/types/User";
|
|
||||||
|
|
||||||
export type Group = {
|
export type Member = {
|
||||||
|
name: string,
|
||||||
|
_links: Links
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Group = Collection & {
|
||||||
name: string,
|
name: string,
|
||||||
description: string,
|
description: string,
|
||||||
type: string,
|
type: string,
|
||||||
members: string[],
|
members: string[],
|
||||||
_links: Links,
|
|
||||||
_embedded: {
|
_embedded: {
|
||||||
members: User[]
|
members: Member[]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user