From 0259b9ba3e4b296fd4d360a7f48f6d9ac53ce38e Mon Sep 17 00:00:00 2001 From: Michal Klobukowski Date: Mon, 29 Oct 2018 22:56:03 +0100 Subject: [PATCH 1/1] Create permission service, create public access and sharing management actions Feature #14365 Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski --- .../permission-service/permission-service.ts | 18 +-- .../sharing-dialog/sharing-dialog-actions.ts | 107 +++++++++++++++++- .../sharing-dialog/sharing-dialog-types.ts | 5 +- .../workflow-panel/workflow-panel-actions.ts | 10 ++ .../action-sets/collection-action-set.ts | 2 +- .../action-sets/project-action-set.ts | 9 ++ .../sharing-dialog/sharing-dialog.tsx | 13 +-- 7 files changed, 142 insertions(+), 22 deletions(-) diff --git a/src/services/permission-service/permission-service.ts b/src/services/permission-service/permission-service.ts index 95666de0..0f880454 100644 --- a/src/services/permission-service/permission-service.ts +++ b/src/services/permission-service/permission-service.ts @@ -4,20 +4,20 @@ import { LinkService } from "~/services/link-service/link-service"; import { PermissionResource } from "~/models/permission"; -import { ListArguments, ListResults } from '~/services/common-service/common-resource-service'; +import { ListArguments, ListResults, CommonResourceService } from '~/services/common-service/common-resource-service'; import { joinFilters, FilterBuilder } from '../api/filter-builder'; import { LinkClass } from '../../models/link'; export class PermissionService extends LinkService { - list(args: ListArguments = {}): Promise> { - const { filters, ...other } = args; - const classFilter = new FilterBuilder().addEqual('class', LinkClass.PERMISSION).getFilters(); - const newArgs = { - ...other, - filters: joinFilters(filters, classFilter), - }; - return super.list(newArgs); + permissionListService = new CommonResourceService(this.serverApi, 'permissions', this.actions); + create(data?: Partial) { + return super.create({ ...data, linkClass: LinkClass.PERMISSION }); + } + + listResourcePermissions(uuid: string, args: ListArguments = {}): Promise> { + const service = new CommonResourceService(this.serverApi, `permissions/${uuid}`, this.actions); + return service.list(args); } } diff --git a/src/store/sharing-dialog/sharing-dialog-actions.ts b/src/store/sharing-dialog/sharing-dialog-actions.ts index 10518bda..76d54e17 100644 --- a/src/store/sharing-dialog/sharing-dialog-actions.ts +++ b/src/store/sharing-dialog/sharing-dialog-actions.ts @@ -4,12 +4,115 @@ import { dialogActions } from "~/store/dialog/dialog-actions"; import { withDialog } from "~/store/dialog/with-dialog"; -import { SHARING_DIALOG_NAME } from "./sharing-dialog-types"; +import { SHARING_DIALOG_NAME, SharingPublicAccessFormData, SHARING_PUBLIC_ACCESS_FORM_NAME, SHARING_INVITATION_FORM_NAME, SharingManagementFormData, SharingInvitationFormData } from './sharing-dialog-types'; +import { Dispatch } from 'redux'; +import { ServiceRepository } from "~/services/services"; +import { FilterBuilder } from '~/services/api/filter-builder'; +import { initialize, getFormValues, isDirty, reset } from 'redux-form'; +import { SHARING_MANAGEMENT_FORM_NAME } from '~/store/sharing-dialog/sharing-dialog-types'; +import { RootState } from '~/store/store'; +import { getDialog } from '~/store/dialog/dialog-reducer'; +import { PermissionLevel } from '../../models/permission'; +import { getPublicGroupUuid } from "~/store/workflow-panel/workflow-panel-actions"; export const openSharingDialog = (resourceUuid: string) => - dialogActions.OPEN_DIALOG({ id: SHARING_DIALOG_NAME, data: resourceUuid }); + async (dispatch: Dispatch, getState: () => RootState, { permissionService }: ServiceRepository) => { + + dispatch(dialogActions.OPEN_DIALOG({ id: SHARING_DIALOG_NAME, data: resourceUuid })); + + const state = getState(); + const { items } = await permissionService.listResourcePermissions(resourceUuid); + + const managementFormData: SharingManagementFormData = { + permissions: items + .filter(item => + item.tailUuid !== getPublicGroupUuid(state)) + .map(({ tailUuid, name }) => ({ + email: tailUuid, + permissions: name as PermissionLevel, + })) + }; + + dispatch(initialize(SHARING_MANAGEMENT_FORM_NAME, managementFormData)); + + const [publicPermission] = items.filter(item => item.tailUuid === getPublicGroupUuid(state)); + if (publicPermission) { + const publicAccessFormData: SharingPublicAccessFormData = { + enabled: publicPermission.name !== PermissionLevel.NONE, + permissions: publicPermission.name as PermissionLevel, + }; + + dispatch(initialize(SHARING_PUBLIC_ACCESS_FORM_NAME, publicAccessFormData)); + } else { + dispatch(reset(SHARING_PUBLIC_ACCESS_FORM_NAME)); + } + }; export const closeSharingDialog = () => dialogActions.CLOSE_DIALOG({ id: SHARING_DIALOG_NAME }); export const connectSharingDialog = withDialog(SHARING_DIALOG_NAME); + +export const saveSharingDialogChanges = async (dispatch: Dispatch) => { + dispatch(savePublicPermissionChanges); + dispatch(sendInvitations); +}; + +const savePublicPermissionChanges = async (dispatch: Dispatch, getState: () => RootState, { permissionService }: ServiceRepository) => { + const state = getState(); + const { user } = state.auth; + const dialog = getDialog(state.dialog, SHARING_DIALOG_NAME); + if (dialog && user) { + const publicAccess = getFormValues(SHARING_PUBLIC_ACCESS_FORM_NAME)(state) as SharingPublicAccessFormData; + + const filters = new FilterBuilder() + .addEqual('headUuid', dialog.data) + .getFilters(); + + const { items } = await permissionService.list({ filters }); + + const [publicPermission] = items.filter(item => item.tailUuid === getPublicGroupUuid(state)); + + if (publicPermission) { + + await permissionService.update(publicPermission.uuid, { + name: publicAccess.enabled ? publicAccess.permissions : PermissionLevel.NONE + }); + + } else { + + await permissionService.create({ + ownerUuid: user.uuid, + headUuid: dialog.data, + tailUuid: getPublicGroupUuid(state), + name: publicAccess.enabled ? publicAccess.permissions : PermissionLevel.NONE + }); + } + } +}; + +const sendInvitations = async (dispatch: Dispatch, getState: () => RootState, { permissionService }: ServiceRepository) => { + const state = getState(); + const { user } = state.auth; + const dialog = getDialog(state.dialog, SHARING_DIALOG_NAME); + if (dialog && user) { + + const invitations = getFormValues(SHARING_INVITATION_FORM_NAME)(state) as SharingInvitationFormData; + + const promises = invitations.invitedPeople + .map(person => ({ + ownerUuid: user.uuid, + headUuid: dialog.data, + tailUuid: person.uuid, + name: invitations.permissions + })) + .map(data => permissionService.create(data)); + + await Promise.all(promises); + } +}; + +export const hasChanges = (state: RootState) => + isDirty(SHARING_PUBLIC_ACCESS_FORM_NAME)(state) || + isDirty(SHARING_MANAGEMENT_FORM_NAME)(state) || + isDirty(SHARING_INVITATION_FORM_NAME)(state); diff --git a/src/store/sharing-dialog/sharing-dialog-types.ts b/src/store/sharing-dialog/sharing-dialog-types.ts index e8f07948..634e9758 100644 --- a/src/store/sharing-dialog/sharing-dialog-types.ts +++ b/src/store/sharing-dialog/sharing-dialog-types.ts @@ -3,7 +3,6 @@ // SPDX-License-Identifier: AGPL-3.0 import { PermissionLevel } from '~/models/permission'; -import { SharingManagementForm } from '../../views-components/sharing-dialog/sharing-management-form'; export const SHARING_DIALOG_NAME = 'SHARING_DIALOG_NAME'; export const SHARING_PUBLIC_ACCESS_FORM_NAME = 'SHARING_PUBLIC_ACCESS_FORM_NAME'; @@ -12,7 +11,7 @@ export const SHARING_INVITATION_FORM_NAME = 'SHARING_INVITATION_FORM_NAME'; export interface SharingPublicAccessFormData { enabled: boolean; - permission: PermissionLevel; + permissions: PermissionLevel; } export interface SharingManagementFormData { @@ -21,7 +20,7 @@ export interface SharingManagementFormData { export interface SharingManagementFormDataRow { email: string; - permission: PermissionLevel; + permissions: PermissionLevel; } export interface SharingInvitationFormData { diff --git a/src/store/workflow-panel/workflow-panel-actions.ts b/src/store/workflow-panel/workflow-panel-actions.ts index ca72e5a5..cc2469d7 100644 --- a/src/store/workflow-panel/workflow-panel-actions.ts +++ b/src/store/workflow-panel/workflow-panel-actions.ts @@ -10,6 +10,7 @@ import { propertiesActions } from '~/store/properties/properties-actions'; import { getResource } from '../resources/resources'; import { getProperty } from '~/store/properties/properties'; import { WorkflowResource } from '../../models/workflow'; +import { ResourceObjectType } from '~/models/resource'; export const WORKFLOW_PANEL_ID = "workflowPanel"; const UUID_PREFIX_PROPERTY_NAME = 'uuidPrefix'; @@ -28,6 +29,15 @@ export const getUuidPrefix = (state: RootState) => { return state.properties.uuidPrefix; }; +export const getPublicUserUuid = (state: RootState) => { + const prefix = getProperty(UUID_PREFIX_PROPERTY_NAME)(state.properties); + return `${prefix}-tpzed-anonymouspublic`; +}; +export const getPublicGroupUuid = (state: RootState) => { + const prefix = getProperty(UUID_PREFIX_PROPERTY_NAME)(state.properties); + return `${prefix}-j7d0g-anonymouspublic`; +}; + export const showWorkflowDetails = (uuid: string) => propertiesActions.SET_PROPERTY({ key: WORKFLOW_PANEL_DETAILS_UUID, value: uuid }); diff --git a/src/views-components/context-menu/action-sets/collection-action-set.ts b/src/views-components/context-menu/action-sets/collection-action-set.ts index 80b5effe..e3ccb2dd 100644 --- a/src/views-components/context-menu/action-sets/collection-action-set.ts +++ b/src/views-components/context-menu/action-sets/collection-action-set.ts @@ -27,7 +27,7 @@ export const collectionActionSet: ContextMenuActionSet = [[ icon: ShareIcon, name: "Share", execute: (dispatch, { uuid }) => { - dispatch(openSharingDialog(uuid)); + dispatch(openSharingDialog(uuid)); } }, { diff --git a/src/views-components/context-menu/action-sets/project-action-set.ts b/src/views-components/context-menu/action-sets/project-action-set.ts index 85848a2d..63ea3b55 100644 --- a/src/views-components/context-menu/action-sets/project-action-set.ts +++ b/src/views-components/context-menu/action-sets/project-action-set.ts @@ -13,6 +13,8 @@ import { openProjectUpdateDialog } from '~/store/projects/project-update-actions import { ToggleTrashAction } from "~/views-components/context-menu/actions/trash-action"; import { toggleProjectTrashed } from "~/store/trash/trash-actions"; import { detailsPanelActions } from '~/store/details-panel/details-panel-action'; +import { ShareIcon } from '~/components/icon/icon'; +import { openSharingDialog } from "~/store/sharing-dialog/sharing-dialog-actions"; export const projectActionSet: ContextMenuActionSet = [[ { @@ -29,6 +31,13 @@ export const projectActionSet: ContextMenuActionSet = [[ dispatch(openProjectUpdateDialog(resource)); } }, + { + icon: ShareIcon, + name: "Share", + execute: (dispatch, { uuid }) => { + dispatch(openSharingDialog(uuid)); + } + }, { component: ToggleFavoriteAction, execute: (dispatch, resource) => { diff --git a/src/views-components/sharing-dialog/sharing-dialog.tsx b/src/views-components/sharing-dialog/sharing-dialog.tsx index b6c956e3..b50c74c7 100644 --- a/src/views-components/sharing-dialog/sharing-dialog.tsx +++ b/src/views-components/sharing-dialog/sharing-dialog.tsx @@ -6,29 +6,28 @@ import { compose, Dispatch } from 'redux'; import { connect } from 'react-redux'; import * as React from 'react'; -import { connectSharingDialog } from '~/store/sharing-dialog/sharing-dialog-actions'; +import { connectSharingDialog, saveSharingDialogChanges, hasChanges } from '~/store/sharing-dialog/sharing-dialog-actions'; import { WithDialogProps } from '~/store/dialog/with-dialog'; import { RootState } from '~/store/store'; import SharingDialogComponent, { SharingDialogDataProps, SharingDialogActionProps } from './sharing-dialog-component'; import { SharingDialogContent } from './sharing-dialog-content'; import { connectAdvancedViewSwitch, AdvancedViewSwitchInjectedProps } from './advanced-view-switch'; -import { isDirty } from 'redux-form'; const mapStateToProps = (state: RootState, { advancedViewOpen, ...props }: WithDialogProps & AdvancedViewSwitchInjectedProps): SharingDialogDataProps => ({ ...props, - saveEnabled: isDirty('SHARING_PUBLIC_ACCESS_FORM')(state) || - isDirty('SHARING_MANAGEMENT_FORM')(state) || - isDirty('SHARING_INVITATION_FORM')(state), + saveEnabled: hasChanges(state), advancedEnabled: !advancedViewOpen, children: , }); -const mapDispatchToProps = (_: Dispatch, { toggleAdvancedView, ...props }: WithDialogProps & AdvancedViewSwitchInjectedProps): SharingDialogActionProps => ({ +const mapDispatchToProps = (dispatch: Dispatch, { toggleAdvancedView, ...props }: WithDialogProps & AdvancedViewSwitchInjectedProps): SharingDialogActionProps => ({ ...props, onClose: props.closeDialog, onExited: toggleAdvancedView, - onSave: () => { console.log('save'); }, + onSave: () => { + dispatch(saveSharingDialogChanges); + }, onAdvanced: toggleAdvancedView, }); -- 2.30.2