Skip to content

Commit

Permalink
Merge branch 'develop' into feature/231-create-dataset-boilerplate
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Mangan committed Dec 14, 2023
2 parents 8d1049f + 14a74ae commit c003229
Show file tree
Hide file tree
Showing 37 changed files with 805 additions and 210 deletions.
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,45 @@

First install node >=16 and npm >=8. Recommended versions `node v19` and `npm v9`.

### Create a `.npmrc` file and add a token

To install the [@iqss/dataverse-client-javascript](https://github.com/IQSS/dataverse-client-javascript/pkgs/npm/dataverse-client-javascript)
from the GitHub registry, necessary for connecting with the Dataverse API, follow these steps to create an `.npmrc` file in
the root of your project using your GitHub token.

1. **Copy `.npmrc.example`**

Duplicate the `.npmrc.example` file in your project and save it as `.npmrc`.

2. **Replace the Token**

Open the newly created `.npmrc` file and replace `YOUR_GITHUB_TOKEN` with your actual GitHub token.

```plaintext
legacy-peer-deps=true
//npm.pkg.github.com/:_authToken=<YOUR_GITHUB_AUTH_TOKEN>
@iqss:registry=https://npm.pkg.github.com/
```

#### How to Get a GitHub Token

If you don't have a GitHub token yet, follow these steps:

1. Go to your GitHub account settings.

2. Navigate to "Developer settings" -> "Personal access tokens."

3. Click "Personal access tokens" -> "Tokens (classic)" -> "Generate new token (classic)".

4. Give the token a name and select the "read:packages" scope.

5. Copy the generated token.

6. Replace `YOUR_GITHUB_AUTH_TOKEN` in the `.npmrc` file with the copied token.

Now, you should be able to install the Dataverse JavaScript client using npm.

### `npm install`

Run this command to install the dependencies. You may see a message about vulnerabilities after running this command. \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,25 @@ interface DropdownItemProps extends React.HTMLAttributes<HTMLElement> {
href?: string
eventKey?: string
disabled?: boolean
download?: string
children: ReactNode
}

export function DropdownButtonItem({
href,
eventKey,
disabled,
download,
children,
...props
}: DropdownItemProps) {
return (
<DropdownBS.Item href={href} eventKey={eventKey} disabled={disabled} {...props}>
<DropdownBS.Item
href={href}
eventKey={eventKey}
disabled={disabled}
download={download}
{...props}>
{children}
</DropdownBS.Item>
)
Expand Down
5 changes: 4 additions & 1 deletion public/locales/en/dataset.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@
}
},
"accessDataset": {
"title": "Access Dataset"
"title": "Access Dataset",
"downloadZip": "Download ZIP",
"downloadOriginalZip": "Original Format ZIP",
"downloadArchiveZip": "Archive Format (.tab) ZIP"
},
"uploadFiles": "Upload Files"
},
Expand Down
13 changes: 10 additions & 3 deletions src/dataset/domain/models/Dataset.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Alert, AlertMessageKey } from '../../../alert/domain/models/Alert'
import { FileDownloadSize } from '../../../files/domain/models/File'

