mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-15 09:46:16 +01:00
update users module to use new pending and error state
This commit is contained in:
@@ -35,7 +35,10 @@ class Page extends React.Component<Props> {
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
const { loading, children } = this.props;
|
||||
const { loading, children, error } = this.props;
|
||||
if (error) {
|
||||
return null;
|
||||
}
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,11 @@ export default function reducer(state: Object = {}, action: Action): Object {
|
||||
} else {
|
||||
const matches = RESET_PATTERN.exec(type);
|
||||
if (matches) {
|
||||
return removeFromState(state, matches[1]);
|
||||
let identifier = matches[1];
|
||||
if (action.itemId) {
|
||||
identifier += "/" + action.itemId;
|
||||
}
|
||||
return removeFromState(state, identifier);
|
||||
}
|
||||
}
|
||||
return state;
|
||||
|
||||
@@ -69,6 +69,16 @@ describe("pending reducer", () => {
|
||||
expect(newState["FETCH_USER/21"]).toBe(true);
|
||||
expect(newState["FETCH_USER/42"]).toBe(false);
|
||||
});
|
||||
|
||||
it("should reset pending for a single item", () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
"FETCH_USER/42": true
|
||||
},
|
||||
{ type: "FETCH_USER_SUCCESS", itemId: 42 }
|
||||
);
|
||||
expect(newState["FETCH_USER/42"]).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("pending selectors", () => {
|
||||
|
||||
4
scm-ui/src/modules/types.js
Normal file
4
scm-ui/src/modules/types.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export const PENDING_SUFFIX = "PENDING";
|
||||
export const SUCCESS_SUFFIX = "SUCCESS";
|
||||
export const FAILURE_SUFFIX = "FAILURE";
|
||||
export const RESET_SUFFIX = "RESET";
|
||||
@@ -10,9 +10,3 @@ export type PagedCollection = Collection & {
|
||||
page: number,
|
||||
pageTotal: number
|
||||
};
|
||||
|
||||
export type PageCollectionStateSlice = {
|
||||
entry?: PagedCollection,
|
||||
error?: Error,
|
||||
loading?: boolean
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { UserEntry } from "../../types/UserEntry";
|
||||
import type { User } from "../../types/User";
|
||||
import { NavLink } from "../../../components/navigation";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
user: UserEntry,
|
||||
user: User,
|
||||
editUrl: String
|
||||
};
|
||||
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import UserRow from "./UserRow";
|
||||
import type { UserEntry } from "../../types/UserEntry";
|
||||
import type { User } from "../../types/User";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
entries: Array<UserEntry>
|
||||
users: User[]
|
||||
};
|
||||
|
||||
class UserTable extends React.Component<Props> {
|
||||
render() {
|
||||
const { entries, t } = this.props;
|
||||
const { users, t } = this.props;
|
||||
return (
|
||||
<table className="table is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
@@ -23,8 +23,8 @@ class UserTable extends React.Component<Props> {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{entries.map((entry, index) => {
|
||||
return <UserRow key={index} user={entry.entry} />;
|
||||
{users.map((user, index) => {
|
||||
return <UserRow key={index} user={user} />;
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -4,17 +4,26 @@ import { connect } from "react-redux";
|
||||
import UserForm from "./../components/UserForm";
|
||||
import type { User } from "../types/User";
|
||||
import type { History } from "history";
|
||||
import { createUser, createUserReset } from "../modules/users";
|
||||
import {
|
||||
createUser,
|
||||
createUserReset,
|
||||
isCreateUserPending,
|
||||
getCreateUserFailure
|
||||
} from "../modules/users";
|
||||
import { Page } from "../../components/layout";
|
||||
import { translate } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
addUser: (user: User, callback?: () => void) => void,
|
||||
loading?: boolean,
|
||||
error?: Error,
|
||||
history: History,
|
||||
resetForm: () => void
|
||||
|
||||
// dispatcher functions
|
||||
addUser: (user: User, callback?: () => void) => void,
|
||||
resetForm: () => void,
|
||||
|
||||
// context objects
|
||||
t: string => string,
|
||||
history: History
|
||||
};
|
||||
|
||||
class AddUser extends React.Component<Props> {
|
||||
@@ -61,10 +70,12 @@ const mapDispatchToProps = dispatch => {
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
if (state.users && state.users.create) {
|
||||
return state.users.create;
|
||||
}
|
||||
return {};
|
||||
const loading = isCreateUserPending(state);
|
||||
const error = getCreateUserFailure(state);
|
||||
return {
|
||||
loading,
|
||||
error
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
|
||||
@@ -4,13 +4,23 @@ 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 {
|
||||
modifyUser,
|
||||
isModifyUserPending,
|
||||
getModifyUserFailure
|
||||
} from "../modules/users";
|
||||
import type { History } from "history";
|
||||
import ErrorNotification from "../../components/ErrorNotification";
|
||||
|
||||
type Props = {
|
||||
user: User,
|
||||
modifyUser: (user: User, callback?: () => void) => void,
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
|
||||
// dispatch functions
|
||||
modifyUser: (user: User, callback?: () => void) => void,
|
||||
|
||||
// context objects
|
||||
user: User,
|
||||
history: History
|
||||
};
|
||||
|
||||
@@ -24,8 +34,17 @@ class EditUser extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { user } = this.props;
|
||||
return <UserForm submitForm={user => this.modifyUser(user)} user={user} />;
|
||||
const { user, loading, error } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<ErrorNotification error={error} />
|
||||
<UserForm
|
||||
submitForm={user => this.modifyUser(user)}
|
||||
user={user}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +57,12 @@ const mapDispatchToProps = dispatch => {
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {};
|
||||
const loading = isModifyUserPending(state, ownProps.user.name);
|
||||
const error = getModifyUserFailure(state, ownProps.user.name);
|
||||
return {
|
||||
loading,
|
||||
error
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
|
||||
@@ -6,9 +6,16 @@ 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 type { History } from "history";
|
||||
import { fetchUser, deleteUser } from "../modules/users";
|
||||
import {
|
||||
fetchUser,
|
||||
deleteUser,
|
||||
getUserByName,
|
||||
isFetchUserPending,
|
||||
getFetchUserFailure,
|
||||
isDeleteUserPending,
|
||||
getDeleteUserFailure
|
||||
} from "../modules/users";
|
||||
import Loading from "../../components/Loading";
|
||||
|
||||
import { Navigation, Section, NavLink } from "../../components/navigation";
|
||||
@@ -16,14 +23,19 @@ import { DeleteUserNavLink, EditUserNavLink } from "./../components/navLinks";
|
||||
import ErrorPage from "../../components/ErrorPage";
|
||||
import { translate } from "react-i18next";
|
||||
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
name: string,
|
||||
userEntry?: UserEntry,
|
||||
match: any,
|
||||
user: User,
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
|
||||
// dispatcher functions
|
||||
deleteUser: (user: User, callback?: () => void) => void,
|
||||
fetchUser: string => void,
|
||||
|
||||
// context objects
|
||||
t: string => string,
|
||||
match: any,
|
||||
history: History
|
||||
};
|
||||
|
||||
@@ -52,23 +64,22 @@ class SingleUser extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, userEntry } = this.props;
|
||||
const { t, loading, error, user } = this.props;
|
||||
|
||||
if (!userEntry || userEntry.loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (userEntry.error) {
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorPage
|
||||
title={t("single-user.error-title")}
|
||||
subtitle={t("single-user.error-subtitle")}
|
||||
error={userEntry.error}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const user = userEntry.entry;
|
||||
if (!user || loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
const url = this.matchedUrl();
|
||||
|
||||
return (
|
||||
@@ -84,7 +95,10 @@ class SingleUser extends React.Component<Props> {
|
||||
<div className="column">
|
||||
<Navigation>
|
||||
<Section label={t("single-user.navigation-label")}>
|
||||
<NavLink to={`${url}`} label={t("single-user.information-label")} />
|
||||
<NavLink
|
||||
to={`${url}`}
|
||||
label={t("single-user.information-label")}
|
||||
/>
|
||||
<EditUserNavLink user={user} editUrl={`${url}/edit`} />
|
||||
</Section>
|
||||
<Section label={t("single-user.actions-label")}>
|
||||
@@ -101,14 +115,17 @@ class SingleUser extends React.Component<Props> {
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const name = ownProps.match.params.name;
|
||||
let userEntry;
|
||||
if (state.users && state.users.byNames) {
|
||||
userEntry = state.users.byNames[name];
|
||||
}
|
||||
const user = getUserByName(state, name);
|
||||
const loading =
|
||||
isFetchUserPending(state, name) || isDeleteUserPending(state, name);
|
||||
const error =
|
||||
getFetchUserFailure(state, name) || getDeleteUserFailure(state, name);
|
||||
|
||||
return {
|
||||
name,
|
||||
userEntry
|
||||
user,
|
||||
loading,
|
||||
error
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -9,20 +9,24 @@ import {
|
||||
fetchUsersByLink,
|
||||
getUsersFromState,
|
||||
selectListAsCollection,
|
||||
isPermittedToCreateUsers
|
||||
isPermittedToCreateUsers,
|
||||
isFetchUsersPending,
|
||||
getFetchUsersFailure
|
||||
} from "../modules/users";
|
||||
|
||||
import { Page } from "../../components/layout";
|
||||
import { UserTable } from "./../components/table";
|
||||
import type { UserEntry } from "../types/UserEntry";
|
||||
import type { PageCollectionStateSlice } from "../../types/Collection";
|
||||
import type { User } from "../types/User";
|
||||
import type { PagedCollection } from "../../types/Collection";
|
||||
import Paginator from "../../components/Paginator";
|
||||
import CreateUserButton from "../components/buttons/CreateUserButton";
|
||||
|
||||
type Props = {
|
||||
userEntries: UserEntry[],
|
||||
users: User[],
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
canAddUsers: boolean,
|
||||
list: PageCollectionStateSlice,
|
||||
list: PagedCollection,
|
||||
page: number,
|
||||
|
||||
// context objects
|
||||
@@ -48,9 +52,9 @@ class Users extends React.Component<Props> {
|
||||
*/
|
||||
componentDidUpdate = (prevProps: Props) => {
|
||||
const { page, list } = this.props;
|
||||
if (list.entry) {
|
||||
if (list.page) {
|
||||
// backend starts paging by 0
|
||||
const statePage: number = list.entry.page + 1;
|
||||
const statePage: number = list.page + 1;
|
||||
if (page !== statePage) {
|
||||
this.props.history.push(`/users/${statePage}`);
|
||||
}
|
||||
@@ -58,15 +62,15 @@ class Users extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { userEntries, list, t } = this.props;
|
||||
const { users, loading, error, t } = this.props;
|
||||
return (
|
||||
<Page
|
||||
title={t("users.title")}
|
||||
subtitle={t("users.subtitle")}
|
||||
loading={list.loading || !userEntries}
|
||||
error={list.error}
|
||||
loading={loading || !users}
|
||||
error={error}
|
||||
>
|
||||
<UserTable entries={userEntries} />
|
||||
<UserTable users={users} />
|
||||
{this.renderPaginator()}
|
||||
{this.renderCreateButton()}
|
||||
</Page>
|
||||
@@ -75,10 +79,8 @@ class Users extends React.Component<Props> {
|
||||
|
||||
renderPaginator() {
|
||||
const { list } = this.props;
|
||||
if (list.entry) {
|
||||
return (
|
||||
<Paginator collection={list.entry} onPageChange={this.onPageChange} />
|
||||
);
|
||||
if (list) {
|
||||
return <Paginator collection={list} onPageChange={this.onPageChange} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -103,12 +105,18 @@ const getPageFromProps = props => {
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const users = getUsersFromState(state);
|
||||
const loading = isFetchUsersPending(state);
|
||||
const error = getFetchUsersFailure(state);
|
||||
|
||||
const page = getPageFromProps(ownProps);
|
||||
const userEntries = getUsersFromState(state);
|
||||
const canAddUsers = isPermittedToCreateUsers(state);
|
||||
const list = selectListAsCollection(state);
|
||||
|
||||
return {
|
||||
userEntries,
|
||||
users,
|
||||
loading,
|
||||
error,
|
||||
canAddUsers,
|
||||
list,
|
||||
page
|
||||
|
||||
@@ -1,37 +1,44 @@
|
||||
// @flow
|
||||
import { apiClient } from "../../apiclient";
|
||||
import { isPending } from "../../modules/pending";
|
||||
import { getFailure } from "../../modules/failure";
|
||||
import * as types from "../../modules/types";
|
||||
import type { User } from "../types/User";
|
||||
import type { UserEntry } from "../types/UserEntry";
|
||||
import { combineReducers, Dispatch } from "redux";
|
||||
import type { Action } from "../../types/Action";
|
||||
import type { PageCollectionStateSlice } from "../../types/Collection";
|
||||
import type { PagedCollection } from "../../types/Collection";
|
||||
|
||||
export const FETCH_USERS_PENDING = "scm/users/FETCH_USERS_PENDING";
|
||||
export const FETCH_USERS_SUCCESS = "scm/users/FETCH_USERS_SUCCESS";
|
||||
export const FETCH_USERS_FAILURE = "scm/users/FETCH_USERS_FAILURE";
|
||||
export const FETCH_USERS = "scm/users/FETCH_USERS";
|
||||
export const FETCH_USERS_PENDING = `${FETCH_USERS}_${types.PENDING_SUFFIX}`;
|
||||
export const FETCH_USERS_SUCCESS = `${FETCH_USERS}_${types.SUCCESS_SUFFIX}`;
|
||||
export const FETCH_USERS_FAILURE = `${FETCH_USERS}_${types.FAILURE_SUFFIX}`;
|
||||
|
||||
export const FETCH_USER_PENDING = "scm/users/FETCH_USER_PENDING";
|
||||
export const FETCH_USER_SUCCESS = "scm/users/FETCH_USER_SUCCESS";
|
||||
export const FETCH_USER_FAILURE = "scm/users/FETCH_USER_FAILURE";
|
||||
export const FETCH_USER = "scm/users/FETCH_USER";
|
||||
export const FETCH_USER_PENDING = `${FETCH_USER}_${types.PENDING_SUFFIX}`;
|
||||
export const FETCH_USER_SUCCESS = `${FETCH_USER}_${types.SUCCESS_SUFFIX}`;
|
||||
export const FETCH_USER_FAILURE = `${FETCH_USER}_${types.FAILURE_SUFFIX}`;
|
||||
|
||||
export const CREATE_USER_PENDING = "scm/users/CREATE_USER_PENDING";
|
||||
export const CREATE_USER_SUCCESS = "scm/users/CREATE_USER_SUCCESS";
|
||||
export const CREATE_USER_FAILURE = "scm/users/CREATE_USER_FAILURE";
|
||||
export const CREATE_USER_RESET = "scm/users/CREATE_USER_RESET";
|
||||
export const CREATE_USER = "scm/users/CREATE_USER";
|
||||
export const CREATE_USER_PENDING = `${CREATE_USER}_${types.PENDING_SUFFIX}`;
|
||||
export const CREATE_USER_SUCCESS = `${CREATE_USER}_${types.SUCCESS_SUFFIX}`;
|
||||
export const CREATE_USER_FAILURE = `${CREATE_USER}_${types.FAILURE_SUFFIX}`;
|
||||
export const CREATE_USER_RESET = `${CREATE_USER}_${types.RESET_SUFFIX}`;
|
||||
|
||||
export const MODIFY_USER_PENDING = "scm/users/MODIFY_USER_PENDING";
|
||||
export const MODIFY_USER_SUCCESS = "scm/users/MODIFY_USER_SUCCESS";
|
||||
export const MODIFY_USER_FAILURE = "scm/users/MODIFY_USER_FAILURE";
|
||||
export const MODIFY_USER = "scm/users/MODIFY_USER";
|
||||
export const MODIFY_USER_PENDING = `${MODIFY_USER}_${types.PENDING_SUFFIX}`;
|
||||
export const MODIFY_USER_SUCCESS = `${MODIFY_USER}_${types.SUCCESS_SUFFIX}`;
|
||||
export const MODIFY_USER_FAILURE = `${MODIFY_USER}_${types.FAILURE_SUFFIX}`;
|
||||
|
||||
export const DELETE_USER_PENDING = "scm/users/DELETE_PENDING";
|
||||
export const DELETE_USER_SUCCESS = "scm/users/DELETE_SUCCESS";
|
||||
export const DELETE_USER_FAILURE = "scm/users/DELETE_FAILURE";
|
||||
export const DELETE_USER = "scm/users/DELETE";
|
||||
export const DELETE_USER_PENDING = `${DELETE_USER}_${types.PENDING_SUFFIX}`;
|
||||
export const DELETE_USER_SUCCESS = `${DELETE_USER}_${types.SUCCESS_SUFFIX}`;
|
||||
export const DELETE_USER_FAILURE = `${DELETE_USER}_${types.FAILURE_SUFFIX}`;
|
||||
|
||||
const USERS_URL = "users";
|
||||
|
||||
const CONTENT_TYPE_USER = "application/vnd.scmm-user+json;v=2";
|
||||
|
||||
//TODO i18n
|
||||
// TODO i18n for error messages
|
||||
|
||||
// fetch users
|
||||
|
||||
@@ -111,14 +118,16 @@ export function fetchUser(name: string) {
|
||||
export function fetchUserPending(name: string): Action {
|
||||
return {
|
||||
type: FETCH_USER_PENDING,
|
||||
payload: name
|
||||
payload: name,
|
||||
itemId: name
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchUserSuccess(user: any): Action {
|
||||
return {
|
||||
type: FETCH_USER_SUCCESS,
|
||||
payload: user
|
||||
payload: user,
|
||||
itemId: user.name
|
||||
};
|
||||
}
|
||||
|
||||
@@ -128,7 +137,8 @@ export function fetchUserFailure(name: string, error: Error): Action {
|
||||
payload: {
|
||||
name,
|
||||
error
|
||||
}
|
||||
},
|
||||
itemId: name
|
||||
};
|
||||
}
|
||||
|
||||
@@ -203,14 +213,16 @@ export function modifyUser(user: User, callback?: () => void) {
|
||||
export function modifyUserPending(user: User): Action {
|
||||
return {
|
||||
type: MODIFY_USER_PENDING,
|
||||
payload: user
|
||||
payload: user,
|
||||
itemId: user.name
|
||||
};
|
||||
}
|
||||
|
||||
export function modifyUserSuccess(user: User): Action {
|
||||
return {
|
||||
type: MODIFY_USER_SUCCESS,
|
||||
payload: user
|
||||
payload: user,
|
||||
itemId: user.name
|
||||
};
|
||||
}
|
||||
|
||||
@@ -220,7 +232,8 @@ export function modifyUserFailure(user: User, error: Error): Action {
|
||||
payload: {
|
||||
error,
|
||||
user
|
||||
}
|
||||
},
|
||||
itemId: user.name
|
||||
};
|
||||
}
|
||||
|
||||
@@ -249,14 +262,16 @@ export function deleteUser(user: User, callback?: () => void) {
|
||||
export function deleteUserPending(user: User): Action {
|
||||
return {
|
||||
type: DELETE_USER_PENDING,
|
||||
payload: user
|
||||
payload: user,
|
||||
itemId: user.name
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteUserSuccess(user: User): Action {
|
||||
return {
|
||||
type: DELETE_USER_SUCCESS,
|
||||
payload: user
|
||||
payload: user,
|
||||
itemId: user.name
|
||||
};
|
||||
}
|
||||
|
||||
@@ -266,40 +281,20 @@ export function deleteUserFailure(user: User, error: Error): Action {
|
||||
payload: {
|
||||
error,
|
||||
user
|
||||
}
|
||||
},
|
||||
itemId: user.name
|
||||
};
|
||||
}
|
||||
|
||||
//helper functions
|
||||
|
||||
export function getUsersFromState(state: any) {
|
||||
if (!state.users.list) {
|
||||
return null;
|
||||
}
|
||||
const userNames = state.users.list.entries;
|
||||
if (!userNames) {
|
||||
return null;
|
||||
}
|
||||
const userEntries: Array<UserEntry> = [];
|
||||
|
||||
for (let userName of userNames) {
|
||||
userEntries.push(state.users.byNames[userName]);
|
||||
}
|
||||
|
||||
return userEntries;
|
||||
}
|
||||
|
||||
function extractUsersByNames(
|
||||
users: Array<User>,
|
||||
userNames: Array<string>,
|
||||
oldUsersByNames: {}
|
||||
users: User[],
|
||||
userNames: string[],
|
||||
oldUsersByNames: Object
|
||||
) {
|
||||
const usersByNames = {};
|
||||
|
||||
for (let user of users) {
|
||||
usersByNames[user.name] = {
|
||||
entry: user
|
||||
};
|
||||
usersByNames[user.name] = user;
|
||||
}
|
||||
|
||||
for (let userName in oldUsersByNames) {
|
||||
@@ -335,20 +330,12 @@ const reducerByName = (state: any, username: string, newUserState: any) => {
|
||||
|
||||
function listReducer(state: any = {}, action: any = {}) {
|
||||
switch (action.type) {
|
||||
// Fetch all users actions
|
||||
case FETCH_USERS_PENDING:
|
||||
return {
|
||||
...state,
|
||||
loading: true
|
||||
};
|
||||
case FETCH_USERS_SUCCESS:
|
||||
const users = action.payload._embedded.users;
|
||||
const userNames = users.map(user => user.name);
|
||||
return {
|
||||
...state,
|
||||
error: null,
|
||||
entries: userNames,
|
||||
loading: false,
|
||||
entry: {
|
||||
userCreatePermission: action.payload._links.create ? true : false,
|
||||
page: action.payload.page,
|
||||
@@ -356,12 +343,7 @@ function listReducer(state: any = {}, action: any = {}) {
|
||||
_links: action.payload._links
|
||||
}
|
||||
};
|
||||
case FETCH_USERS_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
error: action.payload.error
|
||||
};
|
||||
|
||||
// Delete single user actions
|
||||
case DELETE_USER_SUCCESS:
|
||||
const newUserEntries = deleteUserInEntries(
|
||||
@@ -389,44 +371,12 @@ function byNamesReducer(state: any = {}, action: any = {}) {
|
||||
};
|
||||
|
||||
// Fetch single user actions
|
||||
case FETCH_USER_PENDING:
|
||||
return reducerByName(state, action.payload, {
|
||||
loading: true,
|
||||
error: null
|
||||
});
|
||||
case FETCH_USER_SUCCESS:
|
||||
return reducerByName(state, action.payload.name, {
|
||||
loading: false,
|
||||
error: null,
|
||||
entry: action.payload
|
||||
});
|
||||
case FETCH_USER_FAILURE:
|
||||
return reducerByName(state, action.payload.name, {
|
||||
loading: false,
|
||||
error: action.payload.error
|
||||
});
|
||||
return reducerByName(state, action.payload.name, action.payload);
|
||||
|
||||
// Update single user actions
|
||||
case MODIFY_USER_PENDING:
|
||||
return reducerByName(state, action.payload.name, {
|
||||
loading: true
|
||||
});
|
||||
case MODIFY_USER_SUCCESS:
|
||||
return reducerByName(state, action.payload.name, {
|
||||
entry: action.payload
|
||||
});
|
||||
case MODIFY_USER_FAILURE:
|
||||
return reducerByName(state, action.payload.user.name, {
|
||||
error: action.payload.error
|
||||
});
|
||||
return reducerByName(state, action.payload.name, action.payload);
|
||||
|
||||
// Delete single user actions
|
||||
case DELETE_USER_PENDING:
|
||||
return reducerByName(state, action.payload.name, {
|
||||
loading: true,
|
||||
error: null,
|
||||
entry: action.payload
|
||||
});
|
||||
case DELETE_USER_SUCCESS:
|
||||
const newUserByNames = deleteUserInUsersByNames(
|
||||
state,
|
||||
@@ -434,17 +384,16 @@ function byNamesReducer(state: any = {}, action: any = {}) {
|
||||
);
|
||||
return newUserByNames;
|
||||
|
||||
case DELETE_USER_FAILURE:
|
||||
return reducerByName(state, action.payload.user.name, {
|
||||
loading: false,
|
||||
error: action.payload.error,
|
||||
entry: action.payload.user
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default combineReducers({
|
||||
list: listReducer,
|
||||
byNames: byNamesReducer
|
||||
});
|
||||
|
||||
// selectors
|
||||
|
||||
const selectList = (state: Object) => {
|
||||
@@ -454,7 +403,7 @@ const selectList = (state: Object) => {
|
||||
return {};
|
||||
};
|
||||
|
||||
const selectListEntry = (state: Object) => {
|
||||
const selectListEntry = (state: Object): Object => {
|
||||
const list = selectList(state);
|
||||
if (list.entry) {
|
||||
return list.entry;
|
||||
@@ -462,10 +411,8 @@ const selectListEntry = (state: Object) => {
|
||||
return {};
|
||||
};
|
||||
|
||||
export const selectListAsCollection = (
|
||||
state: Object
|
||||
): PageCollectionStateSlice => {
|
||||
return selectList(state);
|
||||
export const selectListAsCollection = (state: Object): PagedCollection => {
|
||||
return selectListEntry(state);
|
||||
};
|
||||
|
||||
export const isPermittedToCreateUsers = (state: Object): boolean => {
|
||||
@@ -475,29 +422,63 @@ export const isPermittedToCreateUsers = (state: Object): boolean => {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
function createReducer(state: any = {}, action: any = {}) {
|
||||
switch (action.type) {
|
||||
case CREATE_USER_PENDING:
|
||||
return {
|
||||
loading: true
|
||||
};
|
||||
case CREATE_USER_SUCCESS:
|
||||
case CREATE_USER_RESET:
|
||||
return {
|
||||
loading: false
|
||||
};
|
||||
case CREATE_USER_FAILURE:
|
||||
return {
|
||||
loading: false,
|
||||
error: action.payload
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
|
||||
export function getUsersFromState(state: Object) {
|
||||
const userNames = selectList(state).entries;
|
||||
if (!userNames) {
|
||||
return null;
|
||||
}
|
||||
const userEntries: User[] = [];
|
||||
|
||||
for (let userName of userNames) {
|
||||
userEntries.push(state.users.byNames[userName]);
|
||||
}
|
||||
|
||||
return userEntries;
|
||||
}
|
||||
|
||||
export function isFetchUsersPending(state: Object) {
|
||||
return isPending(state, FETCH_USERS);
|
||||
}
|
||||
|
||||
export function getFetchUsersFailure(state: Object) {
|
||||
return getFailure(state, FETCH_USERS);
|
||||
}
|
||||
|
||||
export function isCreateUserPending(state: Object) {
|
||||
return isPending(state, CREATE_USER);
|
||||
}
|
||||
|
||||
export function getCreateUserFailure(state: Object) {
|
||||
return getFailure(state, CREATE_USER);
|
||||
}
|
||||
|
||||
export function getUserByName(state: Object, name: string) {
|
||||
if (state.users && state.users.byNames) {
|
||||
return state.users.byNames[name];
|
||||
}
|
||||
}
|
||||
|
||||
export default combineReducers({
|
||||
list: listReducer,
|
||||
byNames: byNamesReducer,
|
||||
create: createReducer
|
||||
});
|
||||
export function isFetchUserPending(state: Object, name: string) {
|
||||
return isPending(state, FETCH_USER, name);
|
||||
}
|
||||
|
||||
export function getFetchUserFailure(state: Object, name: string) {
|
||||
return getFailure(state, FETCH_USER, name);
|
||||
}
|
||||
|
||||
export function isModifyUserPending(state: Object, name: string) {
|
||||
return isPending(state, MODIFY_USER, name);
|
||||
}
|
||||
|
||||
export function getModifyUserFailure(state: Object, name: string) {
|
||||
return getFailure(state, MODIFY_USER, name);
|
||||
}
|
||||
|
||||
export function isDeleteUserPending(state: Object, name: string) {
|
||||
return isPending(state, DELETE_USER, name);
|
||||
}
|
||||
|
||||
export function getDeleteUserFailure(state: Object, name: string) {
|
||||
return getFailure(state, DELETE_USER, name);
|
||||
}
|
||||
|
||||
@@ -8,16 +8,10 @@ import reducer, {
|
||||
CREATE_USER_PENDING,
|
||||
CREATE_USER_SUCCESS,
|
||||
createUser,
|
||||
createUserFailure,
|
||||
createUserPending,
|
||||
createUserSuccess,
|
||||
createUserReset,
|
||||
DELETE_USER_FAILURE,
|
||||
DELETE_USER_PENDING,
|
||||
DELETE_USER_SUCCESS,
|
||||
deleteUser,
|
||||
deleteUserFailure,
|
||||
deleteUserPending,
|
||||
deleteUserSuccess,
|
||||
FETCH_USER_FAILURE,
|
||||
FETCH_USER_PENDING,
|
||||
@@ -26,21 +20,15 @@ import reducer, {
|
||||
FETCH_USERS_PENDING,
|
||||
FETCH_USERS_SUCCESS,
|
||||
fetchUser,
|
||||
fetchUserFailure,
|
||||
fetchUserPending,
|
||||
fetchUsers,
|
||||
fetchUsersFailure,
|
||||
fetchUsersPending,
|
||||
fetchUsersSuccess,
|
||||
fetchUserSuccess,
|
||||
fetchUsers,
|
||||
fetchUsersSuccess,
|
||||
selectListAsCollection,
|
||||
isPermittedToCreateUsers,
|
||||
MODIFY_USER_FAILURE,
|
||||
MODIFY_USER_PENDING,
|
||||
MODIFY_USER_SUCCESS,
|
||||
modifyUser,
|
||||
modifyUserFailure,
|
||||
modifyUserPending,
|
||||
modifyUserSuccess
|
||||
} from "./users";
|
||||
|
||||
@@ -325,19 +313,11 @@ describe("users fetch()", () => {
|
||||
});
|
||||
|
||||
describe("users reducer", () => {
|
||||
it("should update state correctly according to FETCH_USERS_PENDING action", () => {
|
||||
const newState = reducer({}, fetchUsersPending());
|
||||
expect(newState.list.loading).toBeTruthy();
|
||||
expect(newState.list.error).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should update state correctly according to FETCH_USERS_SUCCESS action", () => {
|
||||
const newState = reducer({}, fetchUsersSuccess(responseBody));
|
||||
|
||||
expect(newState.list).toEqual({
|
||||
entries: ["zaphod", "ford"],
|
||||
error: null,
|
||||
loading: false,
|
||||
entry: {
|
||||
userCreatePermission: true,
|
||||
page: 0,
|
||||
@@ -347,31 +327,13 @@ describe("users reducer", () => {
|
||||
});
|
||||
|
||||
expect(newState.byNames).toEqual({
|
||||
zaphod: {
|
||||
entry: userZaphod
|
||||
},
|
||||
ford: {
|
||||
entry: userFord
|
||||
}
|
||||
zaphod: userZaphod,
|
||||
ford: userFord
|
||||
});
|
||||
|
||||
expect(newState.list.entry.userCreatePermission).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should set error when fetching users failed", () => {
|
||||
const oldState = {
|
||||
list: {
|
||||
loading: true
|
||||
}
|
||||
};
|
||||
|
||||
const error = new Error("kaputt");
|
||||
|
||||
const newState = reducer(oldState, fetchUsersFailure("url.com", error));
|
||||
expect(newState.list.loading).toBeFalsy();
|
||||
expect(newState.list.error).toEqual(error);
|
||||
});
|
||||
|
||||
it("should set userCreatePermission to true if update link is present", () => {
|
||||
const newState = reducer({}, fetchUsersSuccess(responseBody));
|
||||
|
||||
@@ -381,9 +343,7 @@ describe("users reducer", () => {
|
||||
it("should not replace whole byNames map when fetching users", () => {
|
||||
const oldState = {
|
||||
byNames: {
|
||||
ford: {
|
||||
entry: userFord
|
||||
}
|
||||
ford: userFord
|
||||
}
|
||||
};
|
||||
|
||||
@@ -392,95 +352,14 @@ describe("users reducer", () => {
|
||||
expect(newState.byNames["ford"]).toBeDefined();
|
||||
});
|
||||
|
||||
it("should update state correctly according to DELETE_USER_PENDING action", () => {
|
||||
const state = {
|
||||
byNames: {
|
||||
zaphod: {
|
||||
loading: false,
|
||||
error: null,
|
||||
entry: userZaphod
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const newState = reducer(state, deleteUserPending(userZaphod));
|
||||
const zaphod = newState.byNames["zaphod"];
|
||||
expect(zaphod.loading).toBeTruthy();
|
||||
expect(zaphod.entry).toBe(userZaphod);
|
||||
});
|
||||
|
||||
it("should not effect other users if one user will be deleted", () => {
|
||||
const state = {
|
||||
byNames: {
|
||||
zaphod: {
|
||||
loading: false,
|
||||
error: null,
|
||||
entry: userZaphod
|
||||
},
|
||||
ford: {
|
||||
loading: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const newState = reducer(state, deleteUserPending(userZaphod));
|
||||
const ford = newState.byNames["ford"];
|
||||
expect(ford.loading).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should set the error of user which could not be deleted", () => {
|
||||
const state = {
|
||||
byNames: {
|
||||
zaphod: {
|
||||
loading: true,
|
||||
entry: userZaphod
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const error = new Error("error");
|
||||
const newState = reducer(state, deleteUserFailure(userZaphod, error));
|
||||
const zaphod = newState.byNames["zaphod"];
|
||||
expect(zaphod.loading).toBeFalsy();
|
||||
expect(zaphod.error).toBe(error);
|
||||
});
|
||||
|
||||
it("should not effect other users if one user could not be deleted", () => {
|
||||
const state = {
|
||||
byNames: {
|
||||
zaphod: {
|
||||
loading: false,
|
||||
error: null,
|
||||
entry: userZaphod
|
||||
},
|
||||
ford: {
|
||||
loading: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const error = new Error("error");
|
||||
const newState = reducer(state, deleteUserFailure(userZaphod, error));
|
||||
const ford = newState.byNames["ford"];
|
||||
expect(ford.loading).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should remove user from state when delete succeeds", () => {
|
||||
const state = {
|
||||
list: {
|
||||
entries: ["ford", "zaphod"]
|
||||
},
|
||||
byNames: {
|
||||
zaphod: {
|
||||
loading: true,
|
||||
error: null,
|
||||
entry: userZaphod
|
||||
},
|
||||
ford: {
|
||||
loading: true,
|
||||
error: null,
|
||||
entry: userFord
|
||||
}
|
||||
zaphod: userZaphod,
|
||||
ford: userFord
|
||||
}
|
||||
};
|
||||
|
||||
@@ -499,68 +378,9 @@ describe("users reducer", () => {
|
||||
expect(newState.byNames["zaphod"]).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should update state correctly according to CREATE_USER_PENDING action", () => {
|
||||
const newState = reducer({}, createUserPending(userZaphod));
|
||||
expect(newState.create.loading).toBeTruthy();
|
||||
expect(newState.create.error).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should update state correctly according to CREATE_USER_SUCCESS action", () => {
|
||||
const newState = reducer(
|
||||
{ create: { loading: true } },
|
||||
createUserSuccess()
|
||||
);
|
||||
expect(newState.create.loading).toBeFalsy();
|
||||
expect(newState.create.error).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should set the loading to false and the error if user could not be created", () => {
|
||||
const newState = reducer(
|
||||
{ create: { loading: true, error: null } },
|
||||
createUserFailure(new Error("kaputt kaputt"))
|
||||
);
|
||||
expect(newState.create.loading).toBeFalsy();
|
||||
expect(newState.create.error).toEqual(new Error("kaputt kaputt"));
|
||||
});
|
||||
|
||||
it("should reset the user create form", () => {
|
||||
const newState = reducer(
|
||||
{ create: { loading: true, error: new Error("kaputt kaputt") } },
|
||||
createUserReset()
|
||||
);
|
||||
expect(newState.create.loading).toBeFalsy();
|
||||
expect(newState.create.error).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should update state according to FETCH_USER_PENDING action", () => {
|
||||
const newState = reducer({}, fetchUserPending("zaphod"));
|
||||
expect(newState.byNames["zaphod"].loading).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should not affect list state", () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
list: {
|
||||
entries: ["ford"]
|
||||
}
|
||||
},
|
||||
fetchUserPending("zaphod")
|
||||
);
|
||||
expect(newState.byNames["zaphod"].loading).toBeTruthy();
|
||||
expect(newState.list.entries).toEqual(["ford"]);
|
||||
});
|
||||
|
||||
it("should update state according to FETCH_USER_FAILURE action", () => {
|
||||
const error = new Error("kaputt!");
|
||||
const newState = reducer({}, fetchUserFailure(userFord.name, error));
|
||||
expect(newState.byNames["ford"].error).toBe(error);
|
||||
expect(newState.byNames["ford"].loading).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should update state according to FETCH_USER_SUCCESS action", () => {
|
||||
const newState = reducer({}, fetchUserSuccess(userFord));
|
||||
expect(newState.byNames["ford"].loading).toBeFalsy();
|
||||
expect(newState.byNames["ford"].entry).toBe(userFord);
|
||||
expect(newState.byNames["ford"]).toBe(userFord);
|
||||
});
|
||||
|
||||
it("should affect users state nor the state of other users", () => {
|
||||
@@ -572,62 +392,22 @@ describe("users reducer", () => {
|
||||
},
|
||||
fetchUserSuccess(userFord)
|
||||
);
|
||||
expect(newState.byNames["ford"].loading).toBeFalsy();
|
||||
expect(newState.byNames["ford"].entry).toBe(userFord);
|
||||
expect(newState.byNames["ford"]).toBe(userFord);
|
||||
expect(newState.list.entries).toEqual(["zaphod"]);
|
||||
});
|
||||
|
||||
it("should update state according to MODIFY_USER_PENDING action", () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
byNames: {
|
||||
ford: {
|
||||
error: new Error("something"),
|
||||
entry: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
modifyUserPending(userFord)
|
||||
);
|
||||
expect(newState.byNames["ford"].loading).toBeTruthy();
|
||||
expect(newState.byNames["ford"].error).toBeFalsy();
|
||||
expect(newState.byNames["ford"].entry).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should update state according to MODIFY_USER_SUCCESS action", () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
byNames: {
|
||||
ford: {
|
||||
loading: true,
|
||||
error: new Error("something"),
|
||||
entry: {}
|
||||
name: "ford"
|
||||
}
|
||||
}
|
||||
},
|
||||
modifyUserSuccess(userFord)
|
||||
);
|
||||
expect(newState.byNames["ford"].loading).toBeFalsy();
|
||||
expect(newState.byNames["ford"].error).toBeFalsy();
|
||||
expect(newState.byNames["ford"].entry).toBe(userFord);
|
||||
});
|
||||
|
||||
it("should update state according to MODIFY_USER_SUCCESS action", () => {
|
||||
const error = new Error("something went wrong");
|
||||
const newState = reducer(
|
||||
{
|
||||
byNames: {
|
||||
ford: {
|
||||
loading: true,
|
||||
entry: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
modifyUserFailure(userFord, error)
|
||||
);
|
||||
expect(newState.byNames["ford"].loading).toBeFalsy();
|
||||
expect(newState.byNames["ford"].error).toBe(error);
|
||||
expect(newState.byNames["ford"].entry).toBeFalsy();
|
||||
expect(newState.byNames["ford"]).toBe(userFord);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export type UserEntry = {
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
entry: User
|
||||
};
|
||||
Reference in New Issue
Block a user