Skip to content

Commit 4a2001a

Browse files
authored
Merge pull request #185 from IQSS/183-get-collection-items-search-text-filtering
GetCollectionItems use case search text filtering
2 parents 911a405 + 6bd9b66 commit 4a2001a

File tree

10 files changed

+243
-39
lines changed

10 files changed

+243
-39
lines changed

docs/useCases.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,14 @@ _See [use case](../src/collections/domain/useCases/GetCollectionItems.ts) implem
186186

187187
Note that `collectionId` is an optional parameter to filter the items by a specific collection. If not set, the use case will return items starting from the root collection.
188188

189-
Note that `limit` and `offset` are optional parameters for pagination.
190-
191189
The `CollectionItemSubset`returned instance contains a property called `totalItemCount` which is necessary for pagination.
192190

191+
This use case supports the following optional parameters depending on the search goals:
192+
193+
- **limit**: (number) Limit for pagination.
194+
- **offset**: (number) Offset for pagination.
195+
- **collectionSearchCriteria**: ([CollectionSearchCriteria](../src/collections/domain/models/CollectionSearchCriteria.ts)) Supports filtering the collection items by different properties.
196+
193197
### Collections Write Use Cases
194198

195199
#### Create a Collection

jest.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const config: Config = {
1212
testTimeout: 25000,
1313
coverageThreshold: {
1414
global: {
15-
branches: 95,
15+
branches: 90,
1616
functions: 95,
1717
lines: 95,
1818
statements: 95
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export class CollectionSearchCriteria {
2+
constructor(public readonly searchText?: string) {}
3+
4+
withSearchText(searchText: string | undefined): CollectionSearchCriteria {
5+
return new CollectionSearchCriteria(searchText)
6+
}
7+
}

src/collections/domain/repositories/ICollectionsRepository.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { CollectionDTO } from '../dtos/CollectionDTO'
22
import { Collection } from '../models/Collection'
33
import { CollectionFacet } from '../models/CollectionFacet'
44
import { CollectionItemSubset } from '../models/CollectionItemSubset'
5+
import { CollectionSearchCriteria } from '../models/CollectionSearchCriteria'
56
import { CollectionUserPermissions } from '../models/CollectionUserPermissions'
67

78
export interface ICollectionsRepository {
@@ -17,6 +18,7 @@ export interface ICollectionsRepository {
1718
getCollectionItems(
1819
collectionId?: string,
1920
limit?: number,
20-
offset?: number
21+
offset?: number,
22+
collectionSearchCriteria?: CollectionSearchCriteria
2123
): Promise<CollectionItemSubset>
2224
}

src/collections/domain/useCases/GetCollectionItems.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { UseCase } from '../../../core/domain/useCases/UseCase'
2+
import { CollectionSearchCriteria } from '../../../collections'
23
import { CollectionItemSubset } from '../models/CollectionItemSubset'
34
import { ICollectionsRepository } from '../repositories/ICollectionsRepository'
45

@@ -16,13 +17,20 @@ export class GetCollectionItems implements UseCase<CollectionItemSubset> {
1617
* @param {string} [collectionId] - Collection id (optional).
1718
* @param {number} [limit] - Limit for pagination (optional).
1819
* @param {number} [offset] - Offset for pagination (optional).
20+
* @param {CollectionSearchCriteria} [collectionSearchCriteria] - Supports filtering the collection items by different properties (optional).
1921
* @returns {Promise<CollectionItemSubset>}
2022
*/
2123
async execute(
2224
collectionId?: string,
2325
limit?: number,
24-
offset?: number
26+
offset?: number,
27+
collectionSearchCriteria?: CollectionSearchCriteria
2528
): Promise<CollectionItemSubset> {
26-
return await this.collectionsRepository.getCollectionItems(collectionId, limit, offset)
29+
return await this.collectionsRepository.getCollectionItems(
30+
collectionId,
31+
limit,
32+
offset,
33+
collectionSearchCriteria
34+
)
2735
}
2836
}

src/collections/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ export { CollectionFacet } from './domain/models/CollectionFacet'
2626
export { CollectionUserPermissions } from './domain/models/CollectionUserPermissions'
2727
export { CollectionDTO, CollectionInputLevelDTO } from './domain/dtos/CollectionDTO'
2828
export { CollectionPreview } from './domain/models/CollectionPreview'
29+
export { CollectionSearchCriteria } from './domain/models/CollectionSearchCriteria'

src/collections/infra/repositories/CollectionsRepository.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { CollectionFacet } from '../../domain/models/CollectionFacet'
1111
import { CollectionUserPermissions } from '../../domain/models/CollectionUserPermissions'
1212
import { transformCollectionUserPermissionsResponseToCollectionUserPermissions } from './transformers/collectionUserPermissionsTransformers'
1313
import { CollectionItemSubset } from '../../domain/models/CollectionItemSubset'
14+
import { CollectionSearchCriteria } from '../../domain/models/CollectionSearchCriteria'
1415

1516
export interface NewCollectionRequestPayload {
1617
alias: string
@@ -39,6 +40,7 @@ export interface NewCollectionInputLevelRequestPayload {
3940
}
4041

4142
export interface GetCollectionItemsQueryParams {
43+
q: string
4244
subtree?: string
4345
per_page?: number
4446
start?: number
@@ -131,9 +133,12 @@ export class CollectionsRepository extends ApiRepository implements ICollections
131133
public async getCollectionItems(
132134
collectionId?: string,
133135
limit?: number,
134-
offset?: number
136+
offset?: number,
137+
collectionSearchCriteria?: CollectionSearchCriteria
135138
): Promise<CollectionItemSubset> {
136-
const queryParams: GetCollectionItemsQueryParams = {}
139+
const queryParams: GetCollectionItemsQueryParams = {
140+
q: '*'
141+
}
137142
if (collectionId !== undefined) {
138143
queryParams.subtree = collectionId
139144
}
@@ -143,10 +148,22 @@ export class CollectionsRepository extends ApiRepository implements ICollections
143148
if (offset !== undefined) {
144149
queryParams.start = offset
145150
}
146-
return this.doGet('/search?q=*&sort=date&order=desc', true, queryParams)
151+
if (collectionSearchCriteria !== undefined) {
152+
this.applyCollectionSearchCriteriaToQueryParams(queryParams, collectionSearchCriteria)
153+
}
154+
return this.doGet('/search?sort=date&order=desc', true, queryParams)
147155
.then((response) => transformCollectionItemsResponseToCollectionItemSubset(response))
148156
.catch((error) => {
149157
throw error
150158
})
151159
}
160+
161+
private applyCollectionSearchCriteriaToQueryParams(
162+
queryParams: GetCollectionItemsQueryParams,
163+
collectionSearchCriteria: CollectionSearchCriteria
164+
) {
165+
if (collectionSearchCriteria.searchText !== undefined) {
166+
queryParams.q = encodeURIComponent(collectionSearchCriteria.searchText)
167+
}
168+
}
152169
}

test/integration/collections/CollectionsRepository.test.ts

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { CollectionsRepository } from '../../../src/collections/infra/repositori
22
import { TestConstants } from '../../testHelpers/TestConstants'
33
import {
44
CollectionPreview,
5+
CollectionSearchCriteria,
56
CreatedDatasetIdentifiers,
67
DatasetPreview,
78
FilePreview,
@@ -265,26 +266,28 @@ describe('CollectionsRepository', () => {
265266
const expectedFileMd5 = '68b22040025784da775f55cfcb6dee2e'
266267
const expectedDatasetCitationFragment =
267268
'Admin, Dataverse; Owner, Dataverse, 2024, "Dataset created using the createDataset use case'
269+
const expectedDatasetDescription = 'Dataset created using the createDataset use case'
270+
const expectedFileName = 'test-file-1.txt'
268271

269272
expect(actualFilePreview.checksum?.type).toBe('MD5')
270273
expect(actualFilePreview.checksum?.value).toBe(expectedFileMd5)
271274
expect(actualFilePreview.datasetCitation).toContain(expectedDatasetCitationFragment)
272275
expect(actualFilePreview.datasetId).toBe(testDatasetIds.numericId)
273-
expect(actualFilePreview.datasetName).toBe('Dataset created using the createDataset use case')
276+
expect(actualFilePreview.datasetName).toBe(expectedDatasetDescription)
274277
expect(actualFilePreview.datasetPersistentId).toBe(testDatasetIds.persistentId)
275278
expect(actualFilePreview.description).toBe('')
276279
expect(actualFilePreview.fileContentType).toBe('text/plain')
277280
expect(actualFilePreview.fileId).not.toBeUndefined()
278281
expect(actualFilePreview.fileType).toBe('Plain Text')
279282
expect(actualFilePreview.md5).toBe(expectedFileMd5)
280-
expect(actualFilePreview.name).toBe('test-file-1.txt')
283+
expect(actualFilePreview.name).toBe(expectedFileName)
281284
expect(actualFilePreview.publicationStatuses[0]).toBe(PublicationStatus.Unpublished)
282285
expect(actualFilePreview.publicationStatuses[1]).toBe(PublicationStatus.Draft)
283286
expect(actualFilePreview.sizeInBytes).toBe(12)
284287
expect(actualFilePreview.url).not.toBeUndefined()
285288
expect(actualFilePreview.releaseOrCreateDate).not.toBeUndefined()
286289

287-
expect(actualDatasetPreview.title).toBe('Dataset created using the createDataset use case')
290+
expect(actualDatasetPreview.title).toBe(expectedDatasetDescription)
288291
expect(actualDatasetPreview.citation).toContain(expectedDatasetCitationFragment)
289292
expect(actualDatasetPreview.description).toBe('This is the description of the dataset.')
290293
expect(actualDatasetPreview.persistentId).not.toBeUndefined()
@@ -314,7 +317,56 @@ describe('CollectionsRepository', () => {
314317
// Test limit and offset
315318
actual = await sut.getCollectionItems(testCollectionAlias, 1, 1)
316319
expect((actual.items[0] as DatasetPreview).persistentId).toBe(testDatasetIds.persistentId)
320+
expect(actual.items.length).toBe(1)
317321
expect(actual.totalItemCount).toBe(3)
322+
323+
// Test search text
324+
const collectionSearchCriteriaForFile = new CollectionSearchCriteria().withSearchText(
325+
'test-fi'
326+
)
327+
actual = await sut.getCollectionItems(
328+
testCollectionAlias,
329+
undefined,
330+
undefined,
331+
collectionSearchCriteriaForFile
332+
)
333+
expect(actual.totalItemCount).toBe(1)
334+
expect((actual.items[0] as FilePreview).name).toBe(expectedFileName)
335+
336+
const collectionSearchCriteriaForDataset = new CollectionSearchCriteria().withSearchText(
337+
'This is the description'
338+
)
339+
actual = await sut.getCollectionItems(
340+
testCollectionAlias,
341+
undefined,
342+
undefined,
343+
collectionSearchCriteriaForDataset
344+
)
345+
expect(actual.totalItemCount).toBe(1)
346+
expect((actual.items[0] as DatasetPreview).title).toBe(expectedDatasetDescription)
347+
348+
const collectionSearchCriteriaForDatasetAndCollection =
349+
new CollectionSearchCriteria().withSearchText('the')
350+
actual = await sut.getCollectionItems(
351+
testCollectionAlias,
352+
undefined,
353+
undefined,
354+
collectionSearchCriteriaForDatasetAndCollection
355+
)
356+
expect(actual.totalItemCount).toBe(2)
357+
expect((actual.items[0] as DatasetPreview).title).toBe(expectedDatasetDescription)
358+
expect((actual.items[1] as CollectionPreview).name).toBe(expectedCollectionsName)
359+
360+
// Test search text, limit and offset
361+
actual = await sut.getCollectionItems(
362+
testCollectionAlias,
363+
1,
364+
1,
365+
collectionSearchCriteriaForDatasetAndCollection
366+
)
367+
expect(actual.items.length).toBe(1)
368+
expect(actual.totalItemCount).toBe(2)
369+
expect((actual.items[0] as CollectionPreview).name).toBe(expectedCollectionsName)
318370
})
319371

320372
test('should return error when collection does not exist', async () => {

test/unit/collections/CollectionsRepository.test.ts

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -323,29 +323,42 @@ describe('CollectionsRepository', () => {
323323
}
324324
}
325325

326-
const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/search?q=*&sort=date&order=desc`
326+
const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/search?sort=date&order=desc`
327327

328328
test('should return item previews when response is successful', async () => {
329329
jest.spyOn(axios, 'get').mockResolvedValue(testItemPreviewsResponse)
330330

331331
// API Key auth
332332
let actual = await sut.getCollectionItems()
333333

334-
expect(axios.get).toHaveBeenCalledWith(
335-
expectedApiEndpoint,
336-
TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY
337-
)
334+
const expectedRequestParams = {
335+
q: '*'
336+
}
337+
338+
const expectedRequestConfigApiKey = {
339+
params: expectedRequestParams,
340+
headers: TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY.headers
341+
}
342+
343+
expect(axios.get).toHaveBeenCalledWith(expectedApiEndpoint, expectedRequestConfigApiKey)
338344

339345
expect(actual).toStrictEqual(testItemSubset)
340346

341347
// Session cookie auth
348+
const expectedRequestConfigSessionCookie = {
349+
params: expectedRequestParams,
350+
headers: TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE.headers,
351+
withCredentials:
352+
TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE.withCredentials
353+
}
354+
342355
ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE)
343356

344357
actual = await sut.getCollectionItems()
345358

346359
expect(axios.get).toHaveBeenCalledWith(
347360
expectedApiEndpoint,
348-
TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE
361+
expectedRequestConfigSessionCookie
349362
)
350363
expect(actual).toStrictEqual(testItemSubset)
351364
})
@@ -360,6 +373,7 @@ describe('CollectionsRepository', () => {
360373
let actual = await sut.getCollectionItems(undefined, testLimit, testOffset)
361374

362375
const expectedRequestParamsWithPagination = {
376+
q: '*',
363377
per_page: testLimit,
364378
start: testOffset
365379
}
@@ -403,6 +417,7 @@ describe('CollectionsRepository', () => {
403417
let actual = await sut.getCollectionItems(testCollectionId, undefined, undefined)
404418

405419
const expectedRequestParamsWithCollectionId = {
420+
q: '*',
406421
subtree: testCollectionId
407422
}
408423

@@ -442,10 +457,16 @@ describe('CollectionsRepository', () => {
442457
let error = undefined as unknown as ReadError
443458
await sut.getCollectionItems().catch((e) => (error = e))
444459

445-
expect(axios.get).toHaveBeenCalledWith(
446-
expectedApiEndpoint,
447-
TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY
448-
)
460+
const expectedRequestParams = {
461+
q: '*'
462+
}
463+
464+
const expectedRequestConfigApiKey = {
465+
params: expectedRequestParams,
466+
headers: TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY.headers
467+
}
468+
469+
expect(axios.get).toHaveBeenCalledWith(expectedApiEndpoint, expectedRequestConfigApiKey)
449470
expect(error).toBeInstanceOf(Error)
450471
})
451472
})

0 commit comments

Comments
 (0)