Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel

### Added

- New Use Case: [Get Collections For Linking Use Case](./docs/useCases.md#get-collections-for-linking).

### Changed

### Fixed
Expand Down
64 changes: 64 additions & 0 deletions docs/useCases.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The different use cases currently available in the package are classified below,
- [List All Collection Items](#list-all-collection-items)
- [List My Data Collection Items](#list-my-data-collection-items)
- [Get Collection Featured Items](#get-collection-featured-items)
- [Get Collections for Linking](#get-collections-for-linking)
- [Collections write use cases](#collections-write-use-cases)
- [Create a Collection](#create-a-collection)
- [Update a Collection](#update-a-collection)
Expand Down Expand Up @@ -336,6 +337,69 @@ The `collectionIdOrAlias` is a generic collection identifier, which can be eithe

If no collection identifier is specified, the default collection identifier; `:root` will be used. If you want to search for a different collection, you must add the collection identifier as a parameter in the use case call.

#### Get Collections for Linking

Returns an array of [CollectionSummary](../src/collections/domain/models/CollectionSummary.ts) (id, alias, displayName) representing the Dataverse collections to which a given Dataverse collection or Dataset may be linked.

This use case supports an optional `searchTerm` to filter by collection name.

##### Example calls:

```typescript
import { getCollectionsForLinking } from '@iqss/dataverse-client-javascript'

/* ... */

// Case 1: For a given Dataverse collection (by numeric id or alias)
const collectionIdOrAlias: number | string = 'collectionAlias' // or 123
const searchTerm = 'searchOn'

getCollectionsForLinking
.execute('collection', collectionIdOrAlias, searchTerm)
.then((collections) => {
// collections: CollectionSummary[]
/* ... */
})
.catch((error: Error) => {
/* ... */
})

/* ... */

// Case 2: For a given Dataset (by persistent identifier)
const persistentId = 'doi:10.5072/FK2/J8SJZB'

getCollectionsForLinking
.execute('dataset', persistentId, searchTerm)
.then((collections) => {
// collections: CollectionSummary[]
/* ... */
})
.catch((error: Error) => {
/* ... */
})

// Case 3: [alreadyLinked] Optional flag. When true, returns collections currently linked (candidates to unlink). Defaults to false.
const alreadyLinked = true

getCollectionsForLinking
.execute('dataset', persistentId, searchTerm, alreadyLinked)
.then((collections) => {
// collections: CollectionSummary[]
/* ... */
})
.catch((error: Error) => {
/* ... */
})
```

_See [use case](../src/collections/domain/useCases/GetCollectionsForLinking.ts) implementation_.

Notes:

- When the first argument is `'collection'`, the second argument can be a numeric collection id or a collection alias.
- When the first argument is `'dataset'`, the second argument must be the dataset persistent identifier string (e.g., `doi:...`).

### Collections Write Use Cases

#### Create a Collection
Expand Down
8 changes: 8 additions & 0 deletions src/collections/domain/repositories/ICollectionsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { CollectionUserPermissions } from '../models/CollectionUserPermissions'
import { PublicationStatus } from '../../../core/domain/models/PublicationStatus'
import { CollectionItemType } from '../../../collections/domain/models/CollectionItemType'
import { CollectionLinks } from '../models/CollectionLinks'
import { CollectionSummary } from '../models/CollectionSummary'
import { LinkingObjectType } from '../useCases/GetCollectionsForLinking'

export interface ICollectionsRepository {
getCollection(collectionIdOrAlias: number | string): Promise<Collection>
Expand Down Expand Up @@ -60,4 +62,10 @@ export interface ICollectionsRepository {
linkingCollectionIdOrAlias: number | string
): Promise<void>
getCollectionLinks(collectionIdOrAlias: number | string): Promise<CollectionLinks>
getCollectionsForLinking(
objectType: LinkingObjectType,
id: number | string,
searchTerm: string,
alreadyLinked: boolean
): Promise<CollectionSummary[]>
}
34 changes: 34 additions & 0 deletions src/collections/domain/useCases/GetCollectionsForLinking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { ICollectionsRepository } from '../repositories/ICollectionsRepository'
import { CollectionSummary } from '../models/CollectionSummary'

export type LinkingObjectType = 'collection' | 'dataset'

export class GetCollectionsForLinking implements UseCase<CollectionSummary[]> {
private collectionsRepository: ICollectionsRepository

constructor(collectionsRepository: ICollectionsRepository) {
this.collectionsRepository = collectionsRepository
}

/**
* Returns an array of CollectionSummary (id, alias, displayName) to which the given Dataverse collection or Dataset may be linked.
* @param objectType - 'collection' when providing a collection identifier/alias; 'dataset' when providing a dataset persistentId.
* @param id - For objectType 'collection', a numeric id or alias string. For 'dataset', the persistentId string (e.g., doi:...)
* @param searchTerm - Optional search term to filter by collection name. Defaults to empty string (no filtering).
* @param alreadyLinked - Optional flag. When true, returns collections currently linked (candidates to unlink). Defaults to false.
*/
async execute(
objectType: LinkingObjectType,
id: number | string,
searchTerm = '',
alreadyLinked = false
): Promise<CollectionSummary[]> {
return await this.collectionsRepository.getCollectionsForLinking(
objectType,
id,
searchTerm,
alreadyLinked
)
}
}
6 changes: 5 additions & 1 deletion src/collections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { DeleteCollectionFeaturedItem } from './domain/useCases/DeleteCollection
import { LinkCollection } from './domain/useCases/LinkCollection'
import { UnlinkCollection } from './domain/useCases/UnlinkCollection'
import { GetCollectionLinks } from './domain/useCases/GetCollectionLinks'
import { GetCollectionsForLinking } from './domain/useCases/GetCollectionsForLinking'

const collectionsRepository = new CollectionsRepository()

Expand All @@ -34,6 +35,7 @@ const deleteCollectionFeaturedItem = new DeleteCollectionFeaturedItem(collection
const linkCollection = new LinkCollection(collectionsRepository)
const unlinkCollection = new UnlinkCollection(collectionsRepository)
const getCollectionLinks = new GetCollectionLinks(collectionsRepository)
const getCollectionsForLinking = new GetCollectionsForLinking(collectionsRepository)

export {
getCollection,
Expand All @@ -51,7 +53,8 @@ export {
deleteCollectionFeaturedItem,
linkCollection,
unlinkCollection,
getCollectionLinks
getCollectionLinks,
getCollectionsForLinking
}
export { Collection, CollectionInputLevel } from './domain/models/Collection'
export { CollectionFacet } from './domain/models/CollectionFacet'
Expand All @@ -62,3 +65,4 @@ export { CollectionItemType } from './domain/models/CollectionItemType'
export { CollectionSearchCriteria } from './domain/models/CollectionSearchCriteria'
export { FeaturedItem } from './domain/models/FeaturedItem'
export { FeaturedItemsDTO } from './domain/dtos/FeaturedItemsDTO'
export { CollectionSummary } from './domain/models/CollectionSummary'
45 changes: 44 additions & 1 deletion src/collections/infra/repositories/CollectionsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import { ApiConstants } from '../../../core/infra/repositories/ApiConstants'
import { PublicationStatus } from '../../../core/domain/models/PublicationStatus'
import { ReadError } from '../../../core/domain/repositories/ReadError'
import { CollectionLinks } from '../../domain/models/CollectionLinks'
import { CollectionSummary } from '../../domain/models/CollectionSummary'
import { LinkingObjectType } from '../../domain/useCases/GetCollectionsForLinking'

export interface NewCollectionRequestPayload {
alias: string
Expand Down Expand Up @@ -93,7 +95,6 @@ export enum GetMyDataCollectionItemsQueryParams {

export class CollectionsRepository extends ApiRepository implements ICollectionsRepository {
private readonly collectionsResourceName: string = 'dataverses'

public async getCollection(
collectionIdOrAlias: number | string = ROOT_COLLECTION_ID
): Promise<Collection> {
Expand Down Expand Up @@ -485,4 +486,46 @@ export class CollectionsRepository extends ApiRepository implements ICollections
throw error
})
}

public async getCollectionsForLinking(
objectType: LinkingObjectType,
id: number | string,
searchTerm: string,
alreadyLinked: boolean
): Promise<CollectionSummary[]> {
let path: string
const queryParams = new URLSearchParams()
if (objectType === 'collection') {
path = `/${this.collectionsResourceName}/${id}/dataverse/linkingDataverses`
} else {
path = `/${this.collectionsResourceName}/:persistentId/dataset/linkingDataverses`
queryParams.set('persistentId', String(id))
}

if (searchTerm) {
queryParams.set('searchTerm', searchTerm)
}

if (alreadyLinked) {
queryParams.set('alreadyLinking', 'true')
}

return this.doGet(path, true, queryParams)
.then((response) => {
const payload = response.data.data as {
id: number
alias: string
name: string
}[]

return payload.map((item) => ({
id: item.id,
alias: item.alias,
displayName: item.name
}))
})
.catch((error) => {
throw error
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,13 @@ export const transformCollectionLinksResponseToCollectionLinks = (
const responseDataPayload = response.data.data
const linkedCollections = responseDataPayload.linkedDataverses
const collectionsLinkingToThis = responseDataPayload.dataversesLinkingToThis
const linkedDatasets = responseDataPayload.linkedDatasets
const linkedDatasets = responseDataPayload.linkedDatasets.map(
(ld: { identifier: string; title: string }) => ({
persistentId: ld.identifier,
title: ld.title
})
)

return {
linkedCollections,
collectionsLinkingToThis,
Expand Down
4 changes: 2 additions & 2 deletions src/datasets/domain/repositories/IDatasetsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ export interface IDatasetsRepository {
): Promise<DatasetDownloadCount>
getDatasetVersionsSummaries(datasetId: number | string): Promise<DatasetVersionSummaryInfo[]>
deleteDatasetDraft(datasetId: number | string): Promise<void>
linkDataset(datasetId: number, collectionAlias: string): Promise<void>
unlinkDataset(datasetId: number, collectionAlias: string): Promise<void>
linkDataset(datasetId: number | string, collectionIdOrAlias: number | string): Promise<void>
unlinkDataset(datasetId: number | string, collectionIdOrAlias: number | string): Promise<void>
getDatasetLinkedCollections(datasetId: number | string): Promise<DatasetLinkedCollection[]>
getDatasetAvailableCategories(datasetId: number | string): Promise<string[]>
getDatasetCitationInOtherFormats(
Expand Down
8 changes: 4 additions & 4 deletions src/datasets/domain/useCases/LinkDataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ export class LinkDataset implements UseCase<void> {
/**
* Creates a link between a Dataset and a Collection.
*
* @param {number} [datasetId] - The dataset id.
* @param {string} [collectionAlias] - The collection alias.
* @param {number | string} [datasetId] - The dataset id (numeric) or persistent identifier string.
* @param {number | string} [collectionIdOrAlias] - The collection identifier (numeric id) or alias.
* @returns {Promise<void>} - This method does not return anything upon successful completion.
*/
async execute(datasetId: number, collectionAlias: string): Promise<void> {
return await this.datasetsRepository.linkDataset(datasetId, collectionAlias)
async execute(datasetId: number | string, collectionIdOrAlias: number | string): Promise<void> {
return await this.datasetsRepository.linkDataset(datasetId, collectionIdOrAlias)
}
}
8 changes: 4 additions & 4 deletions src/datasets/domain/useCases/UnlinkDataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ export class UnlinkDataset implements UseCase<void> {
/**
* Removes a link between a Dataset and a Collection.
*
* @param {number} [datasetId] - The dataset id.
* @param {string} [collectionAlias] - The collection alias.
* @param {number | string} [datasetId] - The dataset id (numeric) or persistent identifier string.
* @param {number | string} [collectionIdOrAlias] - The collection identifier (numeric id) or alias.
* @returns {Promise<void>} - This method does not return anything upon successful completion.
*/
async execute(datasetId: number, collectionAlias: string): Promise<void> {
return await this.datasetsRepository.unlinkDataset(datasetId, collectionAlias)
async execute(datasetId: number | string, collectionIdOrAlias: number | string): Promise<void> {
return await this.datasetsRepository.unlinkDataset(datasetId, collectionIdOrAlias)
}
}
24 changes: 20 additions & 4 deletions src/datasets/infra/repositories/DatasetsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,16 +327,32 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi
})
}

public async linkDataset(datasetId: number, collectionAlias: string): Promise<void> {
return this.doPut(`/${this.datasetsResourceName}/${datasetId}/link/${collectionAlias}`, {})
public async linkDataset(
datasetId: number | string,
collectionIdOrAlias: number | string
): Promise<void> {
const endpoint = this.buildApiEndpoint(
this.datasetsResourceName,
`link/${collectionIdOrAlias}`,
datasetId
)
return this.doPut(endpoint, {})
.then(() => undefined)
.catch((error) => {
throw error
})
}

public async unlinkDataset(datasetId: number, collectionAlias: string): Promise<void> {
return this.doDelete(`/${this.datasetsResourceName}/${datasetId}/deleteLink/${collectionAlias}`)
public async unlinkDataset(
datasetId: number | string,
collectionIdOrAlias: number | string
): Promise<void> {
const endpoint = this.buildApiEndpoint(
this.datasetsResourceName,
`deleteLink/${collectionIdOrAlias}`,
datasetId
)
return this.doDelete(endpoint)
.then(() => undefined)
.catch((error) => {
throw error
Expand Down
4 changes: 2 additions & 2 deletions test/environment/.env
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
POSTGRES_VERSION=17
DATAVERSE_DB_USER=dataverse
SOLR_VERSION=9.8.0
DATAVERSE_IMAGE_REGISTRY=docker.io
DATAVERSE_IMAGE_TAG=unstable
DATAVERSE_IMAGE_REGISTRY=ghcr.io
DATAVERSE_IMAGE_TAG=11710-find-dataverses-for-linking
DATAVERSE_BOOTSTRAP_TIMEOUT=5m
Loading