SharingManagementFormData,
SharingInvitationFormData,
getSharingMangementFormData,
+ SharingPublicAccessFormData,
+ VisibilityLevel,
+ SHARING_PUBLIC_ACCESS_FORM_NAME,
} from './sharing-dialog-types';
import { Dispatch } from 'redux';
import { ServiceRepository } from "services/services";
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';
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) => {
export const saveSharingDialogChanges = async (dispatch: Dispatch, getState: () => RootState) => {
dispatch(progressIndicatorActions.START_WORKING(SHARING_DIALOG_NAME));
+ await dispatch<any>(savePublicPermissionChanges);
await dispatch<any>(saveManagementChanges);
await dispatch<any>(sendInvitations);
dispatch(reset(SHARING_INVITATION_FORM_NAME));
}
};
-const loadSharingDialog = async (dispatch: Dispatch, getState: () => RootState, { permissionService, apiClientAuthorizationService }: ServiceRepository) => {
+const loadSharingDialog = async (dispatch: Dispatch, getState: () => RootState, { apiClientAuthorizationService }: ServiceRepository) => {
const dialog = getDialog<SharingDialogData>(getState().dialog, SHARING_DIALOG_NAME);
if (dialog) {
dispatch(progressIndicatorActions.START_WORKING(SHARING_DIALOG_NAME));
const resourceUuid = dialog?.data.resourceUuid;
const { items: permissionLinks } = await permissionService.listResourcePermissions(resourceUuid);
+ dispatch<any>(initializePublicAccessForm(permissionLinks));
const filters = new FilterBuilder()
.addIn('uuid', permissionLinks.map(({ tailUuid }) => tailUuid))
.getFilters();
};
const managementPermissions = permissionLinks
+ .filter(item =>
+ item.tailUuid !== getPublicGroupUuid(getState()))
.map(({ tailUuid, name, uuid }) => ({
email: getEmail(tailUuid),
permissions: name as PermissionLevel,
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<SharingDialogData>(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<string>(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));
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[];
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);
} 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;
<DialogContent>
{ tabNr === SharingDialogTab.PERMISSIONS &&
<Grid container direction='column' spacing={24}>
- <Grid item>
- <SharingManagementForm />
- </Grid>
+ <Grid item>
+ <SharingPublicAccessForm />
+ </Grid>
+ <Grid item>
+ <SharingManagementForm />
+ </Grid>
</Grid>
}
{ tabNr === SharingDialogTab.URLS &&
<FieldArray name='permissions' component={SharingManagementFieldArray as any} />;
const SharingManagementFieldArray = ({ fields }: WrappedFieldArrayProps<{ email: string }>) =>
- <div>
- {
- fields.length > 0
- ? fields.map((field, index, fields) =>
- <PermissionManagementRow key={field} {...{ field, index, fields }} />)
- : <Typography>No permissions set</Typography>
- }
+ <div>{ fields.map((field, index, fields) =>
+ <PermissionManagementRow key={field} {...{ field, index, fields }} />) }
<Divider />
</div>;
--- /dev/null
+// 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 }) =>
+ <>
+ <Divider />
+ <Grid container alignItems='center' spacing={8} className={classes.root}>
+ <Grid item xs={8}>
+ <Typography variant='subtitle1'>
+ {renderVisibilityInfo(visibility)}
+ </Typography>
+ </Grid>
+ <Grid item xs={4} container wrap='nowrap'>
+ <Field name='visibility' component={VisibilityLevelSelectComponent} />
+ </Grid>
+ </Grid>
+ </>
+);
+
+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 }) =>
+ <SharingPublicAccessForm {...{ visibility }} />;
+
+const VisibilityLevelSelectComponent = ({ input }: WrappedFieldProps) =>
+ <VisibilityLevelSelect fullWidth disableUnderline {...input} />;
+
--- /dev/null
+// 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);
+
--- /dev/null
+// 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<VisibilityLevelSelectClasses> = theme => ({
+ value: {
+ marginLeft: theme.spacing.unit,
+ }
+});
+export const VisibilityLevelSelect = withStyles(VisibilityLevelSelectStyles)(
+ ({ classes, ...props }: SelectProps & WithStyles<VisibilityLevelSelectClasses>) =>
+ <Select
+ {...props}
+ renderValue={renderPermissionItem}
+ inputProps={{ classes }}>
+ <MenuItem value={VisibilityLevel.PUBLIC}>
+ {renderPermissionItem(VisibilityLevel.PUBLIC)}
+ </MenuItem>
+ <MenuItem value={VisibilityLevel.SHARED}>
+ {renderPermissionItem(VisibilityLevel.SHARED)}
+ </MenuItem>
+ <MenuItem value={VisibilityLevel.PRIVATE}>
+ {renderPermissionItem(VisibilityLevel.PRIVATE)}
+ </MenuItem>
+ </Select>);
+
+const renderPermissionItem = (value: string) =>
+ <SelectItem {...{ value, icon: getIcon(value) }} />;
+
+const getIcon = (value: string) => {
+ switch (value) {
+ case VisibilityLevel.PUBLIC:
+ return Public;
+ case VisibilityLevel.SHARED:
+ return People;
+ case VisibilityLevel.PRIVATE:
+ return Lock;
+ default:
+ return Lock;
+ }
+};
+