From b93da611cdf9b7c8d4896af2100c1c2fdad7767b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 9 Oct 2018 16:17:25 +0200 Subject: [PATCH] use index resources for groups --- scm-ui/src/config/containers/GlobalConfig.js | 16 ++-- scm-ui/src/config/modules/config.js | 7 +- scm-ui/src/config/modules/config.test.js | 4 +- scm-ui/src/containers/Login.js | 6 +- scm-ui/src/groups/containers/AddGroup.js | 17 +++-- scm-ui/src/groups/containers/Groups.js | 15 ++-- scm-ui/src/groups/containers/SingleGroup.js | 14 ++-- scm-ui/src/groups/modules/groups.js | 25 ++++--- scm-ui/src/groups/modules/groups.test.js | 77 ++++++++++++-------- 9 files changed, 112 insertions(+), 69 deletions(-) diff --git a/scm-ui/src/config/containers/GlobalConfig.js b/scm-ui/src/config/containers/GlobalConfig.js index 09bdc4b7c5..252e880a42 100644 --- a/scm-ui/src/config/containers/GlobalConfig.js +++ b/scm-ui/src/config/containers/GlobalConfig.js @@ -14,18 +14,20 @@ import { modifyConfigReset } from "../modules/config"; import { connect } from "react-redux"; -import type { Config } from "@scm-manager/ui-types"; +import type { Config, Link } from "@scm-manager/ui-types"; import ConfigForm from "../components/form/ConfigForm"; +import { getConfigLink } from "../../modules/indexResource"; type Props = { loading: boolean, error: Error, config: Config, configUpdatePermission: boolean, + configLink: string, // dispatch functions modifyConfig: (config: Config, callback?: () => void) => void, - fetchConfig: void => void, + fetchConfig: (link: string) => void, configReset: void => void, // context objects @@ -35,7 +37,7 @@ type Props = { class GlobalConfig extends React.Component { componentDidMount() { this.props.configReset(); - this.props.fetchConfig(); + this.props.fetchConfig(this.props.configLink); } modifyConfig = (config: Config) => { @@ -75,8 +77,8 @@ class GlobalConfig extends React.Component { const mapDispatchToProps = dispatch => { return { - fetchConfig: () => { - dispatch(fetchConfig()); + fetchConfig: (link: string) => { + dispatch(fetchConfig(link)); }, modifyConfig: (config: Config, callback?: () => void) => { dispatch(modifyConfig(config, callback)); @@ -92,12 +94,14 @@ const mapStateToProps = state => { const error = getFetchConfigFailure(state) || getModifyConfigFailure(state); const config = getConfig(state); const configUpdatePermission = getConfigUpdatePermission(state); + const configLink = getConfigLink(state); return { loading, error, config, - configUpdatePermission + configUpdatePermission, + configLink }; }; diff --git a/scm-ui/src/config/modules/config.js b/scm-ui/src/config/modules/config.js index 76ab2dee5e..352afefb70 100644 --- a/scm-ui/src/config/modules/config.js +++ b/scm-ui/src/config/modules/config.js @@ -6,7 +6,6 @@ import { isPending } from "../../modules/pending"; import { getFailure } from "../../modules/failure"; import { Dispatch } from "redux"; import type { Config } from "@scm-manager/ui-types"; -import { getConfigLink } from "../../modules/indexResource"; export const FETCH_CONFIG = "scm/config/FETCH_CONFIG"; export const FETCH_CONFIG_PENDING = `${FETCH_CONFIG}_${types.PENDING_SUFFIX}`; @@ -22,11 +21,11 @@ export const MODIFY_CONFIG_RESET = `${MODIFY_CONFIG}_${types.RESET_SUFFIX}`; const CONTENT_TYPE_CONFIG = "application/vnd.scmm-config+json;v=2"; //fetch config -export function fetchConfig() { - return function(dispatch: any, getState: any) { +export function fetchConfig(link: string) { + return function(dispatch: any) { dispatch(fetchConfigPending()); return apiClient - .get(getConfigLink(getState())) + .get(link) .then(response => { return response.json(); }) diff --git a/scm-ui/src/config/modules/config.test.js b/scm-ui/src/config/modules/config.test.js index 12cbe8ebd2..12c6b347c3 100644 --- a/scm-ui/src/config/modules/config.test.js +++ b/scm-ui/src/config/modules/config.test.js @@ -125,7 +125,7 @@ describe("config fetch()", () => { } }); - return store.dispatch(fetchConfig()).then(() => { + return store.dispatch(fetchConfig(CONFIG_URL)).then(() => { expect(store.getActions()).toEqual(expectedActions); }); }); @@ -144,7 +144,7 @@ describe("config fetch()", () => { } } }); - return store.dispatch(fetchConfig()).then(() => { + return store.dispatch(fetchConfig(CONFIG_URL)).then(() => { const actions = store.getActions(); expect(actions[0].type).toEqual(FETCH_CONFIG_PENDING); expect(actions[1].type).toEqual(FETCH_CONFIG_FAILURE); diff --git a/scm-ui/src/containers/Login.js b/scm-ui/src/containers/Login.js index 00e505e16d..e70afe14d2 100644 --- a/scm-ui/src/containers/Login.js +++ b/scm-ui/src/containers/Login.js @@ -18,6 +18,7 @@ import { Image } from "@scm-manager/ui-components"; import classNames from "classnames"; +import { fetchIndexResources } from "../modules/indexResource"; const styles = { avatar: { @@ -44,6 +45,7 @@ type Props = { // dispatcher props login: (username: string, password: string) => void, + fetchIndexResources: () => void, // context props t: string => string, @@ -87,6 +89,7 @@ class Login extends React.Component { } renderRedirect = () => { + this.props.fetchIndexResources(); const { from } = this.props.location.state || { from: { pathname: "/" } }; return ; }; @@ -155,7 +158,8 @@ const mapStateToProps = state => { const mapDispatchToProps = dispatch => { return { login: (username: string, password: string) => - dispatch(login(username, password)) + dispatch(login(username, password)), + fetchIndexResources: () => dispatch(fetchIndexResources()) }; }; diff --git a/scm-ui/src/groups/containers/AddGroup.js b/scm-ui/src/groups/containers/AddGroup.js index 0d3fc8ee2f..bcb19846b8 100644 --- a/scm-ui/src/groups/containers/AddGroup.js +++ b/scm-ui/src/groups/containers/AddGroup.js @@ -9,18 +9,21 @@ import { createGroup, isCreateGroupPending, getCreateGroupFailure, - createGroupReset + createGroupReset, + getCreateGroupLink } from "../modules/groups"; import type { Group } from "@scm-manager/ui-types"; import type { History } from "history"; +import { getGroupsLink } from "../../modules/indexResource"; type Props = { t: string => string, - createGroup: (group: Group, callback?: () => void) => void, + createGroup: (link: string, group: Group, callback?: () => void) => void, history: History, loading?: boolean, error?: Error, - resetForm: () => void + resetForm: () => void, + createLink: string }; type State = {}; @@ -51,14 +54,14 @@ class AddGroup extends React.Component { this.props.history.push("/groups"); }; createGroup = (group: Group) => { - this.props.createGroup(group, this.groupCreated); + this.props.createGroup(this.props.createLink, group, this.groupCreated); }; } const mapDispatchToProps = dispatch => { return { - createGroup: (group: Group, callback?: () => void) => - dispatch(createGroup(group, callback)), + createGroup: (link: string, group: Group, callback?: () => void) => + dispatch(createGroup(link, group, callback)), resetForm: () => { dispatch(createGroupReset()); } @@ -68,7 +71,9 @@ const mapDispatchToProps = dispatch => { const mapStateToProps = state => { const loading = isCreateGroupPending(state); const error = getCreateGroupFailure(state); + const createLink = getGroupsLink(state); return { + createLink, loading, error }; diff --git a/scm-ui/src/groups/containers/Groups.js b/scm-ui/src/groups/containers/Groups.js index 5fa1423f55..984055c60f 100644 --- a/scm-ui/src/groups/containers/Groups.js +++ b/scm-ui/src/groups/containers/Groups.js @@ -18,6 +18,7 @@ import { isPermittedToCreateGroups, selectListAsCollection } from "../modules/groups"; +import { getGroupsLink } from "../../modules/indexResource"; type Props = { groups: Group[], @@ -26,19 +27,20 @@ type Props = { canAddGroups: boolean, list: PagedCollection, page: number, + groupLink: string, // context objects t: string => string, history: History, // dispatch functions - fetchGroupsByPage: (page: number) => void, + fetchGroupsByPage: (link: string, page: number) => void, fetchGroupsByLink: (link: string) => void }; class Groups extends React.Component { componentDidMount() { - this.props.fetchGroupsByPage(this.props.page); + this.props.fetchGroupsByPage(this.props.groupLink, this.props.page); } onPageChange = (link: string) => { @@ -111,20 +113,23 @@ const mapStateToProps = (state, ownProps) => { const canAddGroups = isPermittedToCreateGroups(state); const list = selectListAsCollection(state); + const groupLink = getGroupsLink(state); + return { groups, loading, error, canAddGroups, list, - page + page, + groupLink }; }; const mapDispatchToProps = dispatch => { return { - fetchGroupsByPage: (page: number) => { - dispatch(fetchGroupsByPage(page)); + fetchGroupsByPage: (link: string, page: number) => { + dispatch(fetchGroupsByPage(link, page)); }, fetchGroupsByLink: (link: string) => { dispatch(fetchGroupsByLink(link)); diff --git a/scm-ui/src/groups/containers/SingleGroup.js b/scm-ui/src/groups/containers/SingleGroup.js index 9e0bcf74ef..d681859808 100644 --- a/scm-ui/src/groups/containers/SingleGroup.js +++ b/scm-ui/src/groups/containers/SingleGroup.js @@ -26,16 +26,18 @@ import { import { translate } from "react-i18next"; import EditGroup from "./EditGroup"; +import { getGroupsLink } from "../../modules/indexResource"; type Props = { name: string, group: Group, loading: boolean, error: Error, + groupLink: string, // dispatcher functions deleteGroup: (group: Group, callback?: () => void) => void, - fetchGroup: string => void, + fetchGroup: (string, string) => void, // context objects t: string => string, @@ -45,7 +47,7 @@ type Props = { class SingleGroup extends React.Component { componentDidMount() { - this.props.fetchGroup(this.props.name); + this.props.fetchGroup(this.props.groupLink, this.props.name); } stripEndingSlash = (url: string) => { @@ -132,19 +134,21 @@ const mapStateToProps = (state, ownProps) => { isFetchGroupPending(state, name) || isDeleteGroupPending(state, name); const error = getFetchGroupFailure(state, name) || getDeleteGroupFailure(state, name); + const groupLink = getGroupsLink(state); return { name, group, loading, - error + error, + groupLink }; }; const mapDispatchToProps = dispatch => { return { - fetchGroup: (name: string) => { - dispatch(fetchGroup(name)); + fetchGroup: (link: string, name: string) => { + dispatch(fetchGroup(link, name)); }, deleteGroup: (group: Group, callback?: () => void) => { dispatch(deleteGroup(group, callback)); diff --git a/scm-ui/src/groups/modules/groups.js b/scm-ui/src/groups/modules/groups.js index 5ba9587260..74e7214052 100644 --- a/scm-ui/src/groups/modules/groups.js +++ b/scm-ui/src/groups/modules/groups.js @@ -32,17 +32,16 @@ export const DELETE_GROUP_PENDING = `${DELETE_GROUP}_${types.PENDING_SUFFIX}`; export const DELETE_GROUP_SUCCESS = `${DELETE_GROUP}_${types.SUCCESS_SUFFIX}`; export const DELETE_GROUP_FAILURE = `${DELETE_GROUP}_${types.FAILURE_SUFFIX}`; -const GROUPS_URL = "groups"; const CONTENT_TYPE_GROUP = "application/vnd.scmm-group+json;v=2"; // fetch groups -export function fetchGroups() { - return fetchGroupsByLink(GROUPS_URL); +export function fetchGroups(link: string) { + return fetchGroupsByLink(link); } -export function fetchGroupsByPage(page: number) { +export function fetchGroupsByPage(link: string, page: number) { // backend start counting by 0 - return fetchGroupsByLink(GROUPS_URL + "?page=" + (page - 1)); + return fetchGroupsByLink(link + "?page=" + (page - 1)); } export function fetchGroupsByLink(link: string) { @@ -56,7 +55,7 @@ export function fetchGroupsByLink(link: string) { }) .catch(cause => { const error = new Error(`could not fetch groups: ${cause.message}`); - dispatch(fetchGroupsFailure(GROUPS_URL, error)); + dispatch(fetchGroupsFailure(link, error)); }); }; } @@ -85,8 +84,8 @@ export function fetchGroupsFailure(url: string, error: Error): Action { } //fetch group -export function fetchGroup(name: string) { - const groupUrl = GROUPS_URL + "/" + name; +export function fetchGroup(link: string, name: string) { + const groupUrl = link.endsWith("/") ? link + name : link + "/" + name; return function(dispatch: any) { dispatch(fetchGroupPending(name)); return apiClient @@ -132,11 +131,11 @@ export function fetchGroupFailure(name: string, error: Error): Action { } //create group -export function createGroup(group: Group, callback?: () => void) { +export function createGroup(link: string, group: Group, callback?: () => void) { return function(dispatch: Dispatch) { dispatch(createGroupPending()); return apiClient - .post(GROUPS_URL, group, CONTENT_TYPE_GROUP) + .post(link, group, CONTENT_TYPE_GROUP) .then(() => { dispatch(createGroupSuccess()); if (callback) { @@ -410,6 +409,12 @@ export const isPermittedToCreateGroups = (state: Object): boolean => { return false; }; +export function getCreateGroupLink(state: Object) { + if (state.groups.list.entry && state.groups.list.entry._links) + return state.groups.list.entry._links.create.href; + return undefined; +} + export function getGroupsFromState(state: Object) { const groupNames = selectList(state).entries; if (!groupNames) { diff --git a/scm-ui/src/groups/modules/groups.test.js b/scm-ui/src/groups/modules/groups.test.js index 191a2122e4..63ab375cd3 100644 --- a/scm-ui/src/groups/modules/groups.test.js +++ b/scm-ui/src/groups/modules/groups.test.js @@ -42,9 +42,11 @@ import reducer, { modifyGroup, MODIFY_GROUP_PENDING, MODIFY_GROUP_SUCCESS, - MODIFY_GROUP_FAILURE + MODIFY_GROUP_FAILURE, + getCreateGroupLink } from "./groups"; const GROUPS_URL = "/api/v2/groups"; +const URL = "/groups"; const error = new Error("You have an error!"); @@ -63,7 +65,7 @@ const humanGroup = { href: "http://localhost:8081/api/v2/groups/humanGroup" }, update: { - href:"http://localhost:8081/api/v2/groups/humanGroup" + href: "http://localhost:8081/api/v2/groups/humanGroup" } }, _embedded: { @@ -95,7 +97,7 @@ const emptyGroup = { href: "http://localhost:8081/api/v2/groups/emptyGroup" }, update: { - href:"http://localhost:8081/api/v2/groups/emptyGroup" + href: "http://localhost:8081/api/v2/groups/emptyGroup" } }, _embedded: { @@ -150,7 +152,7 @@ describe("groups fetch()", () => { const store = mockStore({}); - return store.dispatch(fetchGroups()).then(() => { + return store.dispatch(fetchGroups(URL)).then(() => { expect(store.getActions()).toEqual(expectedActions); }); }); @@ -161,7 +163,7 @@ describe("groups fetch()", () => { }); const store = mockStore({}); - return store.dispatch(fetchGroups()).then(() => { + return store.dispatch(fetchGroups(URL)).then(() => { const actions = store.getActions(); expect(actions[0].type).toEqual(FETCH_GROUPS_PENDING); expect(actions[1].type).toEqual(FETCH_GROUPS_FAILURE); @@ -173,7 +175,7 @@ describe("groups fetch()", () => { fetchMock.getOnce(GROUPS_URL + "/humanGroup", humanGroup); const store = mockStore({}); - return store.dispatch(fetchGroup("humanGroup")).then(() => { + return store.dispatch(fetchGroup(URL, "humanGroup")).then(() => { const actions = store.getActions(); expect(actions[0].type).toEqual(FETCH_GROUP_PENDING); expect(actions[1].type).toEqual(FETCH_GROUP_SUCCESS); @@ -187,7 +189,7 @@ describe("groups fetch()", () => { }); const store = mockStore({}); - return store.dispatch(fetchGroup("humanGroup")).then(() => { + return store.dispatch(fetchGroup(URL, "humanGroup")).then(() => { const actions = store.getActions(); expect(actions[0].type).toEqual(FETCH_GROUP_PENDING); expect(actions[1].type).toEqual(FETCH_GROUP_FAILURE); @@ -195,14 +197,13 @@ describe("groups fetch()", () => { }); }); - it("should successfully create group", () => { fetchMock.postOnce(GROUPS_URL, { status: 201 }); const store = mockStore({}); - return store.dispatch(createGroup(humanGroup)).then(() => { + return store.dispatch(createGroup(URL, humanGroup)).then(() => { const actions = store.getActions(); expect(actions[0].type).toEqual(CREATE_GROUP_PENDING); expect(actions[1].type).toEqual(CREATE_GROUP_SUCCESS); @@ -219,14 +220,13 @@ describe("groups fetch()", () => { called = true; }; const store = mockStore({}); - return store.dispatch(createGroup(humanGroup, callMe)).then(() => { + return store.dispatch(createGroup(URL, humanGroup, callMe)).then(() => { const actions = store.getActions(); expect(actions[0].type).toEqual(CREATE_GROUP_PENDING); expect(actions[1].type).toEqual(CREATE_GROUP_SUCCESS); expect(called).toEqual(true); }); }); - it("should fail creating group on HTTP 500", () => { fetchMock.postOnce(GROUPS_URL, { @@ -234,7 +234,7 @@ describe("groups fetch()", () => { }); const store = mockStore({}); - return store.dispatch(createGroup(humanGroup)).then(() => { + return store.dispatch(createGroup(URL, humanGroup)).then(() => { const actions = store.getActions(); expect(actions[0].type).toEqual(CREATE_GROUP_PENDING); expect(actions[1].type).toEqual(CREATE_GROUP_FAILURE); @@ -248,7 +248,7 @@ describe("groups fetch()", () => { status: 204 }); - const store = mockStore({}); + const store = mockStore({}); return store.dispatch(modifyGroup(humanGroup)).then(() => { const actions = store.getActions(); @@ -267,7 +267,7 @@ describe("groups fetch()", () => { const callback = () => { called = true; }; - const store = mockStore({}); + const store = mockStore({}); return store.dispatch(modifyGroup(humanGroup, callback)).then(() => { const actions = store.getActions(); @@ -282,7 +282,7 @@ describe("groups fetch()", () => { status: 500 }); - const store = mockStore({}); + const store = mockStore({}); return store.dispatch(modifyGroup(humanGroup)).then(() => { const actions = store.getActions(); @@ -337,13 +337,10 @@ describe("groups fetch()", () => { expect(actions[1].payload).toBeDefined(); }); }); - }); describe("groups reducer", () => { - it("should update state correctly according to FETCH_GROUPS_SUCCESS action", () => { - const newState = reducer({}, fetchGroupsSuccess(responseBody)); expect(newState.list).toEqual({ @@ -391,7 +388,6 @@ describe("groups reducer", () => { expect(newState.byNames["humanGroup"]).toBeTruthy(); }); - it("should update state according to FETCH_GROUP_SUCCESS action", () => { const newState = reducer({}, fetchGroupSuccess(emptyGroup)); expect(newState.byNames["emptyGroup"]).toBe(emptyGroup); @@ -426,7 +422,6 @@ describe("groups reducer", () => { expect(newState.byNames["emptyGroup"]).toBeFalsy(); expect(newState.list.entries).toEqual(["humanGroup"]); }); - }); describe("selector tests", () => { @@ -476,6 +471,23 @@ describe("selector tests", () => { expect(isPermittedToCreateGroups(state)).toBe(true); }); + it("should return create Group link", () => { + const state = { + groups: { + list: { + entry: { + _links: { + create: { + href: "/create" + } + } + } + } + } + }; + expect(getCreateGroupLink(state)).toBe("/create"); + }); + it("should get groups from state", () => { const state = { groups: { @@ -488,7 +500,7 @@ describe("selector tests", () => { } } }; - + expect(getGroupsFromState(state)).toEqual([{ name: "a" }, { name: "b" }]); }); @@ -560,9 +572,13 @@ describe("selector tests", () => { }); it("should return true if create group is pending", () => { - expect(isCreateGroupPending({pending: { - [CREATE_GROUP]: true - }})).toBeTruthy(); + expect( + isCreateGroupPending({ + pending: { + [CREATE_GROUP]: true + } + }) + ).toBeTruthy(); }); it("should return false if create group is not pending", () => { @@ -570,18 +586,19 @@ describe("selector tests", () => { }); it("should return error if creating group failed", () => { - expect(getCreateGroupFailure({ - failure: { - [CREATE_GROUP]: error - } - })).toEqual(error); + expect( + getCreateGroupFailure({ + failure: { + [CREATE_GROUP]: error + } + }) + ).toEqual(error); }); it("should return undefined if creating group did not fail", () => { expect(getCreateGroupFailure({})).toBeUndefined(); }); - it("should return true, when delete group humanGroup is pending", () => { const state = { pending: {