Merged heads

This commit is contained in:
Philipp Czora
2018-07-26 09:52:21 +02:00
5 changed files with 130 additions and 71 deletions

View File

@@ -1,10 +1,9 @@
// @flow
import React from "react";
import { translate } from "react-i18next";
import type { User } from "../types/User";
import { InputField, Checkbox } from "../../components/forms";
import { SubmitButton } from "../../components/buttons";
import Loading from "../../components/Loading";
import {translate} from "react-i18next";
import type {User} from "../types/User";
import {Checkbox, InputField} from "../../components/forms";
import {SubmitButton} from "../../components/buttons";
type Props = {
submitForm: User => void,

View File

@@ -1,27 +1,38 @@
//@flow
import React from "react";
import { connect } from "react-redux";
import {connect} from "react-redux";
import {withRouter} from "react-router-dom";
import UserForm from "./../components/UserForm";
import type { User } from "../types/User";
import { modifyUser } from "../modules/users";
import type {User} from "../types/User";
import {modifyUser} from "../modules/users";
import type {History} from "history";
type Props = {
user: User,
updateUser: User => void,
loading: boolean
modifyUser: (user: User, callback?: () => void) => void,
loading: boolean,
history: History
};
class EditUser extends React.Component<Props> {
userModified = (user: User) => () => {
this.props.history.push(`/user/${user.name}`);
};
modifyUser = (user: User) => {
this.props.modifyUser(user, this.userModified(user));
};
render() {
const { user, updateUser } = this.props;
return <UserForm submitForm={user => updateUser(user)} user={user} />;
const { user } = this.props;
return <UserForm submitForm={user => this.modifyUser(user)} user={user} />;
}
}
const mapDispatchToProps = dispatch => {
return {
updateUser: (user: User) => {
dispatch(modifyUser(user));
modifyUser: (user: User, callback?: () => void) => {
dispatch(modifyUser(user, callback));
}
};
};
@@ -33,4 +44,4 @@ const mapStateToProps = (state, ownProps) => {
export default connect(
mapStateToProps,
mapDispatchToProps
)(EditUser);
)(withRouter(EditUser));

View File

@@ -1,25 +1,27 @@
//@flow
import React from "react";
import { connect } from "react-redux";
import { Page } from "../../components/layout";
import { Route } from "react-router";
import { Details } from "./../components/table";
import {connect} from "react-redux";
import {Page} from "../../components/layout";
import {Route} from "react-router";
import {Details} from "./../components/table";
import EditUser from "./EditUser";
import type { User } from "../types/User";
import type { UserEntry } from "../types/UserEntry";
import { fetchUser, deleteUser } from "../modules/users";
import type {User} from "../types/User";
import type {UserEntry} from "../types/UserEntry";
import type {History} from "history";
import {deleteUser, fetchUser} from "../modules/users";
import Loading from "../../components/Loading";
import { Navigation, Section, NavLink } from "../../components/navigation";
import { DeleteUserButton } from "./../components/buttons";
import {Navigation, NavLink, Section} from "../../components/navigation";
import {DeleteUserButton} from "./../components/buttons";
import ErrorPage from "../../components/ErrorPage";
type Props = {
name: string,
userEntry?: UserEntry,
match: any,
deleteUser: (user: User) => void,
fetchUser: string => void
deleteUser: (user: User, callback?: () => void) => void,
fetchUser: string => void,
history: History
};
class SingleUser extends React.Component<Props> {
@@ -27,6 +29,14 @@ class SingleUser extends React.Component<Props> {
this.props.fetchUser(this.props.name);
}
userDeleted = () => {
this.props.history.push("/users");
};
deleteUser = (user: User) => {
this.props.deleteUser(user, this.userDeleted);
};
stripEndingSlash = (url: string) => {
if (url.endsWith("/")) {
return url.substring(0, url.length - 2);
@@ -34,8 +44,12 @@ class SingleUser extends React.Component<Props> {
return url;
};
matchedUrl = () => {
return this.stripEndingSlash(this.props.match.url);
};
render() {
const { userEntry, match, deleteUser } = this.props;
const { userEntry } = this.props;
if (!userEntry || userEntry.loading) {
return <Loading />;
@@ -52,7 +66,7 @@ class SingleUser extends React.Component<Props> {
}
const user = userEntry.entry;
const url = this.stripEndingSlash(match.url);
const url = this.matchedUrl();
// TODO i18n
@@ -73,7 +87,7 @@ class SingleUser extends React.Component<Props> {
<NavLink to={`${url}/edit`} label="Edit" />
</Section>
<Section label="Actions">
<DeleteUserButton user={user} deleteUser={deleteUser} />
<DeleteUserButton user={user} deleteUser={this.deleteUser} />
<NavLink to="/users" label="Back" />
</Section>
</Navigation>
@@ -102,8 +116,8 @@ const mapDispatchToProps = dispatch => {
fetchUser: (name: string) => {
dispatch(fetchUser(name));
},
deleteUser: (user: User) => {
dispatch(deleteUser(user));
deleteUser: (user: User, callback?: () => void) => {
dispatch(deleteUser(user, callback));
}
};
};

View File

@@ -1,9 +1,9 @@
// @flow
import { apiClient } from "../../apiclient";
import type { User } from "../types/User";
import type { UserEntry } from "../types/UserEntry";
import { Dispatch, combineReducers } from "redux";
import type { Action } from "../../types/Action";
import {apiClient} from "../../apiclient";
import type {User} from "../types/User";
import type {UserEntry} from "../types/UserEntry";
import {combineReducers, Dispatch} from "redux";
import type {Action} from "../../types/Action";
export const FETCH_USERS_PENDING = "scm/users/FETCH_USERS_PENDING";
export const FETCH_USERS_SUCCESS = "scm/users/FETCH_USERS_SUCCESS";
@@ -173,13 +173,16 @@ export function createUserFailure(user: User, err: Error): Action {
//modify user
export function modifyUser(user: User) {
export function modifyUser(user: User, callback?: () => void) {
return function(dispatch: Dispatch) {
dispatch(modifyUserPending(user));
return apiClient
.putWithContentType(user._links.update.href, user, CONTENT_TYPE_USER)
.then(() => {
dispatch(modifyUserSuccess(user));
if (callback) {
callback();
}
})
.catch(err => {
dispatch(modifyUserFailure(user, err));
@@ -213,13 +216,16 @@ export function modifyUserFailure(user: User, error: Error): Action {
//delete user
export function deleteUser(user: User) {
export function deleteUser(user: User, callback?: () => void) {
return function(dispatch: any) {
dispatch(deleteUserPending(user));
return apiClient
.delete(user._links.delete.href)
.then(() => {
dispatch(deleteUserSuccess(user));
if (callback) {
callback();
}
})
.catch(cause => {
const error = new Error(

View File

@@ -3,46 +3,44 @@ import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import fetchMock from "fetch-mock";
import {
FETCH_USERS_PENDING,
FETCH_USERS_SUCCESS,
fetchUsers,
FETCH_USERS_FAILURE,
createUserPending,
import reducer, {
CREATE_USER_FAILURE,
CREATE_USER_PENDING,
CREATE_USER_SUCCESS,
CREATE_USER_FAILURE,
modifyUser,
MODIFY_USER_PENDING,
MODIFY_USER_FAILURE,
MODIFY_USER_SUCCESS,
deleteUserPending,
deleteUserFailure,
createUser,
createUserFailure,
createUserPending,
createUserSuccess,
DELETE_USER_FAILURE,
DELETE_USER_PENDING,
DELETE_USER_SUCCESS,
DELETE_USER_FAILURE,
deleteUser,
fetchUsersFailure,
fetchUsersSuccess,
fetchUser,
deleteUserFailure,
deleteUserPending,
deleteUserSuccess,
FETCH_USER_FAILURE,
FETCH_USER_PENDING,
FETCH_USER_SUCCESS,
FETCH_USER_FAILURE,
createUser,
createUserSuccess,
createUserFailure,
modifyUserPending,
modifyUserSuccess,
modifyUserFailure,
fetchUserSuccess,
deleteUserSuccess,
fetchUsersPending,
FETCH_USERS_FAILURE,
FETCH_USERS_PENDING,
FETCH_USERS_SUCCESS,
fetchUser,
fetchUserFailure,
fetchUserPending,
fetchUserFailure
fetchUsers,
fetchUsersFailure,
fetchUsersPending,
fetchUsersSuccess,
fetchUserSuccess,
MODIFY_USER_FAILURE,
MODIFY_USER_PENDING,
MODIFY_USER_SUCCESS,
modifyUser,
modifyUserFailure,
modifyUserPending,
modifyUserSuccess
} from "./users";
import reducer from "./users";
const userZaphod = {
active: true,
admin: true,
@@ -236,16 +234,32 @@ describe("users fetch()", () => {
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", {
status: 204
});
// after update, the users are fetched again
const store = mockStore({});
return store.dispatch(modifyUser(userZaphod)).then(() => {
const actions = store.getActions();
expect(actions.length).toBe(2);
expect(actions[0].type).toEqual(MODIFY_USER_PENDING);
expect(actions[1].type).toEqual(MODIFY_USER_SUCCESS);
});
});
it("should call callback, after successful modified user", () => {
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", {
status: 204
});
let called = false;
const callMe = () => {
called = true;
};
const store = mockStore({});
return store.dispatch(modifyUser(userZaphod, callMe)).then(() => {
expect(called).toBeTruthy();
});
});
it("should fail updating user on HTTP 500", () => {
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", {
status: 500
@@ -264,18 +278,33 @@ describe("users fetch()", () => {
fetchMock.deleteOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", {
status: 204
});
// after update, the users are fetched again
fetchMock.getOnce(USERS_URL, response);
const store = mockStore({});
return store.dispatch(deleteUser(userZaphod)).then(() => {
const actions = store.getActions();
expect(actions.length).toBe(2);
expect(actions[0].type).toEqual(DELETE_USER_PENDING);
expect(actions[0].payload).toBe(userZaphod);
expect(actions[1].type).toEqual(DELETE_USER_SUCCESS);
});
});
it("should call the callback, after successful delete", () => {
fetchMock.deleteOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", {
status: 204
});
let called = false;
const callMe = () => {
called = true;
};
const store = mockStore({});
return store.dispatch(deleteUser(userZaphod, callMe)).then(() => {
expect(called).toBeTruthy();
});
});
it("should fail to delete user zaphod", () => {
fetchMock.deleteOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", {
status: 500