Added group editing feature

This commit is contained in:
Philipp Czora
2018-08-02 11:38:08 +02:00
parent df11cdc332
commit ed3b57b818
5 changed files with 190 additions and 9 deletions

View File

@@ -10,7 +10,8 @@ import * as validator from "./groupValidation";
type Props = {
t: string => string,
submitForm: Group => void,
loading?: boolean
loading?: boolean,
group?: Group
};
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) => {
event.preventDefault();
this.props.submitForm(this.state.group);
@@ -55,18 +63,27 @@ class GroupForm extends React.Component<Props, State> {
render() {
const { t, loading } = this.props;
return (
<form onSubmit={this.onSubmit}>
const { group } = this.state
let nameField = null;
if (!this.props.group) {
nameField = (
<InputField
label={t("group.name")}
errorMessage="group name invalid"
errorMessage="group name invalid" // TODO: i18n
onChange={this.handleGroupNameChange}
value={group.name}
validationError={this.state.nameValidationError}
/>
);
}
return (
<form onSubmit={this.onSubmit}>
{nameField}
<InputField
label={t("group.description")}
errorMessage=""
onChange={this.handleDescriptionChange}
value={group.description}
validationError={false}
/>
<SubmitButton label={t("group-form.submit")} loading={loading}/>

View 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));

View File

@@ -18,6 +18,7 @@ import { Navigation, Section, NavLink } from "../../components/navigation";
import ErrorPage from "../../components/ErrorPage";
import { translate } from "react-i18next";
import EditGroupNavLink from "../components/navLinks/EditGroupNavLink";
import EditGroup from "./EditGroup";
type Props = {
name: string,
@@ -74,6 +75,7 @@ class SingleGroup extends React.Component<Props> {
<div className="columns">
<div className="column is-three-quarters">
<Route path={url} exact component={() => <Details group={group} />} />
<Route path={`${url}/edit`} exact component={() => <EditGroup group={group} />} />
</div>
<div className="column">
<Navigation>

View File

@@ -177,6 +177,49 @@ export function createGroupReset() {
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
export function deleteGroup(group: Group, callback?: () => void) {
@@ -311,6 +354,8 @@ function byNamesReducer(state: any = {}, action: any = {}) {
};
case FETCH_GROUP_SUCCESS:
return reducerByName(state, action.payload.name, action.payload);
case MODIFY_GROUP_SUCCESS:
return reducerByName(state, action.payload.name, action.payload);
case DELETE_GROUP_SUCCESS:
const newGroupByNames = deleteGroupInGroupsByNames(
state,
@@ -387,6 +432,14 @@ export function getCreateGroupFailure(state: Object) {
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) {
if (state.groups && state.groups.byNames) {
return state.groups.byNames[name];

View File

@@ -38,7 +38,11 @@ import reducer, {
DELETE_GROUP,
deleteGroupSuccess,
isDeleteGroupPending,
getDeleteGroupFailure
getDeleteGroupFailure,
modifyGroup,
MODIFY_GROUP_PENDING,
MODIFY_GROUP_SUCCESS,
MODIFY_GROUP_FAILURE
} from "./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", () => {
fetchMock.deleteOnce("http://localhost:8081/scm/api/rest/v2/groups/humanGroup", {
status: 204