diff --git a/scm-ui/src/config/modules/roles.js b/scm-ui/src/config/modules/roles.js index 8d9c99f0bc..9050fb54df 100644 --- a/scm-ui/src/config/modules/roles.js +++ b/scm-ui/src/config/modules/roles.js @@ -16,17 +16,28 @@ export const FETCH_ROLE_PENDING = `${FETCH_ROLE}_${types.PENDING_SUFFIX}`; export const FETCH_ROLE_SUCCESS = `${FETCH_ROLE}_${types.SUCCESS_SUFFIX}`; export const FETCH_ROLE_FAILURE = `${FETCH_ROLE}_${types.FAILURE_SUFFIX}`; -export const FETCH_VERBS = "scm/roles/FETCH_VERBS"; -export const FETCH_VERBS_PENDING = `${FETCH_VERBS}_${types.PENDING_SUFFIX}`; -export const FETCH_VERBS_SUCCESS = `${FETCH_VERBS}_${types.SUCCESS_SUFFIX}`; -export const FETCH_VERBS_FAILURE = `${FETCH_VERBS}_${types.FAILURE_SUFFIX}`; - export const CREATE_ROLE = "scm/roles/CREATE_ROLE"; export const CREATE_ROLE_PENDING = `${CREATE_ROLE}_${types.PENDING_SUFFIX}`; export const CREATE_ROLE_SUCCESS = `${CREATE_ROLE}_${types.SUCCESS_SUFFIX}`; export const CREATE_ROLE_FAILURE = `${CREATE_ROLE}_${types.FAILURE_SUFFIX}`; export const CREATE_ROLE_RESET = `${CREATE_ROLE}_${types.RESET_SUFFIX}`; +export const MODIFY_ROLE = "scm/roles/MODIFY_ROLE"; +export const MODIFY_ROLE_PENDING = `${MODIFY_ROLE}_${types.PENDING_SUFFIX}`; +export const MODIFY_ROLE_SUCCESS = `${MODIFY_ROLE}_${types.SUCCESS_SUFFIX}`; +export const MODIFY_ROLE_FAILURE = `${MODIFY_ROLE}_${types.FAILURE_SUFFIX}`; +export const MODIFY_ROLE_RESET = `${MODIFY_ROLE}_${types.RESET_SUFFIX}`; + +export const DELETE_ROLE = "scm/roles/DELETE_ROLE"; +export const DELETE_ROLE_PENDING = `${DELETE_ROLE}_${types.PENDING_SUFFIX}`; +export const DELETE_ROLE_SUCCESS = `${DELETE_ROLE}_${types.SUCCESS_SUFFIX}`; +export const DELETE_ROLE_FAILURE = `${DELETE_ROLE}_${types.FAILURE_SUFFIX}`; + +export const FETCH_VERBS = "scm/roles/FETCH_VERBS"; +export const FETCH_VERBS_PENDING = `${FETCH_VERBS}_${types.PENDING_SUFFIX}`; +export const FETCH_VERBS_SUCCESS = `${FETCH_VERBS}_${types.SUCCESS_SUFFIX}`; +export const FETCH_VERBS_FAILURE = `${FETCH_VERBS}_${types.FAILURE_SUFFIX}`; + const CONTENT_TYPE_ROLE = "application/vnd.scmm-repositoryRole+json;v=2"; // fetch roles @@ -72,13 +83,8 @@ export function fetchRoles(link: string) { return fetchRolesByLink(link); } -export function fetchRolesByPage(link: string, page: number, filter?: string) { +export function fetchRolesByPage(link: string, page: number) { // backend start counting by 0 - if (filter) { - return fetchRolesByLink( - `${link}?page=${page - 1}&q=${decodeURIComponent(filter)}` - ); - } return fetchRolesByLink(`${link}?page=${page - 1}`); } @@ -226,24 +232,104 @@ function verbReducer(state: any = {}, action: any = {}) { } } -function listReducer(state: any = {}, action: any = {}) { - switch (action.type) { - case FETCH_ROLES_SUCCESS: - const roles = action.payload._embedded.repositoryRoles; - const roleNames = roles.map(role => role.name); - return { - ...state, - entries: roleNames, - entry: { - roleCreatePermission: !!action.payload._links.create, - page: action.payload.page, - pageTotal: action.payload.pageTotal, - _links: action.payload._links +// modify role +export function modifyRolePending(role: Role): Action { + return { + type: MODIFY_ROLE_PENDING, + payload: role, + itemId: role.name + }; +} + +export function modifyRoleSuccess(role: Role): Action { + return { + type: MODIFY_ROLE_SUCCESS, + payload: role, + itemId: role.name + }; +} + +export function modifyRoleFailure(role: Role, error: Error): Action { + return { + type: MODIFY_ROLE_FAILURE, + payload: { + error, + role + }, + itemId: role.name + }; +} + +export function modifyRoleReset(role: Role): Action { + return { + type: MODIFY_ROLE_RESET, + itemId: role.name + }; +} + +export function modifyRole(role: Role, callback?: () => void) { + return function(dispatch: Dispatch) { + dispatch(modifyRolePending(role)); + return apiClient + .put(role._links.update.href, role, CONTENT_TYPE_ROLE) + .then(() => { + dispatch(modifyRoleSuccess(role)); + if (callback) { + callback(); } - }; - default: - return state; - } + }) + .then(() => { + dispatch(fetchRoleByLink(role)); + }) + .catch(err => { + dispatch(modifyRoleFailure(role, err)); + }); + }; +} + +// delete role +export function deleteRolePending(role: Role): Action { + return { + type: DELETE_ROLE_PENDING, + payload: role, + itemId: role.name + }; +} + +export function deleteRoleSuccess(role: Role): Action { + return { + type: DELETE_ROLE_SUCCESS, + payload: role, + itemId: role.name + }; +} + +export function deleteRoleFailure(role: Role, error: Error): Action { + return { + type: DELETE_ROLE_FAILURE, + payload: { + error, + role + }, + itemId: role.name + }; +} + +export function deleteRole(role: Role, callback?: () => void) { + return function(dispatch: any) { + dispatch(deleteRolePending(role)); + return apiClient + .delete(role._links.delete.href) + .then(() => { + dispatch(deleteRoleSuccess(role)); + if (callback) { + callback(); + } + }) + .catch(error => { + dispatch(deleteRoleFailure(role, error)); + }); + }; } function extractRolesByNames( @@ -263,6 +349,22 @@ function extractRolesByNames( return rolesByNames; } +function deleteRoleInRolesByNames(roles: {}, roleName: string) { + let newRoles = {}; + for (let rolename in roles) { + if (rolename !== roleName) newRoles[rolename] = roles[rolename]; + } + return newRoles; +} + +function deleteRoleInEntries(roles: [], roleName: string) { + let newRoles = []; + for (let role of roles) { + if (role !== roleName) newRoles.push(role); + } + return newRoles; +} + const reducerByName = (state: any, rolename: string, newRoleState: any) => { return { ...state, @@ -270,6 +372,37 @@ const reducerByName = (state: any, rolename: string, newRoleState: any) => { }; }; +function listReducer(state: any = {}, action: any = {}) { + switch (action.type) { + case FETCH_ROLES_SUCCESS: + const roles = action.payload._embedded.repositoryRoles; + const roleNames = roles.map(role => role.name); + return { + ...state, + entries: roleNames, + entry: { + roleCreatePermission: !!action.payload._links.create, + page: action.payload.page, + pageTotal: action.payload.pageTotal, + _links: action.payload._links + } + }; + + // Delete single role actions + case DELETE_ROLE_SUCCESS: + const newRoleEntries = deleteRoleInEntries( + state.entries, + action.payload.name + ); + return { + ...state, + entries: newRoleEntries + }; + default: + return state; + } +} + function byNamesReducer(state: any = {}, action: any = {}) { switch (action.type) { // Fetch all roles actions @@ -280,9 +413,14 @@ function byNamesReducer(state: any = {}, action: any = {}) { return { ...byNames }; + // Fetch single role actions case FETCH_ROLE_SUCCESS: return reducerByName(state, action.payload.name, action.payload); + + case DELETE_ROLE_SUCCESS: + return deleteRoleInRolesByNames(state, action.payload.name); + default: return state; } @@ -301,7 +439,6 @@ const selectList = (state: Object) => { } return {}; }; - const selectListEntry = (state: Object): Object => { const list = selectList(state); if (list.entry) { @@ -344,14 +481,6 @@ export function getFetchRolesFailure(state: Object) { return getFailure(state, FETCH_ROLES); } -export function isCreateRolePending(state: Object) { - return isPending(state, CREATE_ROLE); -} - -export function getCreateRoleFailure(state: Object) { - return getFailure(state, CREATE_ROLE); -} - export function isFetchVerbsPending(state: Object) { return isPending(state, FETCH_VERBS); } @@ -360,6 +489,14 @@ export function getFetchVerbsFailure(state: Object) { return getFailure(state, FETCH_VERBS); } +export function isCreateRolePending(state: Object) { + return isPending(state, CREATE_ROLE); +} + +export function getCreateRoleFailure(state: Object) { + return getFailure(state, CREATE_ROLE); +} + export function getRoleByName(state: Object, name: string) { if (state.roles && state.roles.byNames) { return state.roles.byNames[name]; @@ -373,3 +510,19 @@ export function isFetchRolePending(state: Object, name: string) { export function getFetchRoleFailure(state: Object, name: string) { return getFailure(state, FETCH_ROLE, name); } + +export function isModifyRolePending(state: Object, name: string) { + return isPending(state, MODIFY_ROLE, name); +} + +export function getModifyRoleFailure(state: Object, name: string) { + return getFailure(state, MODIFY_ROLE, name); +} + +export function isDeleteRolePending(state: Object, name: string) { + return isPending(state, DELETE_ROLE, name); +} + +export function getDeleteRoleFailure(state: Object, name: string) { + return getFailure(state, DELETE_ROLE, name); +} diff --git a/scm-ui/src/config/modules/roles.test.js b/scm-ui/src/config/modules/roles.test.js index fc165f863a..3c09bb7db1 100644 --- a/scm-ui/src/config/modules/roles.test.js +++ b/scm-ui/src/config/modules/roles.test.js @@ -16,6 +16,14 @@ import reducer, { CREATE_ROLE_PENDING, CREATE_ROLE_SUCCESS, CREATE_ROLE_FAILURE, + MODIFY_ROLE, + MODIFY_ROLE_PENDING, + MODIFY_ROLE_SUCCESS, + MODIFY_ROLE_FAILURE, + DELETE_ROLE, + DELETE_ROLE_PENDING, + DELETE_ROLE_SUCCESS, + DELETE_ROLE_FAILURE, fetchRoles, getFetchRolesFailure, getRolesFromState, @@ -30,6 +38,13 @@ import reducer, { isCreateRolePending, getCreateRoleFailure, getRoleByName, + modifyRole, + isModifyRolePending, + getModifyRoleFailure, + deleteRole, + isDeleteRolePending, + deleteRoleSuccess, + getDeleteRoleFailure, selectListAsCollection, isPermittedToCreateRoles } from "./roles"; @@ -101,7 +116,8 @@ const response = { const URL = "repositoryRoles"; const ROLES_URL = "/api/v2/repositoryRoles"; -const ROLE1_URL = "http://localhost:8081/scm/api/v2/repositoryRoles/specialrole"; +const ROLE1_URL = + "http://localhost:8081/scm/api/v2/repositoryRoles/specialrole"; const error = new Error("FEHLER!"); @@ -247,6 +263,99 @@ describe("repository roles fetch", () => { expect(callMe).toBe("yeah"); }); }); + + it("successfully update role", () => { + fetchMock.putOnce(ROLE1_URL, { + status: 204 + }); + fetchMock.getOnce(ROLE1_URL, role1); + + const store = mockStore({}); + return store.dispatch(modifyRole(role1)).then(() => { + const actions = store.getActions(); + expect(actions.length).toBe(3); + expect(actions[0].type).toEqual(MODIFY_ROLE_PENDING); + expect(actions[1].type).toEqual(MODIFY_ROLE_SUCCESS); + expect(actions[2].type).toEqual(FETCH_ROLE_PENDING); + }); + }); + + it("should call callback, after successful modified role", () => { + fetchMock.putOnce(ROLE1_URL, { + status: 204 + }); + fetchMock.getOnce(ROLE1_URL, role1); + + let called = false; + const callMe = () => { + called = true; + }; + + const store = mockStore({}); + return store.dispatch(modifyRole(role1, callMe)).then(() => { + expect(called).toBeTruthy(); + }); + }); + + it("should fail updating role on HTTP 500", () => { + fetchMock.putOnce(ROLE1_URL, { + status: 500 + }); + + const store = mockStore({}); + return store.dispatch(modifyRole(role1)).then(() => { + const actions = store.getActions(); + expect(actions[0].type).toEqual(MODIFY_ROLE_PENDING); + expect(actions[1].type).toEqual(MODIFY_ROLE_FAILURE); + expect(actions[1].payload).toBeDefined(); + }); + }); + + it("should delete successfully role1", () => { + fetchMock.deleteOnce(ROLE1_URL, { + status: 204 + }); + + const store = mockStore({}); + return store.dispatch(deleteRole(role1)).then(() => { + const actions = store.getActions(); + expect(actions.length).toBe(2); + expect(actions[0].type).toEqual(DELETE_ROLE_PENDING); + expect(actions[0].payload).toBe(role1); + expect(actions[1].type).toEqual(DELETE_ROLE_SUCCESS); + }); + }); + + it("should call the callback after successful delete", () => { + fetchMock.deleteOnce(ROLE1_URL, { + status: 204 + }); + + let called = false; + const callMe = () => { + called = true; + }; + + const store = mockStore({}); + return store.dispatch(deleteRole(role1, callMe)).then(() => { + expect(called).toBeTruthy(); + }); + }); + + it("should fail to delete role1", () => { + fetchMock.deleteOnce(ROLE1_URL, { + status: 500 + }); + + const store = mockStore({}); + return store.dispatch(deleteRole(role1)).then(() => { + const actions = store.getActions(); + expect(actions[0].type).toEqual(DELETE_ROLE_PENDING); + expect(actions[0].payload).toBe(role1); + expect(actions[1].type).toEqual(DELETE_ROLE_FAILURE); + expect(actions[1].payload).toBeDefined(); + }); + }); }); describe("repository roles reducer", () => { @@ -289,6 +398,23 @@ describe("repository roles reducer", () => { expect(newState.byNames["WRITE"]).toBeDefined(); }); + it("should remove role from state when delete succeeds", () => { + const state = { + list: { + entries: ["WRITE", "specialrole"] + }, + byNames: { + specialrole: role1, + WRITE: role2 + } + }; + + const newState = reducer(state, deleteRoleSuccess(role2)); + expect(newState.byNames["specialrole"]).toBeDefined(); + expect(newState.byNames["WRITE"]).toBeFalsy(); + expect(newState.list.entries).toEqual(["specialrole"]); + }); + it("should set roleCreatePermission to true if create link is present", () => { const newState = reducer({}, fetchRolesSuccess(responseBody)); @@ -341,9 +467,9 @@ describe("repository roles selector", () => { it("should return false", () => { expect(isPermittedToCreateRoles({})).toBe(false); - expect( - isPermittedToCreateRoles({ roles: { list: { entry: {} } } }) - ).toBe(false); + expect(isPermittedToCreateRoles({ roles: { list: { entry: {} } } })).toBe( + false + ); expect( isPermittedToCreateRoles({ roles: { list: { entry: { roleCreatePermission: false } } } @@ -472,4 +598,56 @@ describe("repository roles selector", () => { it("should return undefined when fetch role2 did not fail", () => { expect(getFetchRoleFailure({}, "role2")).toBe(undefined); }); + + it("should return true, when modify role1 is pending", () => { + const state = { + pending: { + [MODIFY_ROLE + "/role1"]: true + } + }; + expect(isModifyRolePending(state, "role1")).toEqual(true); + }); + + it("should return false, when modify role1 is not pending", () => { + expect(isModifyRolePending({}, "role1")).toEqual(false); + }); + + it("should return error when modify role1 did fail", () => { + const state = { + failure: { + [MODIFY_ROLE + "/role1"]: error + } + }; + expect(getModifyRoleFailure(state, "role1")).toEqual(error); + }); + + it("should return undefined when modify role1 did not fail", () => { + expect(getModifyRoleFailure({}, "role1")).toBe(undefined); + }); + + it("should return true, when delete role2 is pending", () => { + const state = { + pending: { + [DELETE_ROLE + "/role2"]: true + } + }; + expect(isDeleteRolePending(state, "role2")).toEqual(true); + }); + + it("should return false, when delete role2 is not pending", () => { + expect(isDeleteRolePending({}, "role2")).toEqual(false); + }); + + it("should return error when delete role2 did fail", () => { + const state = { + failure: { + [DELETE_ROLE + "/role2"]: error + } + }; + expect(getDeleteRoleFailure(state, "role2")).toEqual(error); + }); + + it("should return undefined when delete role2 did not fail", () => { + expect(getDeleteRoleFailure({}, "role2")).toBe(undefined); + }); });