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

129 - Dataset Action Buttons UI #181

Merged
merged 38 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
2f512a1
feat(DatasetActionButtons): add AccessDatasetMenu component
MellyGray Sep 8, 2023
a9b1df2
feat(DatasetActionButtons): add PublishDatasetMenu component
MellyGray Sep 8, 2023
5c53570
feat(DatasetActionButtons): add ChangeCurationStatusMenu component
MellyGray Sep 8, 2023
821d38f
feat(DesignSystem): add HTMLAttributes to ButtonGroup
MellyGray Aug 16, 2023
610556b
feat(DatasetActionButtons): add stories
MellyGray Sep 11, 2023
e666312
feat(DropdownHeader): add DropdownHeader to the Design System
MellyGray Aug 9, 2023
0f2a626
feat(DesignSystem): add disabled property to DropdownButtonItem
MellyGray Aug 10, 2023
d4a32f1
feat(DesignSystem): add disabled property to Dropdown
MellyGray Aug 14, 2023
e2736fc
feat(DatasetActionButtons): add SubmitForReviewButton component
MellyGray Sep 11, 2023
c295c7b
fix(DesignSystem): adding className to ButtonGroup
MellyGray Sep 12, 2023
1c4ab2d
feat(DatasetActionButtons): add EditDatasetMenu component
MellyGray Sep 12, 2023
17ef891
feat(DatasetActionButtons): add isValid property to Dataset model
MellyGray Sep 12, 2023
6149156
feat(DatasetActionButtons): add LinkDatasetButton component
MellyGray Sep 12, 2023
a97d01e
feat(DatasetActionButtons): add Return to Author button to the Publis…
MellyGray Sep 13, 2023
eb2e7b6
feat(DatasetActionButtons): add translations
MellyGray Sep 14, 2023
b0cd203
fix: failing tests
MellyGray Sep 14, 2023
750688d
fix(DatasetActionButtons): use global isReleased instead of version s…
MellyGray Sep 14, 2023
068c6b9
feat(DatasetActionButtons): add stories
MellyGray Sep 14, 2023
ec335d9
Merge branch 'develop' of https://github.com/IQSS/dataverse-frontend …
MellyGray Sep 22, 2023
53947e9
Merge branch 'develop' of https://github.com/IQSS/dataverse-frontend …
MellyGray Sep 22, 2023
1c9a583
fix(test): JSDatasetMapper version check
MellyGray Sep 25, 2023
603553b
feat(LinkDatasetButton): check user is authenticated
MellyGray Sep 25, 2023
99c097d
feat(DatasetActionButtons): create DatasetProvider to get dataset fro…
MellyGray Sep 25, 2023
23df052
Merge branch 'develop' of https://github.com/IQSS/dataverse-frontend …
MellyGray Sep 25, 2023
74696e2
fix: timeout in some e2e tests
MellyGray Sep 25, 2023
7fa9000
feat(DatasetUploadFilesButton): add dataset permissions
MellyGray Sep 25, 2023
07dc40d
feat(EditFilesMenu): add dataset permissions
MellyGray Sep 25, 2023
f41efad
feat(FileOptionsMenu): add dataset permissions
MellyGray Sep 26, 2023
99b2159
feat(IngestInfoMessage): add dataset permissions
MellyGray Sep 26, 2023
f219cf6
fix: remove unused hook
MellyGray Sep 26, 2023
73d3944
fix: unit tests using dataset permissions
MellyGray Sep 26, 2023
d3e824b
fix(Loading): show when opening dataset page
MellyGray Sep 26, 2023
21bc630
fix: e2e tests
MellyGray Sep 26, 2023
c152531
Merge branch 'develop' of https://github.com/IQSS/dataverse-frontend …
MellyGray Sep 26, 2023
0541600
fix(actions): install playwright before running the tests
MellyGray Sep 26, 2023
d0cade8
fix(Stories): fix mocked data
MellyGray Oct 5, 2023
6685508
fix(DatasetActionButtons): fix top alignment with the CitationBlock
MellyGray Oct 5, 2023
3bad83e
fix(DatasetActionButton): single button fix border styles
MellyGray Oct 6, 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
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ jobs:
working-directory: packages/design-system
run: npm run build

- name: Install Playwright
run: npx playwright install

- name: Build Storybook Design System
working-directory: packages/design-system
run: npm run build-storybook --quiet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,18 @@ $navbar-brand-font-size: $dv-brand-font-size;
justify-content: end;
}

.dropdown-menu > .dropdown > .dropdown-toggle {
width: 100%;
text-align: start;
background-color: transparent;
border-color: transparent;
}

.dropdown-menu > .dropdown > .dropdown-toggle:hover {
background-color: $dropdown-link-hover-bg;
border-color: transparent;
}

