mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-09 23:15:43 +01:00
Merged heads
This commit is contained in:
@@ -2,9 +2,8 @@
|
||||
import React from "react";
|
||||
import {translate} from "react-i18next";
|
||||
import type {User} from "../types/User";
|
||||
import { InputField, Checkbox } from "../../components/forms";
|
||||
import {Checkbox, InputField} from "../../components/forms";
|
||||
import {SubmitButton} from "../../components/buttons";
|
||||
import Loading from "../../components/Loading";
|
||||
|
||||
type Props = {
|
||||
submitForm: User => void,
|
||||
|
||||
@@ -1,27 +1,38 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
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 {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));
|
||||
|
||||
@@ -7,10 +7,11 @@ 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 {History} from "history";
|
||||
import {deleteUser, fetchUser} from "../modules/users";
|
||||
import Loading from "../../components/Loading";
|
||||
|
||||
import { Navigation, Section, NavLink } from "../../components/navigation";
|
||||
import {Navigation, NavLink, Section} from "../../components/navigation";
|
||||
import {DeleteUserButton} from "./../components/buttons";
|
||||
import ErrorPage from "../../components/ErrorPage";
|
||||
|
||||
@@ -18,8 +19,9 @@ 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));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import {apiClient} from "../../apiclient";
|
||||
import type {User} from "../types/User";
|
||||
import type {UserEntry} from "../types/UserEntry";
|
||||
import { Dispatch, combineReducers } from "redux";
|
||||
import {combineReducers, Dispatch} from "redux";
|
||||
import type {Action} from "../../types/Action";
|
||||
|
||||
export const FETCH_USERS_PENDING = "scm/users/FETCH_USERS_PENDING";
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user