mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-03-11 14:50:19 +01:00
Merged in feature/ui_users_paging (pull request #53)
Feature/ui users paging
This commit is contained in:
@@ -32,5 +32,9 @@
|
||||
"repositories": "Repositories",
|
||||
"users": "Users",
|
||||
"logout": "Logout"
|
||||
},
|
||||
"paginator": {
|
||||
"next": "Next",
|
||||
"previous": "Previous"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
},
|
||||
"users": {
|
||||
"title": "Users",
|
||||
"subtitle": "Create, read, update and delete users",
|
||||
"add-button": "Add User"
|
||||
"subtitle": "Create, read, update and delete users"
|
||||
},
|
||||
"create-user-button": {
|
||||
"label": "Create"
|
||||
},
|
||||
"delete-user-button": {
|
||||
"label": "Delete",
|
||||
|
||||
120
scm-ui/src/components/Paginator.js
Normal file
120
scm-ui/src/components/Paginator.js
Normal file
@@ -0,0 +1,120 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { PagedCollection } from "../types/Collection";
|
||||
import { Button } from "./buttons";
|
||||
|
||||
type Props = {
|
||||
collection: PagedCollection,
|
||||
onPageChange: string => void,
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class Paginator extends React.Component<Props> {
|
||||
isLinkUnavailable(linkType: string) {
|
||||
return !this.props.collection || !this.props.collection._links[linkType];
|
||||
}
|
||||
|
||||
createAction = (linkType: string) => () => {
|
||||
const { collection, onPageChange } = this.props;
|
||||
const link = collection._links[linkType].href;
|
||||
onPageChange(link);
|
||||
};
|
||||
|
||||
renderFirstButton() {
|
||||
return this.renderPageButton(1, "first");
|
||||
}
|
||||
|
||||
renderPreviousButton() {
|
||||
const { t } = this.props;
|
||||
return this.renderButton(
|
||||
"pagination-previous",
|
||||
t("paginator.previous"),
|
||||
"prev"
|
||||
);
|
||||
}
|
||||
|
||||
renderNextButton() {
|
||||
const { t } = this.props;
|
||||
return this.renderButton("pagination-next", t("paginator.next"), "next");
|
||||
}
|
||||
|
||||
renderLastButton() {
|
||||
const { collection } = this.props;
|
||||
return this.renderPageButton(collection.pageTotal, "last");
|
||||
}
|
||||
|
||||
renderPageButton(page: number, linkType: string) {
|
||||
return this.renderButton("pagination-link", page.toString(), linkType);
|
||||
}
|
||||
|
||||
renderButton(className: string, label: string, linkType: string) {
|
||||
return (
|
||||
<Button
|
||||
className={className}
|
||||
label={label}
|
||||
disabled={this.isLinkUnavailable(linkType)}
|
||||
action={this.createAction(linkType)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
seperator() {
|
||||
return <span className="pagination-ellipsis">…</span>;
|
||||
}
|
||||
|
||||
currentPage(page: number) {
|
||||
return (
|
||||
<Button
|
||||
className="pagination-link is-current"
|
||||
label={page}
|
||||
disabled={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
pageLinks() {
|
||||
const { collection } = this.props;
|
||||
|
||||
const links = [];
|
||||
const page = collection.page + 1;
|
||||
const pageTotal = collection.pageTotal;
|
||||
if (page > 1) {
|
||||
links.push(this.renderFirstButton());
|
||||
}
|
||||
if (page > 3) {
|
||||
links.push(this.seperator());
|
||||
}
|
||||
if (page > 2) {
|
||||
links.push(this.renderPageButton(page - 1, "prev"));
|
||||
}
|
||||
|
||||
links.push(this.currentPage(page));
|
||||
|
||||
if (page + 1 < pageTotal) {
|
||||
links.push(this.renderPageButton(page + 1, "next"));
|
||||
links.push(this.seperator());
|
||||
}
|
||||
if (page < pageTotal) {
|
||||
links.push(this.renderLastButton());
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<nav className="pagination is-centered" aria-label="pagination">
|
||||
{this.renderPreviousButton()}
|
||||
{this.renderNextButton()}
|
||||
<ul className="pagination-list">
|
||||
{this.pageLinks().map((link, index) => {
|
||||
return <li key={index}>{link}</li>;
|
||||
})}
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("commons")(Paginator);
|
||||
253
scm-ui/src/components/Paginator.test.js
Normal file
253
scm-ui/src/components/Paginator.test.js
Normal file
@@ -0,0 +1,253 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import "../tests/enzyme";
|
||||
import "../tests/i18n";
|
||||
|
||||
import Paginator from "./Paginator";
|
||||
|
||||
describe("paginator rendering tests", () => {
|
||||
const dummyLink = {
|
||||
href: "https://dummy"
|
||||
};
|
||||
|
||||
it("should render all buttons but disabled, without links", () => {
|
||||
const collection = {
|
||||
page: 10,
|
||||
pageTotal: 20,
|
||||
_links: {}
|
||||
};
|
||||
|
||||
const paginator = shallow(<Paginator collection={collection} />);
|
||||
const buttons = paginator.find("Button");
|
||||
expect(buttons.length).toBe(7);
|
||||
for (let button of buttons) {
|
||||
expect(button.props.disabled).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it("should render buttons for first page", () => {
|
||||
const collection = {
|
||||
page: 0,
|
||||
pageTotal: 148,
|
||||
_links: {
|
||||
first: dummyLink,
|
||||
next: dummyLink,
|
||||
last: dummyLink
|
||||
}
|
||||
};
|
||||
|
||||
const paginator = shallow(<Paginator collection={collection} />);
|
||||
const buttons = paginator.find("Button");
|
||||
expect(buttons.length).toBe(5);
|
||||
|
||||
// previous button
|
||||
expect(buttons.get(0).props.disabled).toBeTruthy();
|
||||
// last button
|
||||
expect(buttons.get(1).props.disabled).toBeFalsy();
|
||||
// first button
|
||||
const firstButton = buttons.get(2).props;
|
||||
expect(firstButton.disabled).toBeTruthy();
|
||||
expect(firstButton.label).toBe(1);
|
||||
|
||||
// next button
|
||||
const nextButton = buttons.get(3).props;
|
||||
expect(nextButton.disabled).toBeFalsy();
|
||||
expect(nextButton.label).toBe("2");
|
||||
|
||||
// last button
|
||||
const lastButton = buttons.get(4).props;
|
||||
expect(lastButton.disabled).toBeFalsy();
|
||||
expect(lastButton.label).toBe("148");
|
||||
});
|
||||
|
||||
it("should render buttons for second page", () => {
|
||||
const collection = {
|
||||
page: 1,
|
||||
pageTotal: 148,
|
||||
_links: {
|
||||
first: dummyLink,
|
||||
prev: dummyLink,
|
||||
next: dummyLink,
|
||||
last: dummyLink
|
||||
}
|
||||
};
|
||||
|
||||
const paginator = shallow(<Paginator collection={collection} />);
|
||||
const buttons = paginator.find("Button");
|
||||
expect(buttons.length).toBe(6);
|
||||
|
||||
// previous button
|
||||
expect(buttons.get(0).props.disabled).toBeFalsy();
|
||||
// last button
|
||||
expect(buttons.get(1).props.disabled).toBeFalsy();
|
||||
// first button
|
||||
const firstButton = buttons.get(2).props;
|
||||
expect(firstButton.disabled).toBeFalsy();
|
||||
expect(firstButton.label).toBe("1");
|
||||
|
||||
// current button
|
||||
const currentButton = buttons.get(3).props;
|
||||
expect(currentButton.disabled).toBeTruthy();
|
||||
expect(currentButton.label).toBe(2);
|
||||
|
||||
// next button
|
||||
const nextButton = buttons.get(4).props;
|
||||
expect(nextButton.disabled).toBeFalsy();
|
||||
expect(nextButton.label).toBe("3");
|
||||
|
||||
// last button
|
||||
const lastButton = buttons.get(5).props;
|
||||
expect(lastButton.disabled).toBeFalsy();
|
||||
expect(lastButton.label).toBe("148");
|
||||
});
|
||||
|
||||
it("should render buttons for last page", () => {
|
||||
const collection = {
|
||||
page: 147,
|
||||
pageTotal: 148,
|
||||
_links: {
|
||||
first: dummyLink,
|
||||
prev: dummyLink
|
||||
}
|
||||
};
|
||||
|
||||
const paginator = shallow(<Paginator collection={collection} />);
|
||||
const buttons = paginator.find("Button");
|
||||
expect(buttons.length).toBe(5);
|
||||
|
||||
// previous button
|
||||
expect(buttons.get(0).props.disabled).toBeFalsy();
|
||||
// last button
|
||||
expect(buttons.get(1).props.disabled).toBeTruthy();
|
||||
// first button
|
||||
const firstButton = buttons.get(2).props;
|
||||
expect(firstButton.disabled).toBeFalsy();
|
||||
expect(firstButton.label).toBe("1");
|
||||
|
||||
// next button
|
||||
const nextButton = buttons.get(3).props;
|
||||
expect(nextButton.disabled).toBeFalsy();
|
||||
expect(nextButton.label).toBe("147");
|
||||
|
||||
// last button
|
||||
const lastButton = buttons.get(4).props;
|
||||
expect(lastButton.disabled).toBeTruthy();
|
||||
expect(lastButton.label).toBe(148);
|
||||
});
|
||||
|
||||
it("should render buttons for penultimate page", () => {
|
||||
const collection = {
|
||||
page: 146,
|
||||
pageTotal: 148,
|
||||
_links: {
|
||||
first: dummyLink,
|
||||
prev: dummyLink,
|
||||
next: dummyLink,
|
||||
last: dummyLink
|
||||
}
|
||||
};
|
||||
|
||||
const paginator = shallow(<Paginator collection={collection} />);
|
||||
const buttons = paginator.find("Button");
|
||||
expect(buttons.length).toBe(6);
|
||||
|
||||
// previous button
|
||||
expect(buttons.get(0).props.disabled).toBeFalsy();
|
||||
// last button
|
||||
expect(buttons.get(1).props.disabled).toBeFalsy();
|
||||
|
||||
// first button
|
||||
const firstButton = buttons.get(2).props;
|
||||
expect(firstButton.disabled).toBeFalsy();
|
||||
expect(firstButton.label).toBe("1");
|
||||
|
||||
const currentButton = buttons.get(3).props;
|
||||
expect(currentButton.disabled).toBeFalsy();
|
||||
expect(currentButton.label).toBe("146");
|
||||
|
||||
// current button
|
||||
const nextButton = buttons.get(4).props;
|
||||
expect(nextButton.disabled).toBeTruthy();
|
||||
expect(nextButton.label).toBe(147);
|
||||
|
||||
// last button
|
||||
const lastButton = buttons.get(5).props;
|
||||
expect(lastButton.disabled).toBeFalsy();
|
||||
expect(lastButton.label).toBe("148");
|
||||
});
|
||||
|
||||
it("should render buttons for a page in the middle", () => {
|
||||
const collection = {
|
||||
page: 41,
|
||||
pageTotal: 148,
|
||||
_links: {
|
||||
first: dummyLink,
|
||||
prev: dummyLink,
|
||||
next: dummyLink,
|
||||
last: dummyLink
|
||||
}
|
||||
};
|
||||
|
||||
const paginator = shallow(<Paginator collection={collection} />);
|
||||
const buttons = paginator.find("Button");
|
||||
expect(buttons.length).toBe(7);
|
||||
|
||||
// previous button
|
||||
expect(buttons.get(0).props.disabled).toBeFalsy();
|
||||
// next button
|
||||
expect(buttons.get(1).props.disabled).toBeFalsy();
|
||||
|
||||
// first button
|
||||
const firstButton = buttons.get(2).props;
|
||||
expect(firstButton.disabled).toBeFalsy();
|
||||
expect(firstButton.label).toBe("1");
|
||||
|
||||
// previous Button
|
||||
const previousButton = buttons.get(3).props;
|
||||
expect(previousButton.disabled).toBeFalsy();
|
||||
expect(previousButton.label).toBe("41");
|
||||
|
||||
// current button
|
||||
const currentButton = buttons.get(4).props;
|
||||
expect(currentButton.disabled).toBeTruthy();
|
||||
expect(currentButton.label).toBe(42);
|
||||
|
||||
// next button
|
||||
const nextButton = buttons.get(5).props;
|
||||
expect(nextButton.disabled).toBeFalsy();
|
||||
expect(nextButton.label).toBe("43");
|
||||
|
||||
// last button
|
||||
const lastButton = buttons.get(6).props;
|
||||
expect(lastButton.disabled).toBeFalsy();
|
||||
expect(lastButton.label).toBe("148");
|
||||
});
|
||||
|
||||
it("should call the function with the last previous url", () => {
|
||||
const collection = {
|
||||
page: 41,
|
||||
pageTotal: 148,
|
||||
_links: {
|
||||
first: dummyLink,
|
||||
prev: {
|
||||
href: "https://www.scm-manager.org"
|
||||
},
|
||||
next: dummyLink,
|
||||
last: dummyLink
|
||||
}
|
||||
};
|
||||
|
||||
let urlToOpen;
|
||||
const callMe = (url: string) => {
|
||||
urlToOpen = url;
|
||||
};
|
||||
|
||||
const paginator = mount(
|
||||
<Paginator collection={collection} onPageChange={callMe} />
|
||||
);
|
||||
paginator.find("Button.pagination-previous").simulate("click");
|
||||
|
||||
expect(urlToOpen).toBe("https://www.scm-manager.org");
|
||||
});
|
||||
});
|
||||
@@ -9,7 +9,8 @@ export type ButtonProps = {
|
||||
disabled?: boolean,
|
||||
action?: () => void,
|
||||
link?: string,
|
||||
fullWidth?: boolean
|
||||
fullWidth?: boolean,
|
||||
className?: string
|
||||
};
|
||||
|
||||
type Props = ButtonProps & {
|
||||
@@ -17,8 +18,20 @@ type Props = ButtonProps & {
|
||||
};
|
||||
|
||||
class Button extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
type: "default"
|
||||
};
|
||||
|
||||
renderButton = () => {
|
||||
const { label, loading, disabled, type, action, fullWidth } = this.props;
|
||||
const {
|
||||
label,
|
||||
loading,
|
||||
disabled,
|
||||
type,
|
||||
action,
|
||||
fullWidth,
|
||||
className
|
||||
} = this.props;
|
||||
const loadingClass = loading ? "is-loading" : "";
|
||||
const fullWidthClass = fullWidth ? "is-fullwidth" : "";
|
||||
return (
|
||||
@@ -29,7 +42,8 @@ class Button extends React.Component<Props> {
|
||||
"button",
|
||||
"is-" + type,
|
||||
loadingClass,
|
||||
fullWidthClass
|
||||
fullWidthClass,
|
||||
className
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
|
||||
@@ -42,6 +42,12 @@ class Main extends React.Component<Props> {
|
||||
path="/users/add"
|
||||
component={AddUser}
|
||||
/>
|
||||
<ProtectedRoute
|
||||
exact
|
||||
path="/users/:page"
|
||||
component={Users}
|
||||
authenticated={authenticated}
|
||||
/>
|
||||
<ProtectedRoute
|
||||
authenticated={authenticated}
|
||||
path="/user/:name"
|
||||
|
||||
18
scm-ui/src/types/Collection.js
Normal file
18
scm-ui/src/types/Collection.js
Normal file
@@ -0,0 +1,18 @@
|
||||
// @flow
|
||||
import type { Links } from "./hal";
|
||||
|
||||
export type Collection = {
|
||||
_embedded: Object,
|
||||
_links: Links
|
||||
};
|
||||
|
||||
export type PagedCollection = Collection & {
|
||||
page: number,
|
||||
pageTotal: number
|
||||
};
|
||||
|
||||
export type PageCollectionStateSlice = {
|
||||
entry?: PagedCollection,
|
||||
error?: Error,
|
||||
loading?: boolean
|
||||
};
|
||||
30
scm-ui/src/users/components/buttons/CreateUserButton.js
Normal file
30
scm-ui/src/users/components/buttons/CreateUserButton.js
Normal file
@@ -0,0 +1,30 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import injectSheet from "react-jss";
|
||||
import { translate } from "react-i18next";
|
||||
import { AddButton } from "../../../components/buttons";
|
||||
import classNames from "classnames";
|
||||
|
||||
const styles = {
|
||||
spacing: {
|
||||
margin: "1em 0 0 1em"
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
classes: any
|
||||
};
|
||||
|
||||
class CreateUserButton extends React.Component<Props> {
|
||||
render() {
|
||||
const { classes, t } = this.props;
|
||||
return (
|
||||
<div className={classNames("is-pulled-right", classes.spacing)}>
|
||||
<AddButton label={t("create-user-button.label")} link="/users/add" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("users")(injectSheet(styles)(CreateUserButton));
|
||||
@@ -1,80 +1,127 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import type { History } from "history";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
|
||||
import { fetchUsers, getUsersFromState } from "../modules/users";
|
||||
import {
|
||||
fetchUsersByPage,
|
||||
fetchUsersByLink,
|
||||
getUsersFromState,
|
||||
selectListAsCollection,
|
||||
isPermittedToCreateUsers
|
||||
} from "../modules/users";
|
||||
|
||||
import { Page } from "../../components/layout";
|
||||
import { UserTable } from "./../components/table";
|
||||
import type { User } from "../types/User";
|
||||
import { AddButton } from "../../components/buttons";
|
||||
import type { UserEntry } from "../types/UserEntry";
|
||||
import type { PageCollectionStateSlice } from "../../types/Collection";
|
||||
import Paginator from "../../components/Paginator";
|
||||
import CreateUserButton from "../components/buttons/CreateUserButton";
|
||||
|
||||
type Props = {
|
||||
loading?: boolean,
|
||||
error: Error,
|
||||
userEntries: UserEntry[],
|
||||
canAddUsers: boolean,
|
||||
list: PageCollectionStateSlice,
|
||||
page: number,
|
||||
|
||||
// context objects
|
||||
t: string => string,
|
||||
userEntries: Array<UserEntry>,
|
||||
fetchUsers: () => void,
|
||||
canAddUsers: boolean
|
||||
history: History,
|
||||
|
||||
// dispatch functions
|
||||
fetchUsersByPage: (page: number) => void,
|
||||
fetchUsersByLink: (link: string) => void
|
||||
};
|
||||
|
||||
class Users extends React.Component<Props, User> {
|
||||
class Users extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
this.props.fetchUsers();
|
||||
this.props.fetchUsersByPage(this.props.page);
|
||||
}
|
||||
|
||||
onPageChange = (link: string) => {
|
||||
this.props.fetchUsersByLink(link);
|
||||
};
|
||||
|
||||
/**
|
||||
* reflect page transitions in the uri
|
||||
*/
|
||||
componentDidUpdate = (prevProps: Props) => {
|
||||
const { page, list } = this.props;
|
||||
if (list.entry) {
|
||||
// backend starts paging by 0
|
||||
const statePage: number = list.entry.page + 1;
|
||||
if (page !== statePage) {
|
||||
this.props.history.push(`/users/${statePage}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { userEntries, loading, t, error } = this.props;
|
||||
const { userEntries, list, t } = this.props;
|
||||
return (
|
||||
<Page
|
||||
title={t("users.title")}
|
||||
subtitle={t("users.subtitle")}
|
||||
loading={loading || !userEntries}
|
||||
error={error}
|
||||
loading={list.loading || !userEntries}
|
||||
error={list.error}
|
||||
>
|
||||
<UserTable entries={userEntries} />
|
||||
{this.renderAddButton()}
|
||||
{this.renderPaginator()}
|
||||
{this.renderCreateButton()}
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
renderAddButton() {
|
||||
const { canAddUsers, t } = this.props;
|
||||
if (canAddUsers) {
|
||||
renderPaginator() {
|
||||
const { list } = this.props;
|
||||
if (list.entry) {
|
||||
return (
|
||||
<div>
|
||||
<AddButton label={t("users.add-button")} link="/users/add" />
|
||||
</div>
|
||||
<Paginator collection={list.entry} onPageChange={this.onPageChange} />
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderCreateButton() {
|
||||
if (this.props.canAddUsers) {
|
||||
return <CreateUserButton />;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const userEntries = getUsersFromState(state);
|
||||
let error = null;
|
||||
let loading = false;
|
||||
let canAddUsers = false;
|
||||
if (state.users && state.users.list) {
|
||||
error = state.users.list.error;
|
||||
canAddUsers = state.users.list.userCreatePermission;
|
||||
loading = state.users.list.loading;
|
||||
const getPageFromProps = props => {
|
||||
let page = props.match.params.page;
|
||||
if (page) {
|
||||
page = parseInt(page, 10);
|
||||
} else {
|
||||
page = 1;
|
||||
}
|
||||
return page;
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const page = getPageFromProps(ownProps);
|
||||
const userEntries = getUsersFromState(state);
|
||||
const canAddUsers = isPermittedToCreateUsers(state);
|
||||
const list = selectListAsCollection(state);
|
||||
return {
|
||||
userEntries,
|
||||
error,
|
||||
loading,
|
||||
canAddUsers
|
||||
canAddUsers,
|
||||
list,
|
||||
page
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchUsers: () => {
|
||||
dispatch(fetchUsers());
|
||||
fetchUsersByPage: (page: number) => {
|
||||
dispatch(fetchUsersByPage(page));
|
||||
},
|
||||
fetchUsersByLink: (link: string) => {
|
||||
dispatch(fetchUsersByLink(link));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ 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";
|
||||
|
||||
export const FETCH_USERS_PENDING = "scm/users/FETCH_USERS_PENDING";
|
||||
export const FETCH_USERS_SUCCESS = "scm/users/FETCH_USERS_SUCCESS";
|
||||
@@ -35,18 +36,20 @@ const CONTENT_TYPE_USER = "application/vnd.scmm-user+json;v=2";
|
||||
//fetch users
|
||||
|
||||
export function fetchUsers() {
|
||||
return fetchUsersByLink(USERS_URL);
|
||||
}
|
||||
|
||||
export function fetchUsersByPage(page: number) {
|
||||
// backend start counting by 0
|
||||
return fetchUsersByLink(USERS_URL + "?page=" + (page - 1));
|
||||
}
|
||||
|
||||
export function fetchUsersByLink(link: string) {
|
||||
return function(dispatch: any) {
|
||||
dispatch(fetchUsersPending());
|
||||
return apiClient
|
||||
.get(USERS_URL)
|
||||
.then(response => {
|
||||
return response;
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
})
|
||||
.get(link)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
dispatch(fetchUsersSuccess(data));
|
||||
})
|
||||
@@ -348,7 +351,12 @@ function listReducer(state: any = {}, action: any = {}) {
|
||||
error: null,
|
||||
entries: userNames,
|
||||
loading: false,
|
||||
userCreatePermission: action.payload._links.create ? true : false
|
||||
entry: {
|
||||
userCreatePermission: action.payload._links.create ? true : false,
|
||||
page: action.payload.page,
|
||||
pageTotal: action.payload.pageTotal,
|
||||
_links: action.payload._links
|
||||
}
|
||||
};
|
||||
case FETCH_USERS_FAILURE:
|
||||
return {
|
||||
@@ -439,6 +447,36 @@ function byNamesReducer(state: any = {}, action: any = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
// selectors
|
||||
|
||||
const selectList = (state: Object) => {
|
||||
if (state.users && state.users.list) {
|
||||
return state.users.list;
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
const selectListEntry = (state: Object) => {
|
||||
const list = selectList(state);
|
||||
if (list.entry) {
|
||||
return list.entry;
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
export const selectListAsCollection = (
|
||||
state: Object
|
||||
): PageCollectionStateSlice => {
|
||||
return selectList(state);
|
||||
};
|
||||
|
||||
export const isPermittedToCreateUsers = (state: Object): boolean => {
|
||||
const permission = selectListEntry(state).userCreatePermission;
|
||||
if (permission) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
function createReducer(state: any = {}, action: any = {}) {
|
||||
switch (action.type) {
|
||||
case CREATE_USER_PENDING:
|
||||
|
||||
@@ -33,6 +33,8 @@ import reducer, {
|
||||
fetchUsersPending,
|
||||
fetchUsersSuccess,
|
||||
fetchUserSuccess,
|
||||
selectListAsCollection,
|
||||
isPermittedToCreateUsers,
|
||||
MODIFY_USER_FAILURE,
|
||||
MODIFY_USER_PENDING,
|
||||
MODIFY_USER_SUCCESS,
|
||||
@@ -336,7 +338,12 @@ describe("users reducer", () => {
|
||||
entries: ["zaphod", "ford"],
|
||||
error: null,
|
||||
loading: false,
|
||||
userCreatePermission: true
|
||||
entry: {
|
||||
userCreatePermission: true,
|
||||
page: 0,
|
||||
pageTotal: 1,
|
||||
_links: responseBody._links
|
||||
}
|
||||
});
|
||||
|
||||
expect(newState.byNames).toEqual({
|
||||
@@ -348,27 +355,27 @@ describe("users reducer", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("should set error when fetching users failed", () => {
|
||||
const oldState = {
|
||||
list: {
|
||||
loading: true
|
||||
}
|
||||
};
|
||||
expect(newState.list.entry.userCreatePermission).toBeTruthy();
|
||||
});
|
||||
|
||||
const error = new Error("kaputt");
|
||||
it("should set error when fetching users failed", () => {
|
||||
const oldState = {
|
||||
list: {
|
||||
loading: true
|
||||
}
|
||||
};
|
||||
|
||||
const newState = reducer(oldState, fetchUsersFailure("url.com", error));
|
||||
expect(newState.list.loading).toBeFalsy();
|
||||
expect(newState.list.error).toEqual(error);
|
||||
});
|
||||
const error = new Error("kaputt");
|
||||
|
||||
it("should set userCreatePermission to true if update link is present", () => {
|
||||
const newState = reducer({}, fetchUsersSuccess(responseBody));
|
||||
const newState = reducer(oldState, fetchUsersFailure("url.com", error));
|
||||
expect(newState.list.loading).toBeFalsy();
|
||||
expect(newState.list.error).toEqual(error);
|
||||
});
|
||||
|
||||
expect(newState.list.userCreatePermission).toBeTruthy();
|
||||
});
|
||||
it("should set userCreatePermission to true if update link is present", () => {
|
||||
const newState = reducer({}, fetchUsersSuccess(responseBody));
|
||||
|
||||
expect(newState.list.userCreatePermission).toBeTruthy();
|
||||
expect(newState.list.entry.userCreatePermission).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should not replace whole byNames map when fetching users", () => {
|
||||
@@ -385,7 +392,7 @@ describe("users reducer", () => {
|
||||
expect(newState.byNames["ford"]).toBeDefined();
|
||||
});
|
||||
|
||||
test("should update state correctly according to DELETE_USER_PENDING action", () => {
|
||||
it("should update state correctly according to DELETE_USER_PENDING action", () => {
|
||||
const state = {
|
||||
byNames: {
|
||||
zaphod: {
|
||||
@@ -479,8 +486,17 @@ describe("users reducer", () => {
|
||||
|
||||
const newState = reducer(state, deleteUserSuccess(userFord));
|
||||
expect(newState.byNames["zaphod"]).toBeDefined();
|
||||
expect(newState.list.entries).toEqual(["zaphod"]);
|
||||
expect(newState.byNames["ford"]).toBeFalsy();
|
||||
expect(newState.list.entries).toEqual(["zaphod"]);
|
||||
});
|
||||
|
||||
it("should set userCreatePermission to true if create link is present", () => {
|
||||
const newState = reducer({}, fetchUsersSuccess(responseBody));
|
||||
|
||||
expect(newState.list.entry.userCreatePermission).toBeTruthy();
|
||||
expect(newState.list.entries).toEqual(["zaphod", "ford"]);
|
||||
expect(newState.byNames["ford"]).toBeTruthy();
|
||||
expect(newState.byNames["zaphod"]).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should update state correctly according to CREATE_USER_PENDING action", () => {
|
||||
@@ -614,3 +630,46 @@ describe("users reducer", () => {
|
||||
expect(newState.byNames["ford"].entry).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("selector tests", () => {
|
||||
it("should return an empty object", () => {
|
||||
expect(selectListAsCollection({})).toEqual({});
|
||||
expect(selectListAsCollection({ users: { a: "a" } })).toEqual({});
|
||||
});
|
||||
|
||||
it("should return a state slice collection", () => {
|
||||
const state = {
|
||||
users: {
|
||||
list: {
|
||||
loading: false
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(selectListAsCollection(state)).toEqual({ loading: false });
|
||||
});
|
||||
|
||||
it("should return false", () => {
|
||||
expect(isPermittedToCreateUsers({})).toBe(false);
|
||||
expect(isPermittedToCreateUsers({ users: { list: { entry: {} } } })).toBe(
|
||||
false
|
||||
);
|
||||
expect(
|
||||
isPermittedToCreateUsers({
|
||||
users: { list: { entry: { userCreatePermission: false } } }
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true", () => {
|
||||
const state = {
|
||||
users: {
|
||||
list: {
|
||||
entry: {
|
||||
userCreatePermission: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(isPermittedToCreateUsers(state)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user