show group table in ui

This commit is contained in:
Maren Süwer
2018-07-31 13:04:09 +02:00
parent ce3adaa1b5
commit 9c7c2c9d9a
10 changed files with 413 additions and 7 deletions

View File

@@ -0,0 +1,10 @@
{
"group": {
"name": "Name",
"description": "Description"
},
"groups": {
"title": "Groups",
"subtitle": "Create, read, update and delete groups"
}
}

View File

@@ -5,6 +5,7 @@ import { createStore, compose, applyMiddleware, combineReducers } from "redux";
import { routerReducer, routerMiddleware } from "react-router-redux";
import users from "./users/modules/users";
import groups from "./groups/modules/groups";
import auth from "./modules/auth";
import pending from "./modules/pending";
import failure from "./modules/failure";
@@ -20,6 +21,7 @@ function createReduxStore(history: BrowserHistory) {
pending,
failure,
users,
groups,
auth
});

View File

@@ -0,0 +1,32 @@
//@flow
import React from "react";
import type { Group } from "../../types/Group";
import { translate } from "react-i18next";
import { Checkbox } from "../../../components/forms";
type Props = {
group: Group,
t: string => string
};
class Details extends React.Component<Props> {
render() {
const { group, t } = this.props;
return (
<table className="table">
<tbody>
<tr>
<td>{t("group.name")}</td>
<td>{group.name}</td>
</tr>
<tr>
<td>{t("group.description")}</td>
<td>{group.description}</td>
</tr>
</tbody>
</table>
);
}
}
export default translate("groups")(Details);

View File

@@ -0,0 +1,25 @@
// @flow
import React from "react";
import { Link } from "react-router-dom";
import type { Group } from "../../types/Group";
type Props = {
group: Group
};
export default class GroupRow extends React.Component<Props> {
renderLink(to: string, label: string) {
return <Link to={to}>{label}</Link>;
}
render() {
const { group } = this.props;
const to = `/group/${group.name}`;
return (
<tr>
<td className="is-hidden-mobile">{this.renderLink(to, group.name)}</td>
<td>{this.renderLink(to, group.description)}</td>
</tr>
);
}
}

View File

@@ -0,0 +1,33 @@
// @flow
import React from "react";
import { translate } from "react-i18next";
import GroupRow from "./GroupRow";
import type { Group } from "../../types/Group";
type Props = {
t: string => string,
groups: Group[]
};
class GroupTable extends React.Component<Props> {
render() {
const { groups, t } = this.props;
return (
<table className="table is-hoverable is-fullwidth">
<thead>
<tr>
<th className="is-hidden-mobile">{t("group.name")}</th>
<th>{t("group.description")}</th>
</tr>
</thead>
<tbody>
{groups.map((group, index) => {
return <GroupRow key={index} group={group} />;
})}
</tbody>
</table>
);
}
}
export default translate("groups")(GroupTable);

View File

@@ -0,0 +1,3 @@
export { default as Details } from "./Details";
export { default as GroupRow } from "./GroupRow";
export { default as GroupTable } from "./GroupTable";

View File

