Skip to content

Commit

Permalink
Merge branch 'develop' into feature/188-dataset-download-options
Browse files Browse the repository at this point in the history
  • Loading branch information
ekraffmiller committed Dec 12, 2023
2 parents 6a44d6f + 9f73f62 commit 24c1657
Show file tree
Hide file tree
Showing 30 changed files with 452 additions and 101 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 @@ -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
6 changes: 3 additions & 3 deletions src/files/domain/models/File.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export class FileEmbargo {
export interface FileTabularData {
variablesCount: number
observationsCount: number
unf: string
unf?: string
}

export enum FileLabelType {
Expand Down Expand Up @@ -171,12 +171,12 @@ 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 thumbnail?: string,
readonly thumbnail?: string,
readonly directory?: string,
readonly embargo?: FileEmbargo,
readonly tabularData?: FileTabularData,
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
43 changes: 31 additions & 12 deletions src/files/infrastructure/mappers/JSFileMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
FilePublishingStatus,
FileSize,
FileSizeUnit,
FileTabularData,
FileType,
FileVersion
} from '../../domain/models/File'
Expand All @@ -22,7 +23,8 @@ import {
FileContentTypeCount as JSFileContentTypeCount,
FileCategoryNameCount as JSFileCategoryNameCount,
FileAccessStatusCount as JSFileAccessStatusCount,
FileAccessStatus as JSFileAccessStatus
FileAccessStatus as JSFileAccessStatus,
FileDataTable as JSFileTabularData
} from '@iqss/dataverse-client-javascript'
import { DatasetPublishingStatus, DatasetVersion } from '../../../dataset/domain/models/Dataset'
import { FileUserPermissions } from '../../domain/models/FileUserPermissions'
Expand All @@ -35,7 +37,13 @@ import {
import { FileAccessOption, FileTag } from '../../domain/models/FileCriteria'

export class JSFileMapper {
static toFile(jsFile: JSFile, datasetVersion: DatasetVersion): File {
static toFile(
jsFile: JSFile,
datasetVersion: DatasetVersion,
downloadsCount: number,
thumbnail?: string,
jsTabularData?: JSFileTabularData[]
): File {
return new File(
this.toFileId(jsFile.id),
this.toFileVersion(jsFile.version, datasetVersion, jsFile.publicationDate),
Expand All @@ -44,15 +52,15 @@ export class JSFileMapper {
this.toFileType(jsFile.contentType, jsFile.originalFormatLabel),
this.toFileSize(jsFile.sizeBytes),
this.toFileDate(jsFile.creationDate, jsFile.publicationDate, jsFile.embargo),
this.toFileDownloads(),
this.toFileDownloads(downloadsCount),
this.toFileLabels(jsFile.categories, jsFile.tabularTags),
false, // TODO - Implement this when it is added to js-dataverse
this.toFileIsDeleted(jsFile.deleted),
{ status: FileIngestStatus.NONE }, // TODO - Implement this when it is added to js-dataverse
this.toFileChecksum(jsFile.checksum),
this.toFileThumbnail(),
this.toFileThumbnail(thumbnail),
this.toFileDirectory(jsFile.directoryLabel),
this.toFileEmbargo(jsFile.embargo),
this.toFileTabularData(),
this.toFileTabularData(jsTabularData),
this.toFileDescription(jsFile.description)
)
}
Expand Down Expand Up @@ -132,8 +140,8 @@ export class JSFileMapper {
throw new Error('File date not found')
}

static toFileDownloads(): number {
return 0 // This is always 0 because the downloads come from a different endpoint
static toFileDownloads(downloadsCount: number): number {
return downloadsCount
}

static toFileLabels(jsFileCategories?: string[], jsFileTabularTags?: string[]): FileLabel[] {
Expand All @@ -159,8 +167,8 @@ export class JSFileMapper {
return undefined
}

static toFileThumbnail(): undefined {
return undefined // This is always undefined because the thumbnails come from a different endpoint
static toFileThumbnail(thumbnail?: string): string | undefined {
return thumbnail
}

static toFileDirectory(jsFileDirectory: string | undefined): string | undefined {
Expand All @@ -174,8 +182,15 @@ export class JSFileMapper {
return undefined
}

static toFileTabularData(): undefined {
return undefined // This is always undefined because the tabular data comes from a different endpoint
static toFileTabularData(jsTabularData?: JSFileTabularData[]): FileTabularData | undefined {
if (jsTabularData === undefined) {
return undefined
}
return {
variablesCount: jsTabularData[0].varQuantity ?? 0,
observationsCount: jsTabularData[0].caseQuantity ?? 0,
unf: jsTabularData[0].UNF
}
}

static toFileDescription(jsFileDescription?: string): string | undefined {
Expand Down Expand Up @@ -230,4 +245,8 @@ export class JSFileMapper {
return FileAccessOption.EMBARGOED_RESTRICTED
}
}

static toFileIsDeleted(jsFileIsDeleted: boolean | undefined): boolean {
return jsFileIsDeleted ?? false
}
}
4 changes: 4 additions & 0 deletions src/sections/dataset/Dataset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { DatasetActionButtons } from './dataset-action-buttons/DatasetActionButt
import { useDataset } from './DatasetContext'
import { useEffect } from 'react'
import { DatasetAlerts } from './dataset-alerts/DatasetAlerts'
import { useNotImplementedModal } from '../not-implemented/NotImplementedModalContext'
import { NotImplementedModal } from '../not-implemented/NotImplementedModal'

interface DatasetProps {
fileRepository: FileRepository
Expand All @@ -23,6 +25,7 @@ export function Dataset({ fileRepository }: DatasetProps) {
const { setIsLoading } = useLoading()
const { dataset, isLoading } = useDataset()
const { t } = useTranslation('dataset')
const { hideModal, isModalOpen } = useNotImplementedModal()

useEffect(() => {
setIsLoading(isLoading)
Expand All @@ -34,6 +37,7 @@ export function Dataset({ fileRepository }: DatasetProps) {

return (
<>
<NotImplementedModal show={isModalOpen} handleClose={hideModal} />
{!dataset ? (
<PageNotFound />
) : (
Expand Down
17 changes: 10 additions & 7 deletions src/sections/dataset/DatasetFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { SettingJSDataverseRepository } from '../../settings/infrastructure/Sett
import { FilePermissionsProvider } from '../file/file-permissions/FilePermissionsProvider'
import { SettingsProvider } from '../settings/SettingsProvider'
import { DatasetProvider } from './DatasetProvider'
import { NotImplementedModalProvider } from '../not-implemented/NotImplementedModalProvider'
import { AlertProvider } from '../alerts/AlertProvider'

const datasetRepository = new DatasetJSDataverseRepository()
Expand All @@ -23,13 +24,15 @@ export class DatasetFactory {
return (
<FilePermissionsProvider repository={fileRepository}>
<SettingsProvider repository={settingRepository}>
<MetadataBlockInfoProvider repository={metadataBlockInfoRepository}>
<AnonymizedProvider>
<AlertProvider>
<DatasetWithSearchParams />
</AlertProvider>
</AnonymizedProvider>
</MetadataBlockInfoProvider>
<NotImplementedModalProvider>
<MetadataBlockInfoProvider repository={metadataBlockInfoRepository}>
<AnonymizedProvider>
<AlertProvider>
<DatasetWithSearchParams />
</AlertProvider>
</AnonymizedProvider>
</MetadataBlockInfoProvider>
</NotImplementedModalProvider>
</SettingsProvider>
</FilePermissionsProvider>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { EditDatasetPermissionsMenu } from './EditDatasetPermissionsMenu'
import { DeleteDatasetButton } from './DeleteDatasetButton'
import { DeaccessionDatasetButton } from './DeaccessionDatasetButton'
import { useTranslation } from 'react-i18next'
import { useNotImplementedModal } from '../../../not-implemented/NotImplementedModalContext'
import { useSession } from '../../../session/SessionContext'

interface EditDatasetMenuProps {
Expand All @@ -16,10 +17,11 @@ export function EditDatasetMenu({ dataset }: EditDatasetMenuProps) {
if (!user || !dataset.permissions.canUpdateDataset) {
return <></>
}

const { showModal } = useNotImplementedModal()
const { t } = useTranslation('dataset')
return (
<DropdownButton
onSelect={showModal}
id={`edit-dataset-menu`}
title={t('datasetActionButtons.editDataset.title')}
asButtonGroup
Expand Down
Loading

0 comments on commit 24c1657

Please sign in to comment.