Skip to content
Draft
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
5 changes: 4 additions & 1 deletion docs/useCases.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,9 +366,12 @@ This use case supports the following optional parameters depending on the search

- **limit**: (number) Limit for pagination.
- **offset**: (number) Offset for pagination.
- **collectionSearchCriteria**: ([CollectionSearchCriteria](../src/collections/domain/models/CollectionSearchCriteria.ts)) Supports filtering the collection items by different properties.
- **collectionSearchCriteria**: ([CollectionSearchCriteria](../src/collections/domain/models/CollectionSearchCriteria.ts)) Supports filtering the collection items by different properties. `filterQueries` may be a list of simple `key:value` filters or a single complete filter expression, which is sent unchanged and may contain parentheses, `AND`/`OR`, wildcards, ranges, and other search syntax.
- **searchServiceName**: The search service name on which to execute the search (Optional).
- **showTypeCounts**: If true, the response will include the count per object type (Optional).
- **showCollections**: If true, dataset results will include a `collections` array containing [CollectionSummary](../src/collections/domain/models/CollectionSummary.ts) objects for their collections (Optional).
- **metadataFields**: A list of metadata fields to include in dataset results. Each value must use the `metadata_block_name:field_name` format (Optional).
- **keepRawFields**: If true, metadata field values are returned without Markdown transformation (Optional).

#### List My Data Collection Items

Expand Down
6 changes: 2 additions & 4 deletions src/collections/domain/models/CollectionSearchCriteria.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,13 @@ export enum OrderType {
DESC = 'desc'
}

export type FilterQuery = `${string}:${string}`

