mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-15 17:56:17 +01:00
Added group editing feature
This commit is contained in:
@@ -10,7 +10,8 @@ import * as validator from "./groupValidation";
|
|||||||
type Props = {
|
type Props = {
|
||||||
t: string => string,
|
t: string => string,
|
||||||
submitForm: Group => void,
|
submitForm: Group => void,
|
||||||
loading?: boolean
|
loading?: boolean,
|
||||||
|
group?: Group
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@@ -36,6 +37,13 @@ class GroupForm extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { group } = this.props
|
||||||
|
if (group) {
|
||||||
|
this.setState({group: {...group}})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onSubmit = (event: Event) => {
|
onSubmit = (event: Event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.props.submitForm(this.state.group);
|
this.props.submitForm(this.state.group);
|
||||||
@@ -55,18 +63,27 @@ class GroupForm extends React.Component<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { t, loading } = this.props;
|
const { t, loading } = this.props;
|
||||||
return (
|
const { group } = this.state
|
||||||
<form onSubmit={this.onSubmit}>
|
let nameField = null;
|
||||||
|
if (!this.props.group) {
|
||||||
|
nameField = (
|
||||||
<InputField
|
<InputField
|
||||||
label={t("group.name")}
|
label={t("group.name")}
|
||||||
errorMessage="group name invalid"
|
errorMessage="group name invalid" // TODO: i18n
|
||||||
onChange={this.handleGroupNameChange}
|
onChange={this.handleGroupNameChange}
|
||||||
|
value={group.name}
|
||||||
validationError={this.state.nameValidationError}
|
validationError={this.state.nameValidationError}
|
||||||
/>
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<form onSubmit={this.onSubmit}>
|
||||||
|
{nameField}
|
||||||
<InputField
|
<InputField
|
||||||
label={t("group.description")}
|
label={t("group.description")}
|
||||||
errorMessage=""
|
errorMessage=""
|
||||||
onChange={this.handleDescriptionChange}
|
onChange={this.handleDescriptionChange}
|
||||||
|
value={group.description}
|
||||||
validationError={false}
|
validationError={false}
|
||||||
/>
|
/>
|
||||||
<SubmitButton label={t("group-form.submit")} loading={loading}/>
|
<SubmitButton label={t("group-form.submit")} loading={loading}/>
|
||||||
|
|||||||
56
scm-ui/src/groups/containers/EditGroup.js
Normal file
56
scm-ui/src/groups/containers/EditGroup.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import GroupForm from "../components/GroupForm";
|
||||||
|
import { modifyGroup } from "../modules/groups"
|
||||||
|
import type { History } from "history";
|
||||||
|
import { withRouter } from "react-router-dom";
|
||||||
|
import type { Group } from "../types/Group"
|
||||||
|
import { isModifyGroupPending, getModifyGroupFailure } from "../modules/groups"
|
||||||
|
import ErrorNotification from "../../components/ErrorNotification";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
group: Group,
|
||||||
|
modifyGroup: (group: Group, callback?: () => void) => void,
|
||||||
|
history: History,
|
||||||
|
loading?: boolean,
|
||||||
|
error: Error
|
||||||
|
};
|
||||||
|
|
||||||
|
class EditGroup extends React.Component<Props> {
|
||||||
|
groupModified = (group: Group) => {
|
||||||
|
this.props.history.push(`/group/${group.name}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyGroup = (group: Group) => {
|
||||||
|
this.props.modifyGroup(group, this.groupModified(group));
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { group, loading, error } = this.props;
|
||||||
|
return <div>
|
||||||
|
<ErrorNotification error={error} />
|
||||||
|
<GroupForm group={group} submitForm={(group) =>{this.modifyGroup(group)}} loading={loading}/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => {
|
||||||
|
const loading = isModifyGroupPending(state, ownProps.group.name)
|
||||||
|
const error = getModifyGroupFailure(state, ownProps.group.name)
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
error
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => {
|
||||||
|
return {
|
||||||
|
modifyGroup: (group: Group, callback?: () => void) => {dispatch(modifyGroup(group, callback))}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(withRouter(EditGroup));
|
||||||
@@ -18,6 +18,7 @@ import { Navigation, Section, NavLink } from "../../components/navigation";
|
|||||||
import ErrorPage from "../../components/ErrorPage";
|
import ErrorPage from "../../components/ErrorPage";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import EditGroupNavLink from "../components/navLinks/EditGroupNavLink";
|
import EditGroupNavLink from "../components/navLinks/EditGroupNavLink";
|
||||||
|
import EditGroup from "./EditGroup";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
name: string,
|
name: string,
|
||||||
@@ -74,6 +75,7 @@ class SingleGroup extends React.Component<Props> {
|
|||||||
<div className="columns">
|
<div className="columns">
|
||||||
<div className="column is-three-quarters">
|
<div className="column is-three-quarters">
|
||||||
<Route path={url} exact component={() => <Details group={group} />} />
|
<Route path={url} exact component={() => <Details group={group} />} />
|
||||||
|
<Route path={`${url}/edit`} exact component={() => <EditGroup group={group} />} />
|
||||||
</div>
|
</div>
|
||||||
<div className="column">
|
<div className="column">
|
||||||
<Navigation>
|
<Navigation>
|
||||||
|
|||||||
@@ -177,6 +177,49 @@ export function createGroupReset() {
|
|||||||
type: CREATE_GROUP_RESET
|
type: CREATE_GROUP_RESET
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// modify group
|
||||||
|
export function modifyGroup(group: Group, callback?: () => void) {
|
||||||
|
return function(dispatch: Dispatch) {
|
||||||
|
dispatch(modifyGroupPending());
|
||||||
|
return apiClient
|
||||||
|
.putWithContentType(group._links.update.href, group, CONTENT_TYPE_GROUP)
|
||||||
|
.then(() => {
|
||||||
|
dispatch(modifyGroupSuccess(group))
|
||||||
|
if (callback) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(cause => {
|
||||||
|
dispatch(modifyGroupFailure(group, new Error(`could not modify group ${group.name}: ${cause.message}`)))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function modifyGroupPending(): Action {
|
||||||
|
return {
|
||||||
|
type: MODIFY_GROUP_PENDING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function modifyGroupSuccess(group: Group): Action {
|
||||||
|
return {
|
||||||
|
type: MODIFY_GROUP_SUCCESS,
|
||||||
|
payload: group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function modifyGroupFailure(group: Group, error: Error): Action {
|
||||||
|
return {
|
||||||
|
type: MODIFY_GROUP_FAILURE,
|
||||||
|
payload: {
|
||||||
|
error,
|
||||||
|
group
|
||||||
|
},
|
||||||
|
itemId: group.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//delete group
|
//delete group
|
||||||
|
|
||||||
export function deleteGroup(group: Group, callback?: () => void) {
|
export function deleteGroup(group: Group, callback?: () => void) {
|
||||||
@@ -311,6 +354,8 @@ 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:
|
||||||
|
return reducerByName(state, action.payload.name, action.payload);
|
||||||
case DELETE_GROUP_SUCCESS:
|
case DELETE_GROUP_SUCCESS:
|
||||||
const newGroupByNames = deleteGroupInGroupsByNames(
|
const newGroupByNames = deleteGroupInGroupsByNames(
|
||||||
state,
|
state,
|
||||||
@@ -387,6 +432,14 @@ export function getCreateGroupFailure(state: Object) {
|
|||||||
return getFailure(state, CREATE_GROUP);
|
return getFailure(state, CREATE_GROUP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isModifyGroupPending(state: Object, name: string) {
|
||||||
|
return(isPending(state, MODIFY_GROUP, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getModifyGroupFailure(state: Object, name: string) {
|
||||||
|
return(getFailure(state, MODIFY_GROUP, name))
|
||||||
|
}
|
||||||
|
|
||||||
export function getGroupByName(state: Object, name: string) {
|
export function getGroupByName(state: Object, name: string) {
|
||||||
if (state.groups && state.groups.byNames) {
|
if (state.groups && state.groups.byNames) {
|
||||||
return state.groups.byNames[name];
|
return state.groups.byNames[name];
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ import reducer, {
|
|||||||
DELETE_GROUP,
|
DELETE_GROUP,
|
||||||
deleteGroupSuccess,
|
deleteGroupSuccess,
|
||||||
isDeleteGroupPending,
|
isDeleteGroupPending,
|
||||||
getDeleteGroupFailure
|
getDeleteGroupFailure,
|
||||||
|
modifyGroup,
|
||||||
|
MODIFY_GROUP_PENDING,
|
||||||
|
MODIFY_GROUP_SUCCESS,
|
||||||
|
MODIFY_GROUP_FAILURE
|
||||||
} from "./groups";
|
} from "./groups";
|
||||||
const GROUPS_URL = "/scm/api/rest/v2/groups";
|
const GROUPS_URL = "/scm/api/rest/v2/groups";
|
||||||
|
|
||||||
@@ -239,6 +243,55 @@ describe("groups fetch()", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should successfully modify group", () => {
|
||||||
|
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/groups/humanGroup", {
|
||||||
|
status: 204
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
|
||||||
|
return store.dispatch(modifyGroup(humanGroup)).then(() => {
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions[0].type).toEqual(MODIFY_GROUP_PENDING);
|
||||||
|
expect(actions[1].type).toEqual(MODIFY_GROUP_SUCCESS);
|
||||||
|
expect(actions[1].payload).toEqual(humanGroup)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should call the callback after modifying group", () => {
|
||||||
|
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/groups/humanGroup", {
|
||||||
|
status: 204
|
||||||
|
});
|
||||||
|
|
||||||
|
let called = false;
|
||||||
|
const callback = () => {
|
||||||
|
called = true;
|
||||||
|
}
|
||||||
|
const store = mockStore({});
|
||||||
|
|
||||||
|
return store.dispatch(modifyGroup(humanGroup, callback)).then(() => {
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions[0].type).toEqual(MODIFY_GROUP_PENDING);
|
||||||
|
expect(actions[1].type).toEqual(MODIFY_GROUP_SUCCESS);
|
||||||
|
expect(called).toBe(true);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should fail modifying group on HTTP 500", () => {
|
||||||
|
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/groups/humanGroup", {
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
|
||||||
|
return store.dispatch(modifyGroup(humanGroup)).then(() => {
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions[0].type).toEqual(MODIFY_GROUP_PENDING);
|
||||||
|
expect(actions[1].type).toEqual(MODIFY_GROUP_FAILURE);
|
||||||
|
expect(actions[1].payload).toBeDefined();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
it("should delete successfully group humanGroup", () => {
|
it("should delete successfully group humanGroup", () => {
|
||||||
fetchMock.deleteOnce("http://localhost:8081/scm/api/rest/v2/groups/humanGroup", {
|
fetchMock.deleteOnce("http://localhost:8081/scm/api/rest/v2/groups/humanGroup", {
|
||||||
status: 204
|
status: 204
|
||||||
|
|||||||
Reference in New Issue
Block a user