th {
vertical-align: middle;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@import "src/lib/assets/styles/design-tokens/colors.module";

.border > button {
.border > button, .border > [role="group"] > button {
border: 1px solid $dv-button-border-color;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@ interface ButtonGroupProps extends React.HTMLAttributes<HTMLElement> {
vertical?: boolean
}

export function ButtonGroup({ vertical, children, ...props }: PropsWithChildren<ButtonGroupProps>) {
export function ButtonGroup({
vertical,
children,
className,
...props
}: PropsWithChildren<ButtonGroupProps>) {
return (
<ButtonGroupBS vertical={vertical} className={styles.border} {...props}>
<ButtonGroupBS
vertical={vertical}
className={`${styles.border} ${className ? className : ''}`}
{...props}>
{children}
</ButtonGroupBS>
)
Expand Down
39 changes: 37 additions & 2 deletions public/locales/en/dataset.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,43 @@
"learnAbout": "Learn About",
"standards": "Data Citation Standards"
},

"actions": {
"datasetActionButtons": {
"title": "Dataset Action Buttons",
"submitForReview": {
"enabled": "Submit for Review",
"disabled": "Submitted for Review"
},
"publish": {
"title": "Publish Dataset",
"publish": "Publish",
"returnToAuthor": "Return to Author",
"changeCurationStatus": "Change Curation Status",
"removeCurrentStatus": "Remove Current Status"
},
"linkDataset": {
"title": "Link Dataset"
},
"editDataset": {
"title": "Edit Dataset",
"filesUpload": "Files (Upload)",
"metadata": "Metadata",
"terms": "Terms",
"privateUrl": "Private URL",
"thumbnailsPlusWidgets": "Thumbnails + Widgets",
"delete": {
"draft": "Delete Draft Version",
"released": "Delete Dataset"
},
"deaccession": "Deaccession Dataset",
"permissions": {
"title": "Permissions",
"dataset": "Dataset",
"file": "File"
}
},
"accessDataset": {
"title": "Access Dataset"
},
"uploadFiles": "Upload Files"
}
}
79 changes: 72 additions & 7 deletions src/dataset/domain/models/Dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,7 @@ export enum DatasetPublishingStatus {
RELEASED = 'released',
DRAFT = 'draft',
DEACCESSIONED = 'deaccessioned',
EMBARGOED = 'embargoed',
IN_REVIEW = 'inReview'
EMBARGOED = 'embargoed'
}

export enum DatasetNonNumericVersion {
Expand All @@ -214,6 +213,9 @@ export class DatasetVersion {
constructor(
public readonly id: number,
public readonly publishingStatus: DatasetPublishingStatus,
public readonly isLatest: boolean,
public readonly isInReview: boolean,
public readonly latestVersionStatus: DatasetPublishingStatus,
public readonly majorNumber?: number,
public readonly minorNumber?: number
) {}
Expand All @@ -226,6 +228,32 @@ export class DatasetVersion {
}
}

export interface DatasetPermissions {
canDownloadFiles: boolean
canUpdateDataset: boolean
canPublishDataset: boolean
canManageDatasetPermissions: boolean
canManageFilesPermissions: boolean
canDeleteDataset: boolean
}

export interface DatasetLock {
id: number
reason: DatasetLockReason
}

export enum DatasetLockReason {
INGEST = 'ingest',
WORKFLOW = 'workflow',
IN_REVIEW = 'inReview',
DCM_UPLOAD = 'dcmUpload',
GLOBUS_UPLOAD = 'globusUpload',
FINALIZE_PUBLICATION = 'finalizePublication',

EDIT_IN_PROGRESS = 'editInProgress',
FILE_VALIDATION_FAILED = 'fileValidationFailed'
}

export class Dataset {
constructor(
public readonly persistentId: string,
Expand All @@ -234,13 +262,40 @@ export class Dataset {
public readonly labels: DatasetLabel[],
public readonly summaryFields: DatasetMetadataBlock[],
public readonly license: DatasetLicense,
public readonly metadataBlocks: DatasetMetadataBlocks
public readonly metadataBlocks: DatasetMetadataBlocks,
public readonly permissions: DatasetPermissions,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So to create an instance of a dataset model, you will also need to obtain the permissions that the current user has on the particular dataset. I'm not sure if coupling these permissions within the dataset model can be problematic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly like that, in order to create the Dataset you'll need the user permissions.

I understand your concern and these are the reasons to have them together:

  1. The dataset permissions are always used with the dataset model. So I think these permissions belong to the Dataset model, for keeping the cohesion
  2. If we have another page where we want to use the dataset permissions without the model. We can do it, getDatasetPermissions and you return the DatasetPermissions interface. But this is not going to happen in the Dataset Page, in the dataset page we always want the dataset and the dataset permissions, so it makes sense to have them together.
  3. I separated the File Permissions from the File model and it's being a nightmare, you need to remember to call these permissions when you get the files, initialise the React context, wait for them to be fetched, the tests are much more complex. Too much complexity for the aim of keeping the separation of concerns, when in reality I never use the file permissions without the files itself. I want to refactor that but I was also waiting for some feedback on this other approach.

I think the Dataset model is in reality the DatasetPage model, so its aim is to represent a DatasetPage, and the dataset page doesn't make sense without the permissions.

Very interesting conversation, please let me know if you can think of some problems of this approach

public readonly locks: DatasetLock[],
public readonly hasValidTermsOfAccess: boolean,
public readonly isValid: boolean,
public readonly isReleased: boolean
) {}

public getTitle(): string {
return this.metadataBlocks[0].fields.title
}

public get isLockedFromPublishing(): boolean {
return this.isLockedFromEdits
}

public get isLocked(): boolean {
return this.locks.length > 0
}

public get isLockedInWorkflow(): boolean {
return this.locks.some((lock) => lock.reason === DatasetLockReason.WORKFLOW)
}

public get isLockedFromEdits(): boolean {
const lockedReasonIsInReview = this.locks.some(
(lock) => lock.reason === DatasetLockReason.IN_REVIEW
)
// If the lock reason is workflow and the workflow userId is the same as the current user, then the user can edit
// TODO - Ask how we want to manage pending workflows

return this.isLocked && !(lockedReasonIsInReview && this.permissions.canPublishDataset)
}

static Builder = class {
public readonly labels: DatasetLabel[] = []

Expand All @@ -250,7 +305,12 @@ export class Dataset {
public readonly citation: string,
public readonly summaryFields: DatasetMetadataBlock[],
public readonly license: DatasetLicense = defaultLicense,
public readonly metadataBlocks: DatasetMetadataBlocks
public readonly metadataBlocks: DatasetMetadataBlocks,
public readonly permissions: DatasetPermissions,
public readonly locks: DatasetLock[],
public readonly hasValidTermsOfAccess: boolean,
public readonly isValid: boolean,
public readonly isReleased: boolean
) {
this.withLabels()
}
Expand All @@ -267,7 +327,7 @@ export class Dataset {
)
}

if (this.version.publishingStatus !== DatasetPublishingStatus.RELEASED) {
if (!this.isReleased) {
this.labels.push(
new DatasetLabel(DatasetLabelSemanticMeaning.WARNING, DatasetLabelValue.UNPUBLISHED)
)
Expand All @@ -285,7 +345,7 @@ export class Dataset {
)
}

if (this.version.publishingStatus === DatasetPublishingStatus.IN_REVIEW) {
if (this.version.isInReview) {
this.labels.push(
new DatasetLabel(DatasetLabelSemanticMeaning.SUCCESS, DatasetLabelValue.IN_REVIEW)
)
Expand All @@ -308,7 +368,12 @@ export class Dataset {
this.labels,
this.summaryFields,
this.license,
this.metadataBlocks
this.metadataBlocks,
this.permissions,
this.locks,
this.hasValidTermsOfAccess,
this.isValid,
this.isReleased
)
}
}
Expand Down
25 changes: 20 additions & 5 deletions src/dataset/infrastructure/mappers/JSDatasetMapper.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import {
Dataset as JSDataset,
DatasetMetadataBlocks as JSDatasetMetadataBlocks,
DatasetMetadataBlock as JSDatasetMetadataBlock,
DatasetMetadataBlocks as JSDatasetMetadataBlocks,
DatasetMetadataFields as JSDatasetMetadataFields,
DatasetVersionInfo as JSDatasetVersionInfo
} from '@iqss/dataverse-client-javascript'
import { DatasetVersionState as JSDatasetVersionState } from '@iqss/dataverse-client-javascript/dist/datasets/domain/models/Dataset'
import {
Dataset,
DatasetPublishingStatus,
MetadataBlockName,
DatasetMetadataBlock,
DatasetVersion,
DatasetMetadataBlocks,
DatasetMetadataFields,
DatasetMetadataBlocks
DatasetVersion,
MetadataBlockName
} from '../../domain/models/Dataset'

export class JSDatasetMapper {
Expand All @@ -29,7 +29,19 @@ export class JSDatasetMapper {
jsDataset.alternativePersistentId,
jsDataset.publicationDate,
jsDataset.citationDate
)
),
{
canDownloadFiles: true,
canUpdateDataset: true,
canPublishDataset: true,
canManageDatasetPermissions: true,
canManageFilesPermissions: true,
canDeleteDataset: true
}, // TODO Connect with dataset permissions
[], // TODO Connect with dataset locks
true, // TODO Connect with dataset hasValidTermsOfAccess
true, // TODO Connect with dataset isValid
!!jsDataset.versionInfo.releaseTime // TODO Connect with dataset isReleased
).build()
}

Expand All @@ -40,6 +52,9 @@ export class JSDatasetMapper {
return new DatasetVersion(
jDatasetVersionId,
JSDatasetMapper.toStatus(jsDatasetVersionInfo.state),
true, // TODO Connect with dataset version isLatest
false, // TODO Connect with dataset version isInReview
JSDatasetMapper.toStatus(jsDatasetVersionInfo.state), // TODO Connect with dataset version latestVersionState
jsDatasetVersionInfo.majorNumber,
jsDatasetVersionInfo.minorNumber
)
Expand Down
24 changes: 13 additions & 11 deletions src/sections/dataset/Dataset.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { DatasetRepository } from '../../dataset/domain/repositories/DatasetRepository'
import { useDataset } from './useDataset'
import { Tabs, Col, Row } from '@iqss/dataverse-design-system'
import styles from './Dataset.module.scss'
import { DatasetLabels } from './dataset-labels/DatasetLabels'
Expand All @@ -12,22 +10,23 @@ import { DatasetSummary } from './dataset-summary/DatasetSummary'
import { DatasetCitation } from './dataset-citation/DatasetCitation'
import { DatasetFiles } from './dataset-files/DatasetFiles'
import { FileRepository } from '../../files/domain/repositories/FileRepository'
import { DatasetActionButtons } from './dataset-action-buttons/DatasetActionButtons'
import { useDataset } from './DatasetContext'
import { useEffect } from 'react'

interface DatasetProps {
datasetRepository: DatasetRepository
fileRepository: FileRepository
searchParams: {
persistentId?: string
privateUrlToken?: string
version?: string
}
}

export function Dataset({ datasetRepository, fileRepository, searchParams }: DatasetProps) {
const { dataset } = useDataset(datasetRepository, searchParams)
const { isLoading } = useLoading()
export function Dataset({ fileRepository }: DatasetProps) {
const { setIsLoading } = useLoading()
const { dataset, isLoading } = useDataset()
const { t } = useTranslation('dataset')

useEffect(() => {
setIsLoading(isLoading)
}, [isLoading])

if (isLoading) {
return <DatasetSkeleton />
}
Expand All @@ -47,6 +46,9 @@ export function Dataset({ datasetRepository, fileRepository, searchParams }: Dat
<Col sm={9}>
<DatasetCitation citation={dataset.citation} version={dataset.version} />
</Col>
<Col sm={3}>
<DatasetActionButtons dataset={dataset} />
</Col>
</Row>
<Row>
<Col sm={9} className={styles['summary-container']}>
Expand Down
13 changes: 13 additions & 0 deletions src/sections/dataset/DatasetContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createContext, useContext } from 'react'
import { Dataset } from '../../dataset/domain/models/Dataset'

interface DatasetContextProps {
dataset: Dataset | undefined
isLoading: boolean
}
export const DatasetContext = createContext<DatasetContextProps>({
dataset: undefined,
isLoading: false
})

export const useDataset = () => useContext(DatasetContext)
21 changes: 11 additions & 10 deletions src/sections/dataset/DatasetFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { MetadataBlockInfoJSDataverseRepository } from '../../metadata-block-inf
import { SettingJSDataverseRepository } from '../../settings/infrastructure/SettingJSDataverseRepository'
import { FilePermissionsProvider } from '../file/file-permissions/FilePermissionsProvider'
import { SettingsProvider } from '../settings/SettingsProvider'
import { DatasetProvider } from './DatasetProvider'

const datasetRepository = new DatasetJSDataverseRepository()
const fileRepository = new FileJSDataverseRepository()
Expand Down Expand Up @@ -45,19 +46,19 @@ function DatasetWithSearchParams() {

if (privateUrlToken) {
return (
<Dataset
datasetRepository={datasetRepository}
fileRepository={fileRepository}
searchParams={{ privateUrlToken: privateUrlToken }}
/>
<DatasetProvider
repository={datasetRepository}
searchParams={{ privateUrlToken: privateUrlToken }}>
<Dataset fileRepository={fileRepository} />
</DatasetProvider>
)
}

return (
<Dataset
datasetRepository={datasetRepository}
fileRepository={fileRepository}
searchParams={{ persistentId: persistentId, version: version }}
/>
<DatasetProvider
repository={datasetRepository}
searchParams={{ persistentId: persistentId, version: version }}>
<Dataset fileRepository={fileRepository} />
</DatasetProvider>
)
}
Loading
Loading