X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/a4a67b0a5436effb92f682c6edb512700420c374..5430c336b96cbb7c20bffa1cbdb8cffea32fb460:/src/store/sharing-dialog/sharing-dialog-actions.ts diff --git a/src/store/sharing-dialog/sharing-dialog-actions.ts b/src/store/sharing-dialog/sharing-dialog-actions.ts index 53c751e1..fb34398e 100644 --- a/src/store/sharing-dialog/sharing-dialog-actions.ts +++ b/src/store/sharing-dialog/sharing-dialog-actions.ts @@ -4,7 +4,16 @@ import { dialogActions } from "store/dialog/dialog-actions"; import { withDialog } from "store/dialog/with-dialog"; -import { SHARING_DIALOG_NAME, SharingPublicAccessFormData, SHARING_PUBLIC_ACCESS_FORM_NAME, SHARING_INVITATION_FORM_NAME, SharingManagementFormData, SharingInvitationFormData, VisibilityLevel, getSharingMangementFormData, getSharingPublicAccessFormData } from './sharing-dialog-types'; +import { + SHARING_DIALOG_NAME, + SHARING_INVITATION_FORM_NAME, + SharingManagementFormData, + SharingInvitationFormData, + getSharingMangementFormData, + SharingPublicAccessFormData, + VisibilityLevel, + SHARING_PUBLIC_ACCESS_FORM_NAME, +} from './sharing-dialog-types'; import { Dispatch } from 'redux'; import { ServiceRepository } from "services/services"; import { FilterBuilder } from 'services/api/filter-builder'; @@ -12,20 +21,22 @@ import { initialize, getFormValues, 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"; -import { PermissionResource } from 'models/permission'; +import { PermissionLevel, PermissionResource } from 'models/permission'; import { differenceWith } from "lodash"; import { withProgress } from "store/progress-indicator/with-progress"; import { progressIndicatorActions } from 'store/progress-indicator/progress-indicator-actions'; import { snackbarActions, SnackbarKind } from "../snackbar/snackbar-actions"; -import { extractUuidKind, extractUuidObjectType, ResourceKind, ResourceObjectType } from "models/resource"; -import { ApiClientAuthorizationService } from "services/api-client-authorization-service/api-client-authorization-service"; +import { + extractUuidObjectType, + ResourceObjectType +} from "models/resource"; import { resourcesActions } from "store/resources/resources-actions"; +import { getPublicGroupUuid, getAllUsersGroupUuid } from "store/workflow-panel/workflow-panel-actions"; +import { getSharingPublicAccessFormData } from './sharing-dialog-types'; export const openSharingDialog = (resourceUuid: string, refresh?: () => void) => (dispatch: Dispatch) => { - dispatch(dialogActions.OPEN_DIALOG({ id: SHARING_DIALOG_NAME, data: {resourceUuid, refresh} })); + dispatch(dialogActions.OPEN_DIALOG({ id: SHARING_DIALOG_NAME, data: { resourceUuid, refresh } })); dispatch(loadSharingDialog); }; @@ -51,35 +62,19 @@ export const saveSharingDialogChanges = async (dispatch: Dispatch, getState: () } }; -export const sendSharingInvitations = async (dispatch: Dispatch, getState: () => RootState) => { - dispatch(progressIndicatorActions.START_WORKING(SHARING_DIALOG_NAME)); - await dispatch(sendInvitations); - dispatch(closeSharingDialog()); - dispatch(snackbarActions.OPEN_SNACKBAR({ - message: 'Resource has been shared', - kind: SnackbarKind.SUCCESS, - })); - dispatch(progressIndicatorActions.STOP_WORKING(SHARING_DIALOG_NAME)); - - const dialog = getDialog(getState().dialog, SHARING_DIALOG_NAME); - if (dialog && dialog.data.refresh) { - dialog.data.refresh(); - } -}; - export interface SharingDialogData { resourceUuid: string; refresh: () => void; } -export const createSharingToken = async (dispatch: Dispatch, getState: () => RootState, { apiClientAuthorizationService }: ServiceRepository) => { +export const createSharingToken = (expDate: Date | undefined) => async (dispatch: Dispatch, getState: () => RootState, { apiClientAuthorizationService }: ServiceRepository) => { const dialog = getDialog(getState().dialog, SHARING_DIALOG_NAME); if (dialog) { const resourceUuid = dialog.data.resourceUuid; if (extractUuidObjectType(resourceUuid) === ResourceObjectType.COLLECTION) { dispatch(progressIndicatorActions.START_WORKING(SHARING_DIALOG_NAME)); try { - const sharingToken = await apiClientAuthorizationService.createCollectionSharingToken(resourceUuid); + const sharingToken = await apiClientAuthorizationService.createCollectionSharingToken(resourceUuid, expDate); dispatch(resourcesActions.SET_RESOURCES([sharingToken])); dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Sharing URL created', @@ -120,18 +115,17 @@ export const deleteSharingToken = (uuid: string) => async (dispatch: Dispatch, g } }; -const loadSharingDialog = async (dispatch: Dispatch, getState: () => RootState, { permissionService, apiClientAuthorizationService }: ServiceRepository) => { +const loadSharingDialog = async (dispatch: Dispatch, getState: () => RootState, { apiClientAuthorizationService }: ServiceRepository) => { const dialog = getDialog(getState().dialog, SHARING_DIALOG_NAME); + const sharingURLsDisabled = getState().auth.config.clusterConfig.Workbench.DisableSharingURLsUI; if (dialog) { dispatch(progressIndicatorActions.START_WORKING(SHARING_DIALOG_NAME)); try { const resourceUuid = dialog.data.resourceUuid; - const { items } = await permissionService.listResourcePermissions(resourceUuid); - dispatch(initializePublicAccessForm(items)); - await dispatch(initializeManagementForm(items)); + await dispatch(initializeManagementForm); // For collections, we need to load the public sharing tokens - if (extractUuidObjectType(resourceUuid) === ResourceObjectType.COLLECTION) { + if (!sharingURLsDisabled && extractUuidObjectType(resourceUuid) === ResourceObjectType.COLLECTION) { const sharingTokens = await apiClientAuthorizationService.listCollectionSharingTokens(resourceUuid); dispatch(resourcesActions.SET_RESOURCES([...sharingTokens.items])); } @@ -139,7 +133,8 @@ const loadSharingDialog = async (dispatch: Dispatch, getState: () => RootState, dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'You do not have access to share this item', hideDuration: 2000, - kind: SnackbarKind.ERROR })); + kind: SnackbarKind.ERROR + })); dispatch(dialogActions.CLOSE_DIALOG({ id: SHARING_DIALOG_NAME })); } finally { dispatch(progressIndicatorActions.STOP_WORKING(SHARING_DIALOG_NAME)); @@ -147,60 +142,87 @@ const loadSharingDialog = async (dispatch: Dispatch, getState: () => RootState, } }; -const initializeManagementForm = (permissionLinks: PermissionResource[]) => - async (dispatch: Dispatch, getState: () => RootState, { userService, groupsService }: ServiceRepository) => { - - const filters = new FilterBuilder() - .addIn('uuid', permissionLinks.map(({ tailUuid }) => tailUuid)) - .getFilters(); +export const initializeManagementForm = async (dispatch: Dispatch, getState: () => RootState, { userService, groupsService, permissionService }: ServiceRepository) => { - const { items: users } = await userService.list({ filters, count: "none" }); - const { items: groups } = await groupsService.list({ filters, count: "none" }); - - const getEmail = (tailUuid: string) => { - const user = users.find(({ uuid }) => uuid === tailUuid); - const group = groups.find(({ uuid }) => uuid === tailUuid); - return user - ? user.email - : group - ? group.name - : tailUuid; - }; - - const managementPermissions = permissionLinks - .filter(item => - item.tailUuid !== getPublicGroupUuid(getState())) - .map(({ tailUuid, name, uuid }) => ({ - email: getEmail(tailUuid), - permissions: name as PermissionLevel, - permissionUuid: uuid, - })); + const dialog = getDialog(getState().dialog, SHARING_DIALOG_NAME); + if (!dialog) { + return; + } + dispatch(progressIndicatorActions.START_WORKING(SHARING_DIALOG_NAME)); + const resourceUuid = dialog?.data.resourceUuid; + const { items: permissionLinks } = await permissionService.listResourcePermissions(resourceUuid); + dispatch(initializePublicAccessForm(permissionLinks)); + const filters = new FilterBuilder() + .addIn('uuid', Array.from(new Set(permissionLinks.map(({ tailUuid }) => tailUuid)))) + .getFilters(); + + const { items: users } = await userService.list({ filters, count: "none", limit: 1000 }); + const { items: groups } = await groupsService.list({ filters, count: "none", limit: 1000 }); + + const getEmail = (tailUuid: string) => { + const user = users.find(({ uuid }) => uuid === tailUuid); + const group = groups.find(({ uuid }) => uuid === tailUuid); + return user + ? user.email + : group + ? group.name + : tailUuid; + }; - const managementFormData: SharingManagementFormData = { - permissions: managementPermissions, - initialPermissions: managementPermissions, - }; + const managementPermissions = permissionLinks + .map(({ tailUuid, name, uuid }) => ({ + email: getEmail(tailUuid), + permissions: name as PermissionLevel, + permissionUuid: uuid, + })); - dispatch(initialize(SHARING_MANAGEMENT_FORM_NAME, managementFormData)); + const managementFormData: SharingManagementFormData = { + permissions: managementPermissions, + initialPermissions: managementPermissions, }; + dispatch(initialize(SHARING_MANAGEMENT_FORM_NAME, managementFormData)); + dispatch(progressIndicatorActions.STOP_WORKING(SHARING_DIALOG_NAME)); +}; + const initializePublicAccessForm = (permissionLinks: PermissionResource[]) => - (dispatch: Dispatch, getState: () => RootState, ) => { + (dispatch: Dispatch, getState: () => RootState,) => { + + const state = getState(); const [publicPermission] = permissionLinks - .filter(item => item.tailUuid === getPublicGroupUuid(getState())); + .filter(item => item.tailUuid === getPublicGroupUuid(state)); - const publicAccessFormData: SharingPublicAccessFormData = publicPermission - ? { + const [allUsersPermission] = permissionLinks + .filter(item => item.tailUuid === getAllUsersGroupUuid(state)); + + let publicAccessFormData: SharingPublicAccessFormData; + + if (publicPermission) { + publicAccessFormData = { visibility: VisibilityLevel.PUBLIC, - permissionUuid: publicPermission.uuid, - } - : { - visibility: permissionLinks.length > 0 - ? VisibilityLevel.SHARED - : VisibilityLevel.PRIVATE, - permissionUuid: '', + initialVisibility: VisibilityLevel.PUBLIC, + permissionUuid: publicPermission.uuid + }; + } else if (allUsersPermission) { + publicAccessFormData = { + visibility: VisibilityLevel.ALL_USERS, + initialVisibility: VisibilityLevel.ALL_USERS, + permissionUuid: allUsersPermission.uuid }; + } else if (permissionLinks.length > 0) { + publicAccessFormData = { + visibility: VisibilityLevel.SHARED, + initialVisibility: VisibilityLevel.SHARED, + permissionUuid: '' + }; + } else { + publicAccessFormData = { + visibility: VisibilityLevel.PRIVATE, + initialVisibility: VisibilityLevel.PRIVATE, + permissionUuid: '' + }; + } dispatch(initialize(SHARING_PUBLIC_ACCESS_FORM_NAME, publicAccessFormData)); }; @@ -210,19 +232,21 @@ const savePublicPermissionChanges = async (_: Dispatch, getState: () => RootStat const { user } = state.auth; const dialog = getDialog(state.dialog, SHARING_DIALOG_NAME); if (dialog && user) { - const { permissionUuid, visibility } = getSharingPublicAccessFormData(state); - - if (permissionUuid) { - if (visibility === VisibilityLevel.PUBLIC) { - await permissionService.update(permissionUuid, { - name: PermissionLevel.CAN_READ - }); - } else { - await permissionService.delete(permissionUuid); - } - + const { permissionUuid, visibility, initialVisibility } = getSharingPublicAccessFormData(state); + // If visibility level changed, delete the previous link to public/all users. + // On PRIVATE this link will be deleted by saveManagementChanges + // so don't double delete (which would show an error dialog). + if (permissionUuid !== "" && visibility !== initialVisibility) { + await permissionService.delete(permissionUuid); + } + if (visibility === VisibilityLevel.ALL_USERS) { + await permissionService.create({ + ownerUuid: user.uuid, + headUuid: dialog.data.resourceUuid, + tailUuid: getAllUsersGroupUuid(state), + name: PermissionLevel.CAN_READ, + }); } else if (visibility === VisibilityLevel.PUBLIC) { - await permissionService.create({ ownerUuid: user.uuid, headUuid: dialog.data.resourceUuid, @@ -240,60 +264,41 @@ const saveManagementChanges = async (_: Dispatch, getState: () => RootState, { p if (dialog && user) { const { initialPermissions, permissions } = getSharingMangementFormData(state); const { visibility } = getSharingPublicAccessFormData(state); - - if (visibility === VisibilityLevel.PRIVATE) { - for (const permission of initialPermissions) { - await permissionService.delete(permission.permissionUuid); - } - } else { - const cancelledPermissions = differenceWith( + const cancelledPermissions = visibility === VisibilityLevel.PRIVATE + ? initialPermissions + : differenceWith( initialPermissions, permissions, (a, b) => a.permissionUuid === b.permissionUuid ); - for (const { permissionUuid } of cancelledPermissions) { - await permissionService.delete(permissionUuid); - } - - for (const permission of permissions) { - await permissionService.update(permission.permissionUuid, { name: permission.permissions }); - } - } + const deletions = cancelledPermissions.map(async ({ permissionUuid }) => { + try { + await permissionService.delete(permissionUuid, false); + } catch (e) { } + }); + const updates = permissions.map(async update => { + try { + await permissionService.update(update.permissionUuid, { name: update.permissions }, false); + } catch (e) { } + }); + await Promise.all([...deletions, ...updates]); } }; -const sendInvitations = async (_: Dispatch, getState: () => RootState, { permissionService, userService }: ServiceRepository) => { +const sendInvitations = async (_: 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 getGroupsFromForm = invitations.invitedPeople.filter((invitation) => extractUuidKind(invitation.uuid) === ResourceKind.GROUP); - const getUsersFromForm = invitations.invitedPeople.filter((invitation) => extractUuidKind(invitation.uuid) === ResourceKind.USER); - - const invitationDataUsers = getUsersFromForm - .map(person => ({ - ownerUuid: user.uuid, - headUuid: dialog.data.resourceUuid, - tailUuid: person.uuid, - name: invitations.permissions - })); - - const invitationsDataGroups = getGroupsFromForm.map( - group => ({ - ownerUuid: user.uuid, - headUuid: dialog.data.resourceUuid, - tailUuid: group.uuid, - name: invitations.permissions - }) - ); - - const data = invitationDataUsers.concat(invitationsDataGroups); - - for (const invitation of data) { - await permissionService.create(invitation); - } + const data = invitations.invitedPeople.map(invitee => ({ + ownerUuid: user.uuid, + headUuid: dialog.data.resourceUuid, + tailUuid: invitee.uuid, + name: invitations.permissions + })); + const changes = data.map(invitation => permissionService.create(invitation)); + await Promise.all(changes); } };