export enum DatasetLabelSemanticMeaning {
DATASET = 'dataset',
Expand Down Expand Up @@ -276,10 +277,12 @@ export class Dataset {
public readonly permissions: DatasetPermissions,
public readonly locks: DatasetLock[],
public readonly hasValidTermsOfAccess: boolean,
public readonly hasOneTabularFileAtLeast: boolean,
public readonly isValid: boolean,
public readonly isReleased: boolean,
public readonly thumbnail?: string,
public readonly privateUrl?: PrivateUrl
public readonly privateUrl?: PrivateUrl,
public readonly fileDownloadSizes?: FileDownloadSize[]
) {}

public getTitle(): string {
Expand Down Expand Up @@ -359,10 +362,12 @@ export class Dataset {
public readonly permissions: DatasetPermissions,
public readonly locks: DatasetLock[],
public readonly hasValidTermsOfAccess: boolean,
public readonly hasOneTabularFileAtLeast: boolean,
public readonly isValid: boolean,
public readonly isReleased: boolean,
public readonly thumbnail?: string,
public readonly privateUrl?: PrivateUrl
public readonly privateUrl?: PrivateUrl,
public readonly fileDownloadSizes?: FileDownloadSize[]
) {
this.withLabels()
this.withAlerts()
Expand Down Expand Up @@ -467,10 +472,12 @@ export class Dataset {
this.permissions,
this.locks,
this.hasValidTermsOfAccess,
this.hasOneTabularFileAtLeast,
this.isValid,
this.isReleased,
this.thumbnail,
this.privateUrl
this.privateUrl,
this.fileDownloadSizes
)
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/dataset/infrastructure/mappers/JSDatasetMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,12 @@ export class JSDatasetMapper {
JSDatasetMapper.toDatasetPermissions(jsDatasetPermissions),
JSDatasetMapper.toLocks(jsDatasetLocks),
true, // TODO Connect with dataset hasValidTermsOfAccess
true, // TODO Connect with dataset hasOneTabularFileAtLeast
true, // TODO Connect with dataset isValid
JSDatasetMapper.toIsReleased(jsDataset.versionInfo),
undefined, // TODO: get dataset thumbnail from Dataverse https://github.com/IQSS/dataverse-frontend/issues/203
privateUrl
privateUrl,
[] // TODO: Connect with file download use case
).build()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ import {
} from '@iqss/dataverse-client-javascript'
import { JSDatasetMapper } from '../mappers/JSDatasetMapper'

const includeDeaccessioned = true

export class DatasetJSDataverseRepository implements DatasetRepository {
getByPersistentId(
persistentId: string,
version?: string,
requestedVersion?: string
): Promise<Dataset | undefined> {
return getDataset
.execute(persistentId, this.versionToVersionId(version))
.execute(persistentId, this.versionToVersionId(version), includeDeaccessioned)
.then((jsDataset) =>
Promise.all([
jsDataset,
Expand Down
32 changes: 28 additions & 4 deletions src/files/domain/models/File.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,22 @@ export class FileSize {
}
}

export enum FileDownloadSizeMode {
ALL = 'All',
ORIGINAL = 'Original',
ARCHIVAL = 'Archival'
}

export class FileDownloadSize extends FileSize {
constructor(
readonly value: number,
readonly unit: FileSizeUnit,
readonly mode: FileDownloadSizeMode
) {
super(value, unit)
}
}

export interface FileAccess {
restricted: boolean
latestVersionRestricted: boolean
Expand Down Expand Up @@ -108,13 +124,14 @@ export class FileEmbargo {
export interface FileTabularData {
variablesCount: number
observationsCount: number
unf: string
unf?: string
}

export enum FileLabelType {
CATEGORY = 'category',
TAG = 'tag'
}

export interface FileLabel {
type: FileLabelType
value: string
Expand Down Expand Up @@ -145,6 +162,12 @@ export interface FileIngest {
reportMessage?: string
}

export interface FileDownloadUrls {
original: string
tabular?: string
rData?: string
}

export class File {
constructor(
readonly id: number,
Expand All @@ -154,16 +177,17 @@ export class File {
readonly type: FileType,
readonly size: FileSize,
readonly date: FileDate,
public downloadCount: number,
readonly downloadCount: number,
readonly labels: FileLabel[],
public readonly isDeleted: boolean,
public readonly ingest: FileIngest,
readonly checksum?: FileChecksum,
public readonly downloadUrls: FileDownloadUrls,
public thumbnail?: string,
readonly directory?: string,
readonly embargo?: FileEmbargo,
readonly tabularData?: FileTabularData,
readonly description?: string
readonly description?: string,
readonly checksum?: FileChecksum
) {}

getLink(): string {
Expand Down
9 changes: 9 additions & 0 deletions src/files/domain/models/FileCriteria.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ export class FileCriteria {
searchText
)
}

get someFilterApplied(): boolean {
return (
this.filterByType !== undefined ||
this.filterByAccess !== undefined ||
this.filterByTag !== undefined ||
this.searchText !== undefined
)
}
}

export enum FileSortByOption {
Expand Down
68 changes: 38 additions & 30 deletions src/files/infrastructure/FileJSDataverseRepository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FileRepository } from '../domain/repositories/FileRepository'
import { File, FilePublishingStatus } from '../domain/models/File'
import { File } from '../domain/models/File'
import { FilesCountInfo } from '../domain/models/FilesCountInfo'
import { FilePaginationInfo } from '../domain/models/FilePaginationInfo'
import { FileUserPermissions } from '../domain/models/FileUserPermissions'
Expand All @@ -10,7 +10,10 @@ import {
getDatasetFilesTotalDownloadSize,
getFileDownloadCount,
getFileUserPermissions,
ReadError
ReadError,
File as JSFile,
getFileDataTables,
FileDataTable as JSFileTabularData
} from '@iqss/dataverse-client-javascript'
import { FileCriteria } from '../domain/models/FileCriteria'
import { DomainFileMapper } from './mappers/DomainFileMapper'
Expand All @@ -30,7 +33,6 @@ export class FileJSDataverseRepository implements FileRepository {
criteria: FileCriteria = new FileCriteria()
): Promise<File[]> {
const jsPagination = DomainFileMapper.toJSPagination(paginationInfo)

return getDatasetFiles
.execute(
datasetPersistentId,
Expand All @@ -41,48 +43,54 @@ export class FileJSDataverseRepository implements FileRepository {
DomainFileMapper.toJSFileSearchCriteria(criteria),
DomainFileMapper.toJSFileOrderCriteria(criteria.sortBy)
)
.then((jsFiles) => jsFiles.map((jsFile) => JSFileMapper.toFile(jsFile, datasetVersion)))
.then((files) => FileJSDataverseRepository.getAllWithDownloadCount(files))
.then((files) => FileJSDataverseRepository.getAllWithThumbnail(files))
.then((jsFiles) =>
Promise.all([
jsFiles,
FileJSDataverseRepository.getAllDownloadCount(jsFiles),
FileJSDataverseRepository.getAllThumbnails(jsFiles),
FileJSDataverseRepository.getAllTabularData(jsFiles)
])
)
.then(([jsFiles, downloadCounts, thumbnails, jsTabularData]) =>
jsFiles.map((jsFile, index) =>
JSFileMapper.toFile(
jsFile,
datasetVersion,
downloadCounts[index],
thumbnails[index],
jsTabularData[index]
)
)
)
.catch((error: ReadError) => {
throw new Error(error.message)
})
}

private static getAllWithDownloadCount(files: File[]): Promise<File[]> {
private static getAllTabularData(
jsFiles: JSFile[]
): Promise<(JSFileTabularData[] | undefined)[]> {
return Promise.all(
files.map((file) =>
FileJSDataverseRepository.getDownloadCountById(file.id, file.version.publishingStatus).then(
(downloadCount) => {
file.downloadCount = downloadCount
return file
}
)
jsFiles.map((jsFile) =>
jsFile.tabularData ? getFileDataTables.execute(jsFile.id) : undefined
)
)
}

private static getDownloadCountById(
id: number,
publishingStatus: FilePublishingStatus
): Promise<number> {
if (publishingStatus === FilePublishingStatus.RELEASED) {
return getFileDownloadCount.execute(id).then((downloadCount) => Number(downloadCount))
}
return Promise.resolve(0)
}

private static getAllWithThumbnail(files: File[]): Promise<File[]> {
private static getAllDownloadCount(jsFiles: JSFile[]): Promise<number[]> {
return Promise.all(
files.map((file) =>
FileJSDataverseRepository.getThumbnailById(file.id).then((thumbnail) => {
file.thumbnail = thumbnail
return file
})
jsFiles.map((jsFile) =>
jsFile.publicationDate
? getFileDownloadCount.execute(jsFile.id).then((downloadCount) => Number(downloadCount))
: 0
)
)
}

private static getAllThumbnails(jsFiles: JSFile[]): Promise<(string | undefined)[]> {
return Promise.all(jsFiles.map((jsFile) => this.getThumbnailById(jsFile.id)))
}

private static getThumbnailById(id: number): Promise<string | undefined> {
return fetch(`${this.DATAVERSE_BACKEND_URL}/api/access/datafile/${id}?imageThumb=400`)
.then((response) => {
Expand Down
Loading

0 comments on commit c003229

Please sign in to comment.