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:
@@ -1,10 +1,9 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { translate } from "react-i18next";
|
import {translate} from "react-i18next";
|
||||||
import type { User } from "../types/User";
|
import type {User} from "../types/User";
|
||||||
import { InputField, Checkbox } from "../../components/forms";
|
import {Checkbox, InputField} from "../../components/forms";
|
||||||
import { SubmitButton } from "../../components/buttons";
|
import {SubmitButton} from "../../components/buttons";
|
||||||
import Loading from "../../components/Loading";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
submitForm: User => void,
|
submitForm: User => void,
|
||||||
|
|||||||
@@ -1,27 +1,38 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
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 UserForm from "./../components/UserForm";
|
||||||
import type { User } from "../types/User";
|
import type {User} from "../types/User";
|
||||||
import { modifyUser } from "../modules/users";
|
import {modifyUser} from "../modules/users";
|
||||||
|
import type {History} from "history";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
user: User,
|
user: User,
|
||||||
updateUser: User => void,
|
modifyUser: (user: User, callback?: () => void) => void,
|
||||||
loading: boolean
|
loading: boolean,
|
||||||
|
history: History
|
||||||
};
|
};
|
||||||
|
|
||||||
class EditUser extends React.Component<Props> {
|
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() {
|
render() {
|
||||||
const { user, updateUser } = this.props;
|
const { user } = this.props;
|
||||||
return <UserForm submitForm={user => updateUser(user)} user={user} />;
|
return <UserForm submitForm={user => this.modifyUser(user)} user={user} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
updateUser: (user: User) => {
|
modifyUser: (user: User, callback?: () => void) => {
|
||||||
dispatch(modifyUser(user));
|
dispatch(modifyUser(user, callback));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -33,4 +44,4 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(EditUser);
|
)(withRouter(EditUser));
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import {connect} from "react-redux";
|
||||||
import { Page } from "../../components/layout";
|
import {Page} from "../../components/layout";
|
||||||
import { Route } from "react-router";
|
import {Route} from "react-router";
|
||||||
import { Details } from "./../components/table";
|
import {Details} from "./../components/table";
|
||||||
import EditUser from "./EditUser";
|
import EditUser from "./EditUser";
|
||||||
import type { User } from "../types/User";
|
import type {User} from "../types/User";
|
||||||
import type { UserEntry } from "../types/UserEntry";
|
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 Loading from "../../components/Loading";
|
||||||
|
|
||||||
import { Navigation, Section, NavLink } from "../../components/navigation";
|
import {Navigation, NavLink, Section} from "../../components/navigation";
|
||||||
import { DeleteUserButton } from "./../components/buttons";
|
import {DeleteUserButton} from "./../components/buttons";
|
||||||
import ErrorPage from "../../components/ErrorPage";
|
import ErrorPage from "../../components/ErrorPage";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
name: string,
|
name: string,
|
||||||
userEntry?: UserEntry,
|
userEntry?: UserEntry,
|
||||||
match: any,
|
match: any,
|
||||||
deleteUser: (user: User) => void,
|
deleteUser: (user: User, callback?: () => void) => void,
|
||||||
fetchUser: string => void
|
fetchUser: string => void,
|
||||||
|
history: History
|
||||||
};
|
};
|
||||||
|
|
||||||
class SingleUser extends React.Component<Props> {
|
class SingleUser extends React.Component<Props> {
|
||||||
@@ -27,6 +29,14 @@ class SingleUser extends React.Component<Props> {
|
|||||||
this.props.fetchUser(this.props.name);
|
this.props.fetchUser(this.props.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userDeleted = () => {
|
||||||
|
this.props.history.push("/users");
|
||||||
|
};
|
||||||
|
|
||||||
|
deleteUser = (user: User) => {
|
||||||
|
this.props.deleteUser(user, this.userDeleted);
|
||||||
|
};
|
||||||
|
|
||||||
stripEndingSlash = (url: string) => {
|
stripEndingSlash = (url: string) => {
|
||||||
if (url.endsWith("/")) {
|
if (url.endsWith("/")) {
|
||||||
return url.substring(0, url.length - 2);
|
return url.substring(0, url.length - 2);
|
||||||
@@ -34,8 +44,12 @@ class SingleUser extends React.Component<Props> {
|
|||||||
return url;
|
return url;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
matchedUrl = () => {
|
||||||
|
return this.stripEndingSlash(this.props.match.url);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { userEntry, match, deleteUser } = this.props;
|
const { userEntry } = this.props;
|
||||||
|
|
||||||
if (!userEntry || userEntry.loading) {
|
if (!userEntry || userEntry.loading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
@@ -52,7 +66,7 @@ class SingleUser extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const user = userEntry.entry;
|
const user = userEntry.entry;
|
||||||
const url = this.stripEndingSlash(match.url);
|
const url = this.matchedUrl();
|
||||||
|
|
||||||
// TODO i18n
|
// TODO i18n
|
||||||
|
|
||||||
@@ -73,7 +87,7 @@ class SingleUser extends React.Component<Props> {
|
|||||||
<NavLink to={`${url}/edit`} label="Edit" />
|
<NavLink to={`${url}/edit`} label="Edit" />
|
||||||
</Section>
|
</Section>
|
||||||
<Section label="Actions">
|
<Section label="Actions">
|
||||||
<DeleteUserButton user={user} deleteUser={deleteUser} />
|
<DeleteUserButton user={user} deleteUser={this.deleteUser} />
|
||||||
<NavLink to="/users" label="Back" />
|
<NavLink to="/users" label="Back" />
|
||||||
</Section>
|
</Section>
|
||||||
</Navigation>
|
</Navigation>
|
||||||
@@ -102,8 +116,8 @@ const mapDispatchToProps = dispatch => {
|
|||||||
fetchUser: (name: string) => {
|
fetchUser: (name: string) => {
|
||||||
dispatch(fetchUser(name));
|
dispatch(fetchUser(name));
|
||||||
},
|
},
|
||||||
deleteUser: (user: User) => {
|
deleteUser: (user: User, callback?: () => void) => {
|
||||||
dispatch(deleteUser(user));
|
dispatch(deleteUser(user, callback));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import { apiClient } from "../../apiclient";
|
import {apiClient} from "../../apiclient";
|
||||||
import type { User } from "../types/User";
|
import type {User} from "../types/User";
|
||||||
import type { UserEntry } from "../types/UserEntry";
|
import type {UserEntry} from "../types/UserEntry";
|
||||||
import { Dispatch, combineReducers } from "redux";
|
import {combineReducers, Dispatch} from "redux";
|
||||||
import type { Action } from "../../types/Action";
|
import type {Action} from "../../types/Action";
|
||||||
|
|
||||||
export const FETCH_USERS_PENDING = "scm/users/FETCH_USERS_PENDING";
|
export const FETCH_USERS_PENDING = "scm/users/FETCH_USERS_PENDING";
|
||||||
export const FETCH_USERS_SUCCESS = "scm/users/FETCH_USERS_SUCCESS";
|
export const FETCH_USERS_SUCCESS = "scm/users/FETCH_USERS_SUCCESS";
|
||||||
@@ -173,13 +173,16 @@ export function createUserFailure(user: User, err: Error): Action {
|
|||||||
|
|
||||||
//modify user
|
//modify user
|
||||||
|
|
||||||
export function modifyUser(user: User) {
|
export function modifyUser(user: User, callback?: () => void) {
|
||||||
return function(dispatch: Dispatch) {
|
return function(dispatch: Dispatch) {
|
||||||
dispatch(modifyUserPending(user));
|
dispatch(modifyUserPending(user));
|
||||||
return apiClient
|
return apiClient
|
||||||
.putWithContentType(user._links.update.href, user, CONTENT_TYPE_USER)
|
.putWithContentType(user._links.update.href, user, CONTENT_TYPE_USER)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
dispatch(modifyUserSuccess(user));
|
dispatch(modifyUserSuccess(user));
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
dispatch(modifyUserFailure(user, err));
|
dispatch(modifyUserFailure(user, err));
|
||||||
@@ -213,13 +216,16 @@ export function modifyUserFailure(user: User, error: Error): Action {
|
|||||||
|
|
||||||
//delete user
|
//delete user
|
||||||
|
|
||||||
export function deleteUser(user: User) {
|
export function deleteUser(user: User, callback?: () => void) {
|
||||||
return function(dispatch: any) {
|
return function(dispatch: any) {
|
||||||
dispatch(deleteUserPending(user));
|
dispatch(deleteUserPending(user));
|
||||||
return apiClient
|
return apiClient
|
||||||
.delete(user._links.delete.href)
|
.delete(user._links.delete.href)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
dispatch(deleteUserSuccess(user));
|
dispatch(deleteUserSuccess(user));
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(cause => {
|
.catch(cause => {
|
||||||
const error = new Error(
|
const error = new Error(
|
||||||
|
|||||||
@@ -3,46 +3,44 @@ import configureMockStore from "redux-mock-store";
|
|||||||
import thunk from "redux-thunk";
|
import thunk from "redux-thunk";
|
||||||
import fetchMock from "fetch-mock";
|
import fetchMock from "fetch-mock";
|
||||||
|
|
||||||
import {
|
import reducer, {
|
||||||
FETCH_USERS_PENDING,
|
CREATE_USER_FAILURE,
|
||||||
FETCH_USERS_SUCCESS,
|
|
||||||
fetchUsers,
|
|
||||||
FETCH_USERS_FAILURE,
|
|
||||||
createUserPending,
|
|
||||||
CREATE_USER_PENDING,
|
CREATE_USER_PENDING,
|
||||||
CREATE_USER_SUCCESS,
|
CREATE_USER_SUCCESS,
|
||||||
CREATE_USER_FAILURE,
|
createUser,
|
||||||
modifyUser,
|
createUserFailure,
|
||||||
MODIFY_USER_PENDING,
|
createUserPending,
|
||||||
MODIFY_USER_FAILURE,
|
createUserSuccess,
|
||||||
MODIFY_USER_SUCCESS,
|
DELETE_USER_FAILURE,
|
||||||
deleteUserPending,
|
|
||||||
deleteUserFailure,
|
|
||||||
DELETE_USER_PENDING,
|
DELETE_USER_PENDING,
|
||||||
DELETE_USER_SUCCESS,
|
DELETE_USER_SUCCESS,
|
||||||
DELETE_USER_FAILURE,
|
|
||||||
deleteUser,
|
deleteUser,
|
||||||
fetchUsersFailure,
|
deleteUserFailure,
|
||||||
fetchUsersSuccess,
|
deleteUserPending,
|
||||||
fetchUser,
|
deleteUserSuccess,
|
||||||
|
FETCH_USER_FAILURE,
|
||||||
FETCH_USER_PENDING,
|
FETCH_USER_PENDING,
|
||||||
FETCH_USER_SUCCESS,
|
FETCH_USER_SUCCESS,
|
||||||
FETCH_USER_FAILURE,
|
FETCH_USERS_FAILURE,
|
||||||
createUser,
|
FETCH_USERS_PENDING,
|
||||||
createUserSuccess,
|
FETCH_USERS_SUCCESS,
|
||||||
createUserFailure,
|
fetchUser,
|
||||||
modifyUserPending,
|
fetchUserFailure,
|
||||||
modifyUserSuccess,
|
|
||||||
modifyUserFailure,
|
|
||||||
fetchUserSuccess,
|
|
||||||
deleteUserSuccess,
|
|
||||||
fetchUsersPending,
|
|
||||||
fetchUserPending,
|
fetchUserPending,
|
||||||
fetchUserFailure
|
fetchUsers,
|
||||||
|
fetchUsersFailure,
|
||||||
|
fetchUsersPending,
|
||||||
|
fetchUsersSuccess,
|
||||||
|
fetchUserSuccess,
|
||||||
|
MODIFY_USER_FAILURE,
|
||||||
|
MODIFY_USER_PENDING,
|
||||||
|
MODIFY_USER_SUCCESS,
|
||||||
|
modifyUser,
|
||||||
|
modifyUserFailure,
|
||||||
|
modifyUserPending,
|
||||||
|
modifyUserSuccess
|
||||||
} from "./users";
|
} from "./users";
|
||||||
|
|
||||||
import reducer from "./users";
|
|
||||||
|
|
||||||
const userZaphod = {
|
const userZaphod = {
|
||||||
active: true,
|
active: true,
|
||||||
admin: true,
|
admin: true,
|
||||||
@@ -236,16 +234,32 @@ describe("users fetch()", () => {
|
|||||||
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", {
|
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", {
|
||||||
status: 204
|
status: 204
|
||||||
});
|
});
|
||||||
// after update, the users are fetched again
|
|
||||||
|
|
||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
return store.dispatch(modifyUser(userZaphod)).then(() => {
|
return store.dispatch(modifyUser(userZaphod)).then(() => {
|
||||||
const actions = store.getActions();
|
const actions = store.getActions();
|
||||||
|
expect(actions.length).toBe(2);
|
||||||
expect(actions[0].type).toEqual(MODIFY_USER_PENDING);
|
expect(actions[0].type).toEqual(MODIFY_USER_PENDING);
|
||||||
expect(actions[1].type).toEqual(MODIFY_USER_SUCCESS);
|
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", () => {
|
it("should fail updating user on HTTP 500", () => {
|
||||||
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", {
|
fetchMock.putOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", {
|
||||||
status: 500
|
status: 500
|
||||||
@@ -264,18 +278,33 @@ describe("users fetch()", () => {
|
|||||||
fetchMock.deleteOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", {
|
fetchMock.deleteOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", {
|
||||||
status: 204
|
status: 204
|
||||||
});
|
});
|
||||||
// after update, the users are fetched again
|
|
||||||
fetchMock.getOnce(USERS_URL, response);
|
|
||||||
|
|
||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
return store.dispatch(deleteUser(userZaphod)).then(() => {
|
return store.dispatch(deleteUser(userZaphod)).then(() => {
|
||||||
const actions = store.getActions();
|
const actions = store.getActions();
|
||||||
|
expect(actions.length).toBe(2);
|
||||||
expect(actions[0].type).toEqual(DELETE_USER_PENDING);
|
expect(actions[0].type).toEqual(DELETE_USER_PENDING);
|
||||||
expect(actions[0].payload).toBe(userZaphod);
|
expect(actions[0].payload).toBe(userZaphod);
|
||||||
expect(actions[1].type).toEqual(DELETE_USER_SUCCESS);
|
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", () => {
|
it("should fail to delete user zaphod", () => {
|
||||||
fetchMock.deleteOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", {
|
fetchMock.deleteOnce("http://localhost:8081/scm/api/rest/v2/users/zaphod", {
|
||||||
status: 500
|
status: 500
|
||||||
|
|||||||
Reference in New Issue
Block a user