export class CollectionSearchCriteria {
constructor(
public readonly searchText?: string,
public readonly itemTypes?: CollectionItemType[],
public readonly sort?: SortType,
public readonly order?: OrderType,
public readonly filterQueries?: FilterQuery[]
public readonly filterQueries?: string | string[]
) {}

withSearchText(searchText: string | undefined): CollectionSearchCriteria {
Expand Down Expand Up @@ -62,7 +60,7 @@ export class CollectionSearchCriteria {
)
}

withFilterQueries(filterQueries: FilterQuery[] | undefined): CollectionSearchCriteria {
withFilterQueries(filterQueries: string | string[] | undefined): CollectionSearchCriteria {
return new CollectionSearchCriteria(
this.searchText,
this.itemTypes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ export interface ICollectionsRepository {
offset?: number,
collectionSearchCriteria?: CollectionSearchCriteria,
searchServiceName?: string,
showTypeCounts?: boolean
showTypeCounts?: boolean,
showCollections?: boolean,
metadataFields?: `${string}:${string}`[],
keepRawFields?: boolean
): Promise<CollectionItemSubset>
getMyDataCollectionItems(
roleIds: number[],
Expand Down
13 changes: 11 additions & 2 deletions src/collections/domain/useCases/GetCollectionItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export class GetCollectionItems implements UseCase<CollectionItemSubset> {
* @param {CollectionSearchCriteria} [collectionSearchCriteria] - Supports filtering the collection items by different properties (optional).
* @param {string} [searchServiceName] - The search service name on which to execute the search (optional).
* @param {boolean} [showTypeCounts] - If true, the response will include the count per object type (optional).
* @param {boolean} [showCollections] - If true, dataset results will include the collections they belong to (optional).
* @param {`${string}:${string}`[]} [metadataFields] - Metadata fields to include in dataset results (optional).
* @param {boolean} [keepRawFields] - If true, metadata field values will not be transformed to Markdown (optional).
* @returns {Promise<CollectionItemSubset>}
*/
async execute(
Expand All @@ -28,15 +31,21 @@ export class GetCollectionItems implements UseCase<CollectionItemSubset> {
offset?: number,
collectionSearchCriteria?: CollectionSearchCriteria,
searchServiceName?: string,
showTypeCounts = false
showTypeCounts = false,
showCollections = false,
metadataFields?: `${string}:${string}`[],
keepRawFields = false
): Promise<CollectionItemSubset> {
return await this.collectionsRepository.getCollectionItems(
collectionId,
limit,
offset,
collectionSearchCriteria,
searchServiceName,
showTypeCounts
showTypeCounts,
showCollections,
metadataFields,
keepRawFields
)
}
}
44 changes: 33 additions & 11 deletions src/collections/infra/repositories/CollectionsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ export enum GetCollectionItemsQueryParams {
TYPE = 'type',
FILTERQUERY = 'fq',
SHOW_TYPE_COUNTS = 'show_type_counts',
SHOW_COLLECTIONS = 'show_collections',
METADATA_FIELDS = 'metadata_fields',
SEARCH_SERVICE_NAME = 'search_service'
}

Expand Down Expand Up @@ -246,7 +248,10 @@ export class CollectionsRepository extends ApiRepository implements ICollections
offset?: number,
collectionSearchCriteria?: CollectionSearchCriteria,
searchServiceName?: string,
showTypeCounts?: boolean
showTypeCounts?: boolean,
showCollections?: boolean,
metadataFields?: `${string}:${string}`[],
keepRawFields?: boolean
): Promise<CollectionItemSubset> {
const queryParams = new URLSearchParams({
[GetCollectionItemsQueryParams.QUERY]: '*',
Expand All @@ -271,6 +276,14 @@ export class CollectionsRepository extends ApiRepository implements ICollections
queryParams.set(GetCollectionItemsQueryParams.SHOW_TYPE_COUNTS, 'true')
}

if (showCollections) {
queryParams.set(GetCollectionItemsQueryParams.SHOW_COLLECTIONS, 'true')
}

metadataFields?.forEach((metadataField) => {
queryParams.append(GetCollectionItemsQueryParams.METADATA_FIELDS, metadataField)
})

if (searchServiceName) {
queryParams.set(GetCollectionItemsQueryParams.SEARCH_SERVICE_NAME, searchServiceName)
}
Expand All @@ -280,7 +293,9 @@ export class CollectionsRepository extends ApiRepository implements ICollections
}

return this.doGet('/search', true, queryParams)
.then((response) => transformCollectionItemsResponseToCollectionItemSubset(response))
.then((response) =>
transformCollectionItemsResponseToCollectionItemSubset(response, keepRawFields)
)
.catch((error) => {
throw error
})
Expand Down Expand Up @@ -510,18 +525,25 @@ export class CollectionsRepository extends ApiRepository implements ICollections
}

if (collectionSearchCriteria?.filterQueries) {
collectionSearchCriteria.filterQueries.forEach((filterQuery) => {
const idx = filterQuery.indexOf(':')
if (idx === -1) return // Invalid filter query, skip it
if (typeof collectionSearchCriteria.filterQueries === 'string') {
queryParams.append(
GetCollectionItemsQueryParams.FILTERQUERY,
collectionSearchCriteria.filterQueries
)
} else {
collectionSearchCriteria.filterQueries.forEach((filterQuery) => {
const idx = filterQuery.indexOf(':')
if (idx === -1) return // Invalid filter query, skip it

const filterQueryKey = filterQuery.substring(0, idx).trim()
const filterQueryValue = filterQuery.substring(idx + 1).trim()
const filterQueryKey = filterQuery.substring(0, idx).trim()
const filterQueryValue = filterQuery.substring(idx + 1).trim()

const filterQueryValueWithQuotes = `"${filterQueryValue}"`
const filterQueryToSet = `${filterQueryKey}:${filterQueryValueWithQuotes}`
const filterQueryValueWithQuotes = `"${filterQueryValue}"`
const filterQueryToSet = `${filterQueryKey}:${filterQueryValueWithQuotes}`

queryParams.append(GetCollectionItemsQueryParams.FILTERQUERY, filterQueryToSet)
})
queryParams.append(GetCollectionItemsQueryParams.FILTERQUERY, filterQueryToSet)
})
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ const transformInputLevelsPayloadToInputLevels = (
}

export const transformCollectionItemsResponseToCollectionItemSubset = (
response: AxiosResponse
response: AxiosResponse,
keepRawFields = false
): CollectionItemSubset => {
const responseDataPayload = response.data.data
const itemsPayload = responseDataPayload.items
Expand All @@ -125,7 +126,10 @@ export const transformCollectionItemsResponseToCollectionItemSubset = (
items.push(transformFilePreviewPayloadToFilePreview(itemPayload as FilePreviewPayload))
} else if (itemPayload.type === 'dataset') {
items.push(
transformDatasetPreviewPayloadToDatasetPreview(itemPayload as DatasetPreviewPayload)
transformDatasetPreviewPayloadToDatasetPreview(
itemPayload as DatasetPreviewPayload,
keepRawFields
)
)
} else if (itemPayload.type === 'dataverse') {
items.push(
Expand Down
4 changes: 4 additions & 0 deletions src/datasets/domain/models/DatasetPreview.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { CollectionItemType } from '../../../collections'
import { PublicationStatus } from '../../../core/domain/models/PublicationStatus'
import { DatasetVersionInfo } from './Dataset'
import { CollectionSummary } from '../../../collections/domain/models/CollectionSummary'
import { DatasetMetadataBlock } from './Dataset'

export interface DatasetPreview {
type: CollectionItemType.DATASET
Expand All @@ -15,4 +17,6 @@ export interface DatasetPreview {
parentCollectionAlias: string
imageUrl?: string
userRoles?: string[]
collections?: CollectionSummary[]
metadataBlocks?: DatasetMetadataBlock[]
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export interface MetadataBlocksPayload {
}

export interface MetadataBlockPayload {
name: string
name?: string
fields: MetadataFieldPayload[]
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { MetadataBlocksPayload } from './DatasetPayload'

export interface DatasetPreviewPayload {
global_id: string
name: string
Expand All @@ -16,4 +18,12 @@ export interface DatasetPreviewPayload {
identifier_of_dataverse: string
name_of_dataverse: string
image_url?: string
collections?: DatasetPreviewCollectionPayload[]
metadataBlocks?: MetadataBlocksPayload
}

export interface DatasetPreviewCollectionPayload {
id: number
name: string
alias: string
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import { AxiosResponse } from 'axios'
import { DatasetPreview } from '../../../domain/models/DatasetPreview'
import { DatasetVersionState } from '../../../domain/models/Dataset'
import { DatasetPreviewSubset } from '../../../domain/models/DatasetPreviewSubset'
import { DatasetPreviewPayload } from './DatasetPreviewPayload'
import { DatasetPreviewCollectionPayload, DatasetPreviewPayload } from './DatasetPreviewPayload'
import { CollectionSummary } from '../../../../collections/domain/models/CollectionSummary'
import { PublicationStatus } from '../../../../core/domain/models/PublicationStatus'
import { CollectionItemType } from '../../../../collections/domain/models/CollectionItemType'
import { MyDataDatasetPreviewPayload } from './MyDataDatasetPreviewPayload'
import { DatasetMetadataBlock } from '../../../domain/models/Dataset'
import { transformPayloadToDatasetMetadataBlocks } from './datasetTransformers'

export const transformDatasetPreviewsResponseToDatasetPreviewSubset = (
response: AxiosResponse
Expand All @@ -23,12 +26,24 @@ export const transformDatasetPreviewsResponseToDatasetPreviewSubset = (
}

export const transformDatasetPreviewPayloadToDatasetPreview = (
datasetPreviewPayload: DatasetPreviewPayload
datasetPreviewPayload: DatasetPreviewPayload,
keepRawFields = false
): DatasetPreview => {
const publicationStatuses: PublicationStatus[] = []
datasetPreviewPayload.publicationStatuses.forEach((element) => {
publicationStatuses.push(element as unknown as PublicationStatus)
})
const collections: CollectionSummary[] | undefined = datasetPreviewPayload.collections?.map(
(collection: DatasetPreviewCollectionPayload) => ({
id: collection.id,
alias: collection.alias,
displayName: collection.name
})
)
const metadataBlocks: DatasetMetadataBlock[] | undefined = datasetPreviewPayload.metadataBlocks
? transformPayloadToDatasetMetadataBlocks(datasetPreviewPayload.metadataBlocks, keepRawFields)
: undefined

return {
type: CollectionItemType.DATASET,
persistentId: datasetPreviewPayload.global_id,
Expand All @@ -51,7 +66,9 @@ export const transformDatasetPreviewPayloadToDatasetPreview = (
parentCollectionName: datasetPreviewPayload.name_of_dataverse,
...(datasetPreviewPayload.image_url && {
imageUrl: datasetPreviewPayload.image_url
})
}),
...(collections && { collections }),
...(metadataBlocks && { metadataBlocks })
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ export const transformPayloadToDatasetMetadataBlocks = (
return Object.keys(metadataBlocksPayload).map((metadataBlockKey) => {
const metadataBlock = metadataBlocksPayload[metadataBlockKey]
return {
name: metadataBlock.name,
name: metadataBlock.name ?? metadataBlockKey,
fields: transformPayloadToDatasetMetadataFields(metadataBlock.fields, keepRawFields)
}
}) as DatasetMetadataBlocks
Expand Down
41 changes: 40 additions & 1 deletion test/unit/collections/CollectionsRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ import {
} from '../../testHelpers/collections/collectionItemsFacetsHelper'
import {
OrderType,
SortType
SortType,
CollectionSearchCriteria
} from '../../../src/collections/domain/models/CollectionSearchCriteria'
import { RoleAlias } from '../../../src/roles/domain/models/RoleAlias'

Expand Down Expand Up @@ -654,6 +655,44 @@ describe('CollectionsRepository', () => {
expect(actual).toStrictEqual(testItemSubset)
})



test('should preserve structured filter query syntax', async () => {
const getSpy = jest.spyOn(axios, 'get').mockResolvedValue(testItemPreviewsResponse)

await sut.getCollectionItems(
testCollectionModel.alias,
undefined,
undefined,
new CollectionSearchCriteria().withFilterQueries([
'subject_ss:Medicine, Health and Life Sciences'
])
)

const arrayRequestConfig = getSpy.mock.calls[0]?.[1] as { params: URLSearchParams }
// fq filter value should be wrapped in quotes
expect(arrayRequestConfig.params.getAll(GetCollectionItemsQueryParams.FILTERQUERY)).toEqual([
'subject_ss:"Medicine, Health and Life Sciences"'
])

const rawFilterQuery =
'(subject_ss:Medicine OR subject_ss:Health) AND subtreePath:*/42 AND abc:[0 TO 100]'
await sut.getCollectionItems(
testCollectionModel.alias,
undefined,
undefined,
new CollectionSearchCriteria().withFilterQueries(rawFilterQuery)
)

const rawRequestConfig = getSpy.mock.calls[1]?.[1] as { params: URLSearchParams }
// fq filter should be unchanged
expect(rawRequestConfig.params.getAll(GetCollectionItemsQueryParams.FILTERQUERY)).toEqual([
rawFilterQuery
])

getSpy.mockRestore()
})

test('should return item previews when providing pagination params and response is successful', async () => {
jest.spyOn(axios, 'get').mockResolvedValue(testItemPreviewsResponse)

Expand Down
Loading