diff --git a/scm-ui/src/types/Collection.js b/scm-ui/src/types/Collection.js index ffcbf78166..901982a47a 100644 --- a/scm-ui/src/types/Collection.js +++ b/scm-ui/src/types/Collection.js @@ -10,3 +10,9 @@ export type PagedCollection = Collection & { page: number, pageTotal: number }; + +export type PageCollectionStateSlice = { + entry?: PagedCollection, + error?: Error, + loading?: boolean +}; diff --git a/scm-ui/src/users/containers/Users.js b/scm-ui/src/users/containers/Users.js index 34c2fb5915..8794954988 100644 --- a/scm-ui/src/users/containers/Users.js +++ b/scm-ui/src/users/containers/Users.js @@ -8,33 +8,33 @@ import { fetchUsersByPage, fetchUsersByLink, getUsersFromState, - selectList + 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 { PagedCollection } from "../../types/Collection"; +import type { PageCollectionStateSlice } from "../../types/Collection"; import Paginator from "../../components/Paginator"; import CreateUserButton from "../components/buttons/CreateUserButton"; type Props = { - loading?: boolean, - error: Error, - t: string => string, - userEntries: Array, - fetchUsersByPage: (page: number) => void, - fetchUsersByLink: (link: string) => void, + userEntries: UserEntry[], canAddUsers: boolean, - list?: PagedCollection, + list: PageCollectionStateSlice, + page: number, + + // context objects + t: string => string, history: History, - match: any, - page: number + + // dispatch functions + fetchUsersByPage: (page: number) => void, + fetchUsersByLink: (link: string) => void }; -class Users extends React.Component { +class Users extends React.Component { componentDidMount() { this.props.fetchUsersByPage(this.props.page); } @@ -43,11 +43,14 @@ class Users extends React.Component { this.props.fetchUsersByLink(link); }; + /** + * reflect page transitions in the uri + */ componentDidUpdate = (prevProps: Props) => { const { page, list } = this.props; - if (list) { - // backend starts with 0 - const statePage: number = list.page + 1; + if (list.entry) { + // backend starts paging by 0 + const statePage: number = list.entry.page + 1; if (page !== statePage) { this.props.history.push(`/users/${statePage}`); } @@ -55,30 +58,32 @@ class Users extends React.Component { }; render() { - const { userEntries, list, loading, t, error } = this.props; + const { userEntries, list, t } = this.props; return ( {this.renderPaginator()} - {this.renderAddButton()} + {this.renderCreateButton()} ); } renderPaginator() { const { list } = this.props; - if (list) { - return ; + if (list.entry) { + return ( + + ); } return null; } - renderAddButton() { + renderCreateButton() { if (this.props.canAddUsers) { return ; } else { @@ -87,27 +92,23 @@ class Users extends React.Component { } } -const mapStateToProps = (state, ownProps) => { - let page = ownProps.match.params.page; +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); - 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 list = selectList(state); + const canAddUsers = isPermittedToCreateUsers(state); + const list = selectListAsCollection(state); return { userEntries, - error, - loading, canAddUsers, list, page diff --git a/scm-ui/src/users/modules/users.js b/scm-ui/src/users/modules/users.js index 8a30072ec5..2aaa4b5adb 100644 --- a/scm-ui/src/users/modules/users.js +++ b/scm-ui/src/users/modules/users.js @@ -4,6 +4,7 @@ import type { User } from "../types/User"; import type { UserEntry } from "../types/UserEntry"; import { 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"; @@ -345,12 +346,14 @@ export default function reducer(state: any = {}, action: any = {}) { ...state, list: { error: null, - entries: userNames, loading: false, - userCreatePermission: action.payload._links.create ? true : false, - page: action.payload.page, - pageTotal: action.payload.pageTotal, - _links: action.payload._links + entries: userNames, + entry: { + userCreatePermission: action.payload._links.create ? true : false, + page: action.payload.page, + pageTotal: action.payload.pageTotal, + _links: action.payload._links + } }, byNames }; @@ -458,8 +461,31 @@ export default function reducer(state: any = {}, action: any = {}) { // selectors -export const selectList = (state: Object) => { - if (state.users && state.users.list && state.users.list._links) { +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; }; diff --git a/scm-ui/src/users/modules/users.test.js b/scm-ui/src/users/modules/users.test.js index d79bacb4b6..1322a3ca54 100644 --- a/scm-ui/src/users/modules/users.test.js +++ b/scm-ui/src/users/modules/users.test.js @@ -38,7 +38,9 @@ import { deleteUserSuccess, fetchUsersPending, fetchUserPending, - fetchUserFailure + fetchUserFailure, + selectListAsCollection, + isPermittedToCreateUsers } from "./users"; import reducer from "./users"; @@ -359,10 +361,12 @@ describe("users reducer", () => { entries: ["zaphod", "ford"], error: null, loading: false, - userCreatePermission: true, - page: 0, - pageTotal: 1, - _links: responseBody._links + entry: { + userCreatePermission: true, + page: 0, + pageTotal: 1, + _links: responseBody._links + } }); expect(newState.byNames).toEqual({ @@ -374,7 +378,7 @@ describe("users reducer", () => { } }); - expect(newState.list.userCreatePermission).toBeTruthy(); + expect(newState.list.entry.userCreatePermission).toBeTruthy(); }); test("should update state correctly according to DELETE_USER action", () => { @@ -464,10 +468,10 @@ describe("users reducer", () => { expect(newState.byNames["ford"]).toBeDefined(); }); - it("should set userCreatePermission to true if update link is present", () => { + it("should set userCreatePermission to true if create link is present", () => { const newState = reducer({}, fetchUsersSuccess(responseBody)); - expect(newState.list.userCreatePermission).toBeTruthy(); + expect(newState.list.entry.userCreatePermission).toBeTruthy(); }); it("should update state correctly according to CREATE_USER_PENDING action", () => { @@ -577,3 +581,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); + }); +}); diff --git a/scm-ui/src/users/types/User.js b/scm-ui/src/users/types/User.js index 0d85aa179c..b113f7f795 100644 --- a/scm-ui/src/users/types/User.js +++ b/scm-ui/src/users/types/User.js @@ -1,5 +1,5 @@ //@flow -import type { Link, Links } from "../../types/hal"; +import type { Links } from "../../types/hal"; export type User = { displayName: string,