Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

159 - Manage File User Permissions for the Files Tab #165

Merged
merged 17 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
d6ac29e
feat(FileUserPermissions): add FilePermissionsProvider to check if us…
MellyGray Aug 21, 2023
3769c73
feat(FileUserPermissions): add useFileDownloadPermission hook to get …
MellyGray Aug 22, 2023
0e0265d
feat(FileUserPermissions): refactor Files Table to read new permissions
MellyGray Aug 22, 2023
6dcfe6f
feat(FileUserPermissions): add WithFilePermissions decorator to stories
MellyGray Aug 22, 2023
98c60c4
feat(FileUserPermissions): add latestVersionRestricted to File model
MellyGray Aug 22, 2023
911e4c4
feat(FileUserPermissions): add hook to manage edit dataset permission
MellyGray Aug 22, 2023
fa2e348
feat(FileUserPermissions): replace TODO's by the real permissions hoo…
MellyGray Aug 22, 2023
81dfb57
feat(FileUserPermission): add TODO about the privateUrlUser
MellyGray Aug 22, 2023
e565a68
feat(FileUserPerission): manage downloadFile permission for privateUrl
MellyGray Aug 22, 2023
147478d
fix: tests failing because of missing permissions provider in the tests
MellyGray Aug 22, 2023
1d01d54
fix(stories): add WithFilePermissionsGranted decorator
MellyGray Aug 23, 2023
fee1543
feat(FileUserPermissions): fetch the permissions and save them before…
MellyGray Aug 23, 2023
409ebeb
Merge branch 'feature/139-files-table-file-action-buttons-ui' of http…
MellyGray Sep 19, 2023
ee46d31
fix(Storybook): remove duplicated story after merge
MellyGray Sep 19, 2023
bf5016d
Merge branch 'feature/139-files-table-file-action-buttons-ui' of http…
MellyGray Sep 19, 2023
cb2c153
Merge branch 'feature/139-files-table-file-action-buttons-ui' of http…
MellyGray Sep 20, 2023
abbf383
Merge branch 'feature/139-files-table-file-action-buttons-ui' of http…
MellyGray Sep 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading