Skip to content

Commit

Permalink
Merge pull request #165 from IQSS/feature/159-manage-user-permissions…
Browse files Browse the repository at this point in the history
…-files

159 - Manage File User Permissions for the Files Tab
  • Loading branch information
kcondon authored Sep 22, 2023
2 parents e847e8f + abbf383 commit 8f49243
Show file tree
Hide file tree
Showing 52 changed files with 1,440 additions and 491 deletions.
7 changes: 2 additions & 5 deletions public/locales/en/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,13 @@
"name": "Restricted",
"icon": "Restricted File Icon"
},
"restrictedAccess": {
"restrictedWithAccess": {
"name": "Restricted with Access Granted",
"icon": "Restricted with access Icon"
},
"embargoed": {
"name": "Embargoed"
},
"embargoedRestricted": {
"name": "Embargoed"
},
"public": {
"name": "Public",
"icon": "Public File Icon"
Expand Down Expand Up @@ -133,7 +130,7 @@
"cancel": "Cancel",
"close": "Close",
"embargoed": "Files are unavailable during the specified embargo",
"embargoedRestricted": "Files are unavailable during the specified embargo and restricted after that",
"embargoedThenRestricted": "Files are unavailable during the specified embargo and restricted after that",
"requestNotAllowed": "Users may not request access to files",
"accessRequested": "Access Requested"
},
Expand Down
57 changes: 11 additions & 46 deletions src/files/domain/models/File.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,11 @@ export class FileSize {

export interface FileAccess {
restricted: boolean
latestVersionRestricted: boolean
canBeRequested: boolean
requested: boolean
}

export enum FileAccessStatus {
PUBLIC = 'public',
RESTRICTED = 'restricted',
RESTRICTED_WITH_ACCESS = 'restrictedAccess',
EMBARGOED = 'embargoed',
EMBARGOED_RESTRICTED = 'embargoedRestricted'
}

export enum FileStatus {
DRAFT = 'draft',
RELEASED = 'released',
Expand Down Expand Up @@ -76,9 +69,12 @@ export interface FileDate {
date: string
}

export interface FileEmbargo {
active: boolean
date: string
export class FileEmbargo {
constructor(readonly dateAvailable: Date) {}

get isActive(): boolean {
return this.dateAvailable > new Date()
}
}

export interface FileTabularData {
Expand Down Expand Up @@ -109,16 +105,6 @@ export class FileType {
}
}

export enum FileLockStatus {
LOCKED = 'locked',
UNLOCKED = 'unlocked',
OPEN = 'open'
}

export interface FilePermissions {
canDownload: boolean
}

export enum FileIngestStatus {
NONE = 'none',
IN_PROGRESS = 'inProgress',
Expand All @@ -137,7 +123,6 @@ export class File {
readonly version: FileVersion,
readonly name: string,
readonly access: FileAccess,
readonly permissions: FilePermissions,
readonly type: FileType,
readonly size: FileSize,
readonly date: FileDate,
Expand All @@ -157,30 +142,10 @@ export class File {
return `/file?id=${this.id}&version=${this.version.toString()}`
}

get accessStatus(): FileAccessStatus {
if (!this.access.restricted && !this.embargo?.active) {
return FileAccessStatus.PUBLIC
}
if (!this.permissions.canDownload) {
if (!this.embargo?.active) {
return FileAccessStatus.RESTRICTED
}
return FileAccessStatus.EMBARGOED_RESTRICTED
}
if (!this.embargo?.active) {
return FileAccessStatus.RESTRICTED_WITH_ACCESS
}
return FileAccessStatus.EMBARGOED
}

// TODO - Use this attribute for the FilesThumbnail components
get lockStatus(): FileLockStatus {
if (!this.access.restricted && !this.embargo?.active) {
return FileLockStatus.OPEN
}
if (!this.permissions.canDownload) {
return FileLockStatus.LOCKED
get isActivelyEmbargoed(): boolean {
if (this.embargo) {
return this.embargo.isActive
}
return FileLockStatus.UNLOCKED
return false
}
}
10 changes: 10 additions & 0 deletions src/files/domain/models/FileUserPermissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface FileUserPermissions {
fileId: string
canDownloadFile: boolean
canEditDataset: boolean
}

export enum FilePermission {
DOWNLOAD_FILE = 'downloadFile',
EDIT_DATASET = 'editDataset'
}
2 changes: 2 additions & 0 deletions src/files/domain/repositories/FileRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { File } from '../models/File'
import { FileCriteria } from '../models/FileCriteria'
import { FilesCountInfo } from '../models/FilesCountInfo'
import { FilePaginationInfo } from '../models/FilePaginationInfo'
import { FileUserPermissions } from '../models/FileUserPermissions'

export interface FileRepository {
getAllByDatasetPersistentId: (
Expand All @@ -14,4 +15,5 @@ export interface FileRepository {
datasetPersistentId: string,
version?: string
) => Promise<FilesCountInfo>
getFileUserPermissionsById: (id: string) => Promise<FileUserPermissions>
}
22 changes: 22 additions & 0 deletions src/files/domain/useCases/checkFileDownloadPermission.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { FileRepository } from '../repositories/FileRepository'
import { File, FileStatus } from '../models/File'

export async function checkFileDownloadPermission(
fileRepository: FileRepository,
file: File
): Promise<boolean> {
if (file.version.status === FileStatus.DEACCESSIONED) {
return fileRepository.getFileUserPermissionsById(file.id).then((permissions) => {
return permissions.canEditDataset
})
}

const isRestricted = file.access.restricted || file.access.latestVersionRestricted
if (!isRestricted && !file.isActivelyEmbargoed) {
return true
}

return fileRepository.getFileUserPermissionsById(file.id).then((permissions) => {
return permissions.canDownloadFile
})
}
11 changes: 11 additions & 0 deletions src/files/domain/useCases/checkFileEditDatasetPermission.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { FileRepository } from '../repositories/FileRepository'
import { File } from '../models/File'

export async function checkFileEditDatasetPermission(
fileRepository: FileRepository,
file: File
): Promise<boolean> {
return fileRepository.getFileUserPermissionsById(file.id).then((permissions) => {
return permissions.canEditDataset
})
}
11 changes: 11 additions & 0 deletions src/files/infrastructure/FileJSDataverseRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { FilesMockData } from '../../stories/files/FileMockData'
import { FilesCountInfo } from '../domain/models/FilesCountInfo'
import { FilesCountInfoMother } from '../../../tests/component/files/domain/models/FilesCountInfoMother'
import { FilePaginationInfo } from '../domain/models/FilePaginationInfo'
import { FileUserPermissions } from '../domain/models/FileUserPermissions'
import { FileUserPermissionsMother } from '../../../tests/component/files/domain/models/FileUserPermissionsMother'

export class FileJSDataverseRepository implements FileRepository {
getAllByDatasetPersistentId(
Expand Down Expand Up @@ -34,4 +36,13 @@ export class FileJSDataverseRepository implements FileRepository {
}, 1000)
})
}
// eslint-disable-next-line unused-imports/no-unused-vars
getFileUserPermissionsById(id: string): Promise<FileUserPermissions> {
// TODO - implement using js-dataverse
return new Promise((resolve) => {
setTimeout(() => {
resolve(FileUserPermissionsMother.create())
}, 1000)
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import styles from './EditFilesMenu.module.scss'
import { EditFilesOptions } from './EditFilesOptions'
import { File } from '../../../../../../files/domain/models/File'
import { useTranslation } from 'react-i18next'
import { useFileEditDatasetPermission } from '../../../../../file/file-permissions/useFileEditDatasetPermission'

interface EditFilesMenuProps {
files: File[]
Expand All @@ -13,14 +14,14 @@ const MINIMUM_FILES_COUNT_TO_SHOW_EDIT_FILES_BUTTON = 1
export function EditFilesMenu({ files }: EditFilesMenuProps) {
const { t } = useTranslation('files')
const { user } = useSession()
const userHasDatasetUpdatePermissions = true // TODO - Implement permissions
const { sessionUserHasEditDatasetPermission } = useFileEditDatasetPermission(files[0] || {})
const datasetHasValidTermsOfAccess = true // TODO - Implement terms of access validation
const datasetLockedFromEdits = false // TODO - Ask Guillermo if this a dataset property coming from the api

if (
files.length < MINIMUM_FILES_COUNT_TO_SHOW_EDIT_FILES_BUTTON ||
!user ||
!userHasDatasetUpdatePermissions
!sessionUserHasEditDatasetPermission
) {
return <></>
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { File } from '../../../../../../../../files/domain/models/File'
import { Download, FileEarmark } from 'react-bootstrap-icons'
import { AccessStatusText } from './AccessStatusText'
import { AccessStatus } from './AccessStatus'
import { RequestAccessOption } from './RequestAccessOption'
import { DropdownButton, DropdownHeader, Tooltip } from '@iqss/dataverse-design-system'
import { useTranslation } from 'react-i18next'
Expand All @@ -21,13 +21,8 @@ export function AccessFileMenu({ file }: FileActionButtonAccessFileProps) {
<DropdownHeader>
{t('actions.accessFileMenu.headers.fileAccess')} <FileEarmark />
</DropdownHeader>
<AccessStatusText accessStatus={file.accessStatus} lockStatus={file.lockStatus} />
<RequestAccessOption
fileId={file.id}
versionStatus={file.version.status}
accessStatus={file.accessStatus}
access={file.access}
/>
<AccessStatus file={file} />
<RequestAccessOption file={file} />
</DropdownButton>
</Tooltip>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { File } from '../../../../../../../../files/domain/models/File'
import { Globe, LockFill, UnlockFill } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next'
import styles from './AccessFileMenu.module.scss'
import { DropdownButtonItem } from '@iqss/dataverse-design-system'
import { useFileDownloadPermission } from '../../../../../../../file/file-permissions/useFileDownloadPermission'

interface AccessStatusProps {
file: File
}

export function AccessStatus({ file }: AccessStatusProps) {
const { sessionUserHasFileDownloadPermission } = useFileDownloadPermission(file)

return (
<DropdownButtonItem disabled>
<span>
<AccessStatusIcon
sessionUserHasFileDownloadPermission={sessionUserHasFileDownloadPermission}
restricted={file.access.restricted}
/>{' '}
<AccessStatusText
file={file}
sessionUserHasFileDownloadPermission={sessionUserHasFileDownloadPermission}
/>
</span>
</DropdownButtonItem>
)
}

function AccessStatusIcon({
sessionUserHasFileDownloadPermission,
restricted
}: {
sessionUserHasFileDownloadPermission: boolean
restricted: boolean
}) {
const { t } = useTranslation('files')
if (restricted) {
if (sessionUserHasFileDownloadPermission) {
return (
<UnlockFill
title={t('table.fileAccess.restrictedWithAccess.icon')}
className={styles.success}
/>
)
}
return (
<LockFill
role="img"
title={t('table.fileAccess.restricted.icon')}
className={styles.danger}
/>
)
}
return <Globe role="img" title={t('table.fileAccess.public.icon')} className={styles.success} />
}

function AccessStatusText({
file,
sessionUserHasFileDownloadPermission
}: {
file: File
sessionUserHasFileDownloadPermission: boolean
}) {
const { t } = useTranslation('files')
const getAccessStatus = () => {
if (file.isActivelyEmbargoed) {
return 'embargoed'
}

if (file.access.restricted) {
if (!sessionUserHasFileDownloadPermission) {
return 'restricted'
}

return 'restrictedWithAccess'
}

return 'public'
}

return (
<span
className={
styles[
getAccessStatus() === 'public' || sessionUserHasFileDownloadPermission
? 'success'
: 'danger'
]
}>
{t(`table.fileAccess.${getAccessStatus()}.name`)}
</span>
)
}

This file was deleted.

Loading

0 comments on commit 8f49243

Please sign in to comment.