diff --git a/scm-ui/src/permissions/containers/SinglePermission.js b/scm-ui/src/permissions/containers/SinglePermission.js index e5b6436810..f125d4025b 100644 --- a/scm-ui/src/permissions/containers/SinglePermission.js +++ b/scm-ui/src/permissions/containers/SinglePermission.js @@ -70,7 +70,7 @@ class SinglePermission extends React.Component { render() { const { permission } = this.state; const { t, loading, error } = this.props; - const types = ["READ", "OWNER", "GROUP"]; + const types = ["READ", "OWNER", "WRITE"]; const deleteButton = this.props.permission._links.delete ? ( ) : null; @@ -99,7 +99,10 @@ class SinglePermission extends React.Component { {typeSelector} - {deleteButton} {errorNotification} + + {deleteButton} + {errorNotification} + ); } diff --git a/scm-ui/src/permissions/modules/permissions.js b/scm-ui/src/permissions/modules/permissions.js index 950fd8a653..71b78e9f6b 100644 --- a/scm-ui/src/permissions/modules/permissions.js +++ b/scm-ui/src/permissions/modules/permissions.js @@ -5,6 +5,14 @@ import type { Action } from "../../types/Action"; import type { PermissionCollection, Permission } from "../types/Permissions"; import { isPending } from "../../modules/pending"; import { getFailure } from "../../modules/failure"; +import type { User } from "../../users/types/User"; +import { Dispatch } from "redux"; +import { + CREATE_USER_FAILURE, + CREATE_USER_PENDING, + CREATE_USER_RESET, + CREATE_USER_SUCCESS +} from "../../users/modules/users"; export const FETCH_PERMISSIONS = "scm/permissions/FETCH_PERMISSIONS"; export const FETCH_PERMISSIONS_PENDING = `${FETCH_PERMISSIONS}_${ @@ -29,6 +37,17 @@ export const MODIFY_PERMISSION_FAILURE = `${MODIFY_PERMISSION}_${ export const MODIFY_PERMISSION_RESET = `${MODIFY_PERMISSION}_${ types.RESET_SUFFIX }`; +export const CREATE_PERMISSION = "scm/permissions/CREATE_PERMISSION"; +export const CREATE_PERMISSION_PENDING = `${CREATE_PERMISSION}_${ + types.PENDING_SUFFIX +}`; +export const CREATE_PERMISSION_SUCCESS = `$CREATE_PERMISSION}_${ + types.SUCCESS_SUFFIX +}`; +export const CREATE_PERMISSION_FAILURE = `${CREATE_PERMISSION}_${ + types.FAILURE_SUFFIX +}`; + const REPOS_URL = "repositories"; const PERMISSIONS_URL = "permissions"; const CONTENT_TYPE = "application/vnd.scmm-permission+json"; @@ -182,6 +201,75 @@ export function modifyPermissionReset( }; } +// create permission +export function createPermission( + permission: Permission, + namespace: string, + name: string, + callback?: () => void +) { + return function(dispatch: Dispatch) { + dispatch(createPermissionPending(permission, namespace, name)); + return apiClient + .post( + `${REPOS_URL}/${namespace}/${name}/${PERMISSIONS_URL}`, + permission, + CONTENT_TYPE + ) + .then(() => { + dispatch(createPermissionSuccess(namespace, name)); + if (callback) { + callback(); + } + }) + .catch(err => + dispatch( + createPermissionFailure( + new Error( + `failed to add permission ${permission.name}: ${err.message}` + ), + namespace, + name + ) + ) + ); + }; +} + +export function createPermissionPending( + permission: Permission, + namespace: string, + name: string +): Action { + return { + type: CREATE_PERMISSION_PENDING, + payload: permission, + itemId: namespace + "/" + name + }; +} + +export function createPermissionSuccess( + namespace: string, + name: string +): Action { + return { + type: CREATE_PERMISSION_SUCCESS, + itemId: namespace + "/" + name + }; +} + +export function createPermissionFailure( + error: Error, + namespace: string, + name: string +): Action { + return { + type: CREATE_PERMISSION_FAILURE, + payload: error, + itemId: namespace + "/" + name + }; +} + // reducer export default function reducer( state: Object = {}, @@ -195,17 +283,23 @@ export default function reducer( case FETCH_PERMISSIONS_SUCCESS: return { ...state, - [action.itemId]: action.payload._embedded.permissions + [action.itemId]: { + entries: action.payload._embedded.permissions, + createPermission: action.payload._links.create ? true : false + } }; case MODIFY_PERMISSION_SUCCESS: const positionOfPermission = action.payload.position; const newPermission = newPermissions( - state[action.payload.position], + state[action.payload.position].entries, action.payload.permission ); return { ...state, - [positionOfPermission]: newPermission + [positionOfPermission]: { + ...state[positionOfPermission], + entries: newPermission + } }; default: return state; @@ -220,7 +314,7 @@ export function getPermissionsOfRepo( name: string ) { if (state.permissions && state.permissions[namespace + "/" + name]) { - const permissions = state.permissions[namespace + "/" + name]; + const permissions = state.permissions[namespace + "/" + name].entries; return permissions; } } diff --git a/scm-ui/src/permissions/modules/permissions.test.js b/scm-ui/src/permissions/modules/permissions.test.js index 9fca40867c..376c855d21 100644 --- a/scm-ui/src/permissions/modules/permissions.test.js +++ b/scm-ui/src/permissions/modules/permissions.test.js @@ -12,6 +12,7 @@ import reducer, { modifyPermissionSuccess, getModifyPermissionFailure, isModifyPermissionPending, + createPermission, MODIFY_PERMISSION_FAILURE, MODIFY_PERMISSION_PENDING, FETCH_PERMISSIONS, @@ -19,7 +20,12 @@ import reducer, { FETCH_PERMISSIONS_SUCCESS, FETCH_PERMISSIONS_FAILURE, MODIFY_PERMISSION_SUCCESS, - MODIFY_PERMISSION + MODIFY_PERMISSION, + CREATE_PERMISSION, + CREATE_PERMISSION_PENDING, + CREATE_PERMISSION_SUCCESS, + CREATE_PERMISSION_FAILURE, + createPermissionSuccess } from "./permissions"; import type { Permission, PermissionCollection } from "../types/Permissions"; @@ -71,6 +77,11 @@ const hitchhiker_puzzle42Permissions: PermissionCollection = [ const hitchhiker_puzzle42RepoPermissions = { _embedded: { permissions: hitchhiker_puzzle42Permissions + }, + _links: { + create: { + link: "link" + } } }; @@ -203,6 +214,83 @@ describe("permission fetch", () => { expect(actions[1].payload).toBeDefined(); }); }); + + it("should add a permission successfully", () => { + // unmatched + fetchMock.postOnce(REPOS_URL + "/hitchhiker/puzzle42/permissions", { + status: 204 + }); + + // after create, the users are fetched again + fetchMock.getOnce( + REPOS_URL + "/hitchhiker/puzzle42", + hitchhiker_puzzle42RepoPermissions + ); + + const store = mockStore({}); + return store + .dispatch( + createPermission( + hitchhiker_puzzle42Permission_user_eins, + "hitchhiker", + "puzzle42" + ) + ) + .then(() => { + const actions = store.getActions(); + expect(actions[0].type).toEqual(CREATE_PERMISSION_PENDING); + expect(actions[1].type).toEqual(CREATE_PERMISSION_SUCCESS); + }); + }); + + it("should fail adding a permission on HTTP 500", () => { + fetchMock.postOnce(REPOS_URL + "/hitchhiker/puzzle42/permissions", { + status: 500 + }); + + const store = mockStore({}); + return store + .dispatch( + createPermission( + hitchhiker_puzzle42Permission_user_eins, + "hitchhiker", + "puzzle42" + ) + ) + .then(() => { + const actions = store.getActions(); + expect(actions[0].type).toEqual(CREATE_PERMISSION_PENDING); + expect(actions[1].type).toEqual(CREATE_PERMISSION_FAILURE); + expect(actions[1].payload).toBeDefined(); + }); + }); + + it("should call the callback after permission successfully created", () => { + // unmatched + fetchMock.postOnce(REPOS_URL + "/hitchhiker/puzzle42/permissions", { + status: 204 + }); + + let callMe = "not yet"; + + const callback = () => { + callMe = "yeah"; + }; + + const store = mockStore({}); + return store + .dispatch( + createPermission( + hitchhiker_puzzle42Permission_user_eins, + "hitchhiker", + "puzzle42", + callback + ) + ) + .then(() => { + expect(callMe).toBe("yeah"); + }); + }); }); describe("permissions reducer", () => { @@ -230,19 +318,23 @@ describe("permissions reducer", () => { ) ); - expect(newState["hitchhiker/puzzle42"]).toBe( + expect(newState["hitchhiker/puzzle42"].entries).toBe( hitchhiker_puzzle42Permissions ); }); it("should update permission", () => { const oldState = { - "hitchhiker/puzzle42": [hitchhiker_puzzle42Permission_user_eins] + "hitchhiker/puzzle42": { + entries: [hitchhiker_puzzle42Permission_user_eins] + } }; let permissionEdited = { ...hitchhiker_puzzle42Permission_user_eins }; permissionEdited.type = "OWNER"; let expectedState = { - "hitchhiker/puzzle42": [permissionEdited] + "hitchhiker/puzzle42": { + entries: [permissionEdited] + } }; const newState = reducer( oldState, @@ -260,7 +352,9 @@ describe("permissions selectors", () => { it("should return the permissions of one repository", () => { const state = { permissions: { - "hitchhiker/puzzle42": hitchhiker_puzzle42Permissions + "hitchhiker/puzzle42": { + entries: hitchhiker_puzzle42Permissions + } } };