From: Lucas Di Pentima Date: Mon, 23 May 2022 18:43:58 +0000 (-0300) Subject: 16115: Brings back the Visibility Level form. X-Git-Tag: 2.4.1~1^2~2^2~2 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/6fbb4eca48ea5907887e61d60b60987cf2d1c8ca 16115: Brings back the Visibility Level form. To avoid confusing users, this form is back and it's rendered as the first row of the permissions list instead of being hidden inside an 'Advanced' mode. Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima --- diff --git a/src/store/sharing-dialog/sharing-dialog-actions.ts b/src/store/sharing-dialog/sharing-dialog-actions.ts index ffd81fb7..367eea81 100644 --- a/src/store/sharing-dialog/sharing-dialog-actions.ts +++ b/src/store/sharing-dialog/sharing-dialog-actions.ts @@ -10,6 +10,9 @@ import { SharingManagementFormData, SharingInvitationFormData, getSharingMangementFormData, + SharingPublicAccessFormData, + VisibilityLevel, + SHARING_PUBLIC_ACCESS_FORM_NAME, } from './sharing-dialog-types'; import { Dispatch } from 'redux'; import { ServiceRepository } from "services/services"; @@ -18,7 +21,7 @@ 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 { 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'; @@ -28,6 +31,8 @@ import { ResourceObjectType } from "models/resource"; import { resourcesActions } from "store/resources/resources-actions"; +import { getPublicGroupUuid } from "store/workflow-panel/workflow-panel-actions"; +import { getSharingPublicAccessFormData } from './sharing-dialog-types'; export const openSharingDialog = (resourceUuid: string, refresh?: () => void) => (dispatch: Dispatch) => { @@ -44,6 +49,7 @@ export const connectSharingDialogProgress = withProgress(SHARING_DIALOG_NAME); export const saveSharingDialogChanges = async (dispatch: Dispatch, getState: () => RootState) => { dispatch(progressIndicatorActions.START_WORKING(SHARING_DIALOG_NAME)); + await dispatch(savePublicPermissionChanges); await dispatch(saveManagementChanges); await dispatch(sendInvitations); dispatch(reset(SHARING_INVITATION_FORM_NAME)); @@ -109,7 +115,7 @@ 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); if (dialog) { @@ -143,6 +149,7 @@ export const initializeManagementForm = async (dispatch: Dispatch, getState: () 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', permissionLinks.map(({ tailUuid }) => tailUuid)) .getFilters(); @@ -161,6 +168,8 @@ export const initializeManagementForm = async (dispatch: Dispatch, getState: () }; const managementPermissions = permissionLinks + .filter(item => + item.tailUuid !== getPublicGroupUuid(getState())) .map(({ tailUuid, name, uuid }) => ({ email: getEmail(tailUuid), permissions: name as PermissionLevel, @@ -176,17 +185,63 @@ export const initializeManagementForm = async (dispatch: Dispatch, getState: () dispatch(progressIndicatorActions.STOP_WORKING(SHARING_DIALOG_NAME)); }; +const initializePublicAccessForm = (permissionLinks: PermissionResource[]) => + (dispatch: Dispatch, getState: () => RootState, ) => { + const [publicPermission] = permissionLinks + .filter(item => item.tailUuid === getPublicGroupUuid(getState())); + const publicAccessFormData: SharingPublicAccessFormData = publicPermission + ? { + visibility: VisibilityLevel.PUBLIC, + permissionUuid: publicPermission.uuid, + } + : { + visibility: permissionLinks.length > 0 + ? VisibilityLevel.SHARED + : VisibilityLevel.PRIVATE, + permissionUuid: '', + }; + dispatch(initialize(SHARING_PUBLIC_ACCESS_FORM_NAME, publicAccessFormData)); + }; + +const savePublicPermissionChanges = 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 { permissionUuid, visibility } = getSharingPublicAccessFormData(state); + if (permissionUuid) { + if (visibility === VisibilityLevel.PUBLIC) { + await permissionService.update(permissionUuid, { + name: PermissionLevel.CAN_READ + }); + } else { + await permissionService.delete(permissionUuid); + } + } else if (visibility === VisibilityLevel.PUBLIC) { + await permissionService.create({ + ownerUuid: user.uuid, + headUuid: dialog.data.resourceUuid, + tailUuid: getPublicGroupUuid(state), + name: PermissionLevel.CAN_READ, + }); + } + } +}; + const saveManagementChanges = 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 { initialPermissions, permissions } = getSharingMangementFormData(state); - const cancelledPermissions = differenceWith( - initialPermissions, - permissions, - (a, b) => a.permissionUuid === b.permissionUuid - ); + const { visibility } = getSharingPublicAccessFormData(state); + const cancelledPermissions = visibility === VisibilityLevel.PRIVATE + ? initialPermissions + : differenceWith( + initialPermissions, + permissions, + (a, b) => a.permissionUuid === b.permissionUuid + ); const deletions = cancelledPermissions.map(({ permissionUuid }) => permissionService.delete(permissionUuid)); diff --git a/src/store/sharing-dialog/sharing-dialog-types.ts b/src/store/sharing-dialog/sharing-dialog-types.ts index 7ca8b5c5..a05224e2 100644 --- a/src/store/sharing-dialog/sharing-dialog-types.ts +++ b/src/store/sharing-dialog/sharing-dialog-types.ts @@ -7,9 +7,21 @@ import { getFormValues, isDirty } from 'redux-form'; import { RootState } from 'store/store'; export const SHARING_DIALOG_NAME = 'SHARING_DIALOG_NAME'; +export const SHARING_PUBLIC_ACCESS_FORM_NAME = 'SHARING_PUBLIC_ACCESS_FORM_NAME'; export const SHARING_MANAGEMENT_FORM_NAME = 'SHARING_MANAGEMENT_FORM_NAME'; export const SHARING_INVITATION_FORM_NAME = 'SHARING_INVITATION_FORM_NAME'; +export enum VisibilityLevel { + PRIVATE = 'Private', + SHARED = 'Shared', + PUBLIC = 'Public', +} + +export interface SharingPublicAccessFormData { + visibility: VisibilityLevel; + permissionUuid: string; +} + export interface SharingManagementFormData { permissions: SharingManagementFormDataRow[]; initialPermissions: SharingManagementFormDataRow[]; @@ -35,6 +47,10 @@ export interface SharingInvitationFormPersonData { export const getSharingMangementFormData = (state: any) => getFormValues(SHARING_MANAGEMENT_FORM_NAME)(state) as SharingManagementFormData; +export const getSharingPublicAccessFormData = (state: any) => + getFormValues(SHARING_PUBLIC_ACCESS_FORM_NAME)(state) as SharingPublicAccessFormData; + 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/views-components/sharing-dialog/sharing-dialog-component.tsx b/src/views-components/sharing-dialog/sharing-dialog-component.tsx index 0fa0056c..cd7ea9bb 100644 --- a/src/views-components/sharing-dialog/sharing-dialog-component.tsx +++ b/src/views-components/sharing-dialog/sharing-dialog-component.tsx @@ -38,6 +38,7 @@ import { } from 'material-ui-pickers'; import DateFnsUtils from "@date-io/date-fns"; import moment from 'moment'; +import { SharingPublicAccessForm } from './sharing-public-access-form'; export interface SharingDialogDataProps { open: boolean; @@ -101,9 +102,12 @@ export default (props: SharingDialogDataProps & SharingDialogActionProps) => { { tabNr === SharingDialogTab.PERMISSIONS && - - - + + + + + + } { tabNr === SharingDialogTab.URLS && diff --git a/src/views-components/sharing-dialog/sharing-management-form-component.tsx b/src/views-components/sharing-dialog/sharing-management-form-component.tsx index 2ebf8c2d..d4d10952 100644 --- a/src/views-components/sharing-dialog/sharing-management-form-component.tsx +++ b/src/views-components/sharing-dialog/sharing-management-form-component.tsx @@ -21,13 +21,8 @@ export default () => ; const SharingManagementFieldArray = ({ fields }: WrappedFieldArrayProps<{ email: string }>) => -
- { - fields.length > 0 - ? fields.map((field, index, fields) => - ) - : No permissions set - } +
{ fields.map((field, index, fields) => + ) }
; diff --git a/src/views-components/sharing-dialog/sharing-public-access-form-component.tsx b/src/views-components/sharing-dialog/sharing-public-access-form-component.tsx new file mode 100644 index 00000000..7ec71161 --- /dev/null +++ b/src/views-components/sharing-dialog/sharing-public-access-form-component.tsx @@ -0,0 +1,54 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import React from 'react'; +import { Grid, StyleRulesCallback, Divider, Typography } from '@material-ui/core'; +import { Field, WrappedFieldProps } from 'redux-form'; +import { WithStyles } from '@material-ui/core/styles'; +import withStyles from '@material-ui/core/styles/withStyles'; +import { VisibilityLevelSelect } from './visibility-level-select'; +import { VisibilityLevel } from 'store/sharing-dialog/sharing-dialog-types'; + +const sharingPublicAccessStyles: StyleRulesCallback<'root'> = theme => ({ + root: { + padding: `${theme.spacing.unit * 2}px 0`, + } +}); + +const SharingPublicAccessForm = withStyles(sharingPublicAccessStyles)( + ({ classes, visibility }: WithStyles<'root'> & { visibility: VisibilityLevel }) => + <> + + + + + {renderVisibilityInfo(visibility)} + + + + + + + +); + +const renderVisibilityInfo = (visibility: VisibilityLevel) => { + switch (visibility) { + case VisibilityLevel.PUBLIC: + return 'Anyone can access'; + case VisibilityLevel.SHARED: + return 'Specific people can access'; + case VisibilityLevel.PRIVATE: + return 'Only you can access'; + default: + return ''; + } +}; + +export default ({ visibility }: { visibility: VisibilityLevel }) => + ; + +const VisibilityLevelSelectComponent = ({ input }: WrappedFieldProps) => + ; + diff --git a/src/views-components/sharing-dialog/sharing-public-access-form.tsx b/src/views-components/sharing-dialog/sharing-public-access-form.tsx new file mode 100644 index 00000000..8ee1d94d --- /dev/null +++ b/src/views-components/sharing-dialog/sharing-public-access-form.tsx @@ -0,0 +1,24 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { reduxForm } from 'redux-form'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; +import SharingPublicAccessFormComponent from './sharing-public-access-form-component'; +import { SHARING_PUBLIC_ACCESS_FORM_NAME, VisibilityLevel } from 'store/sharing-dialog/sharing-dialog-types'; +import { RootState } from 'store/store'; +import { getSharingPublicAccessFormData } from '../../store/sharing-dialog/sharing-dialog-types'; + +export const SharingPublicAccessForm = compose( + reduxForm( + { form: SHARING_PUBLIC_ACCESS_FORM_NAME } + ), + connect( + (state: RootState) => { + const { visibility } = getSharingPublicAccessFormData(state) || { visibility: VisibilityLevel.PRIVATE }; + return { visibility }; + } + ) +)(SharingPublicAccessFormComponent); + diff --git a/src/views-components/sharing-dialog/visibility-level-select.tsx b/src/views-components/sharing-dialog/visibility-level-select.tsx new file mode 100644 index 00000000..434b8f51 --- /dev/null +++ b/src/views-components/sharing-dialog/visibility-level-select.tsx @@ -0,0 +1,55 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import React from 'react'; +import { MenuItem, Select, withStyles, StyleRulesCallback } from '@material-ui/core'; +import Lock from '@material-ui/icons/Lock'; +import People from '@material-ui/icons/People'; +import Public from '@material-ui/icons/Public'; +import { WithStyles } from '@material-ui/core/styles'; +import { SelectProps } from '@material-ui/core/Select'; +import { SelectItem } from './select-item'; +import { VisibilityLevel } from 'store/sharing-dialog/sharing-dialog-types'; + + +type VisibilityLevelSelectClasses = 'value'; + +const VisibilityLevelSelectStyles: StyleRulesCallback = theme => ({ + value: { + marginLeft: theme.spacing.unit, + } +}); +export const VisibilityLevelSelect = withStyles(VisibilityLevelSelectStyles)( + ({ classes, ...props }: SelectProps & WithStyles) => + ); + +const renderPermissionItem = (value: string) => + ; + +const getIcon = (value: string) => { + switch (value) { + case VisibilityLevel.PUBLIC: + return Public; + case VisibilityLevel.SHARED: + return People; + case VisibilityLevel.PRIVATE: + return Lock; + default: + return Lock; + } +}; +