@@ -1,24 +1,139 @@
//@flow
import React from "react";
import { connect } from "react-redux";
import { translate } from "react-i18next";
import type { Group } from "../types/Group.js";
import type { PagedCollection } from "../../types/Collection";
import type { History } from "history";
import { Page } from "../../components/layout";
import { GroupTable } from "./../components/table";
import Paginator from "../../components/Paginator";
type Props = {};
import {
fetchGroupsByPage,
fetchGroupsByLink,
getGroupsFromState,
isFetchGroupsPending,
getFetchGroupsFailure,
isPermittedToCreateGroups,
selectListAsCollection
} from "../modules/groups";
type Props = {
groups: Group[],
loading: boolean,
error: Error,
canAddGroups: boolean,
list: PagedCollection,
page: number,
// context objects
t: string => string,
history: History,
// dispatch functions
fetchGroupsByPage: (page: number) => void,
fetchGroupsByLink: (link: string) => void
};
class Groups extends React.Component<Props> {
componentDidMount() {
this.props.fetchGroupsByPage(this.props.page);
}
onPageChange = (link: string) => {
this.props.fetchGroupsByLink(link);
};
/**
* reflect page transitions in the uri
*/
componentDidUpdate = (prevProps: Props) => {
const { page, list } = this.props;
if (list.page) {
// backend starts paging by 0
const statePage: number = list.page + 1;
if (page !== statePage) {
this.props.history.push(`/groups/${statePage}`);
}
}
};
render() {
return "groups will be displayed here";
const { groups, loading, error, t } = this.props;
return (
<Page
title={t("groups.title")}
subtitle={t("groups.subtitle")}
loading={loading || !groups}
error={error}
>
<GroupTable groups={groups} />
{this.renderPaginator()}
{this.renderCreateButton()}
</Page>
);
}
renderPaginator() {
const { list } = this.props;
if (list) {
return <Paginator collection={list} onPageChange={this.onPageChange} />;
}
return null;
}
renderCreateButton() {
/* if (this.props.canAddGroups) {
return <CreateGroupButton />;
} else {
return;
}*/
}
}
const mapStateToProps = state => {
return {};
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 groups = getGroupsFromState(state);
const loading = isFetchGroupsPending(state);
const error = getFetchGroupsFailure(state);
const page = getPageFromProps(ownProps);
const canAddGroups = isPermittedToCreateGroups(state);
const list = selectListAsCollection(state);
return {
groups,
loading,
error,
canAddGroups,
list,
page
};
};
const mapDispatchToProps = (dispatch) => {
return {};
return {
fetchGroupsByPage: (page: number) => {
dispatch(fetchGroupsByPage(page));
},
fetchGroupsByLink: (link: string) => {
dispatch(fetchGroupsByLink(link));
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Groups);
)(translate("groups")(Groups));

View File

@@ -5,6 +5,7 @@ import * as types from "../../modules/types";
import { combineReducers, Dispatch } from "redux";
import type { Action } from "../../types/Action";
import type { PagedCollection } from "../../types/Collection";
import type {Groups} from "../types/Groups";
export const FETCH_GROUPS = "scm/groups/FETCH_GROUPS";
export const FETCH_GROUPS_PENDING = `${FETCH_GROUPS}_${types.PENDING_SUFFIX}`;
@@ -154,3 +155,69 @@ export default combineReducers({
list: listReducer,
byNames: byNamesReducer
});
// selectors
const selectList = (state: Object) => {
if (state.groups && state.groups.list) {
return state.groups.list;
}
return {};
};
const selectListEntry = (state: Object): Object => {
const list = selectList(state);
if (list.entry) {
return list.entry;
}
return {};
};
export const selectListAsCollection = (state: Object): PagedCollection => {
return selectListEntry(state);
};
export const isPermittedToCreateGroups = (state: Object): boolean => {
const permission = selectListEntry(state).groupCreatePermission;
if (permission) {
return true;
}
return false;
};
export function getGroupsFromState(state: Object) {
const groupNames = selectList(state).entries;
if (!groupNames) {
return null;
}
const groupEntries: Group[] = [];
for (let groupName of groupNames) {
groupEntries.push(state.groups.byNames[groupName]);
}
return groupEntries;
}
export function isFetchGroupsPending(state: Object) {
return isPending(state, FETCH_GROUPS);
}
export function getFetchGroupsFailure(state: Object) {
return getFailure(state, FETCH_GROUPS);
}
export function isCreateGroupPending(state: Object) {
return isPending(state, CREATE_GROUP);
}
export function getCreateGroupFailure(state: Object) {
return getFailure(state, CREATE_GROUP);
}
export function getGroupByName(state: Object, name: string) {
if (state.groups && state.groups.byNames) {
return state.groups.byNames[name];
}
}

View File

@@ -5,13 +5,23 @@ import fetchMock from "fetch-mock";
import reducer, {
fetchGroups,
FETCH_GROUPS,
FETCH_GROUPS_PENDING,
FETCH_GROUPS_SUCCESS,
FETCH_GROUPS_FAILURE,
fetchGroupsSuccess
isFetchUsersPending,
fetchGroupsSuccess,
isPermittedToCreateGroups,
getGroupsFromState,
getFetchGroupsFailure,
isFetchGroupsPending,
selectListAsCollection
} from "./groups"
const GROUPS_URL = "/scm/api/rest/v2/groups";
const error = new Error("You have an error!");
const groupZaphod = {
creationDate: "2018-07-31T08:39:07.860Z",
description: "This is a group",
@@ -182,3 +192,95 @@ describe("groups reducer", () => {
expect(newState.byNames["zaphodGroup"]).toBeTruthy();
});
});
describe("selector tests", () => {
it("should return an empty object", () => {
expect(selectListAsCollection({})).toEqual({});
expect(selectListAsCollection({ groups: { a: "a" } })).toEqual({});
});
it("should return a state slice collection", () => {
const collection = {
page: 3,
totalPages: 42
};
const state = {
groups: {
list: {
entry: collection
}
}
};
expect(selectListAsCollection(state)).toBe(collection);
});
it("should return false", () => {
expect(isPermittedToCreateGroups({})).toBe(false);
expect(isPermittedToCreateGroups({ groups: { list: { entry: {} } } })).toBe(
false
);
expect(
isPermittedToCreateGroups({
groups: { list: { entry: { groupCreatePermission: false } } }
})
).toBe(false);
});
it("should return true", () => {
const state = {
groups: {
list: {
entry: {
groupCreatePermission: true
}
}
}
};
expect(isPermittedToCreateGroups(state)).toBe(true);
});
it("should get groups from state", () => {
const state = {
groups: {
list: {
entries: ["a", "b"]
},
byNames: {
a: { name: "a" },
b: { name: "b" }
}
}
};
expect(getGroupsFromState(state)).toEqual([{ name: "a" }, { name: "b" }]);
});
it("should return true, when fetch groups is pending", () => {
const state = {
pending: {
[FETCH_GROUPS]: true
}
};
expect(isFetchGroupsPending(state)).toEqual(true);
});
it("should return false, when fetch groups is not pending", () => {
expect(isFetchGroupsPending({})).toEqual(false);
});
it("should return error when fetch groups did fail", () => {
const state = {
failure: {
[FETCH_GROUPS]: error
}
};
expect(getFetchGroupsFailure(state)).toEqual(error);
});
it("should return undefined when fetch users did not fail", () => {
expect(getFetchGroupsFailure({})).toBe(undefined);
});
});

View File

@@ -0,0 +1,17 @@
//@flow
import type { Links } from "../../types/hal";
import type { User } from "../../users/types/User";
export type Group = {
name: string,
creationDate: string,
description: string,
lastModified: string,
type: string,
properties: [],
members: string[],
_links: Links,
_embedded: {
members: User[]
}
};