Skip to content

Commit efba631

Browse files
[DSC-1875] Port of [CST-16286] Handle results with 1 item & 0 bitstreams in lucky-search (fix)
1 parent 1697d0f commit efba631

4 files changed

Lines changed: 89 additions & 82 deletions

File tree

src/app/lucky-search/search/lucky-search.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ <h5 class="text-center">{{'lucky.search.results.notfound' | translate}} {{curren
2323
<div class="col-md-12 d-flex justify-content-center">
2424
<div class="col-md-8">
2525
<ds-search-form [inPlaceSearch]="false"
26-
[query]="currentFilter.value"
26+
[query]="currentFilter.bitstreamValue ? currentFilter.value + ' OR ' + currentFilter.bitstreamValue : currentFilter.value"
2727
[searchPlaceholder]="'search.search-form.placeholder' | translate">
2828
</ds-search-form>
2929
</div>

src/app/lucky-search/search/lucky-search.component.spec.ts

Lines changed: 64 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import {FileSizePipe} from '../../shared/utils/file-size-pipe';
2323
import {HardRedirectService} from '../../core/services/hard-redirect.service';
2424
import {getBitstreamDownloadRoute} from '../../app-routing-paths';
2525
import {PLATFORM_ID} from '@angular/core';
26+
import {NotificationsService} from '../../shared/notifications/notifications.service';
27+
import {NotificationsServiceStub} from '../../shared/testing/notifications-service.stub';
2628

2729
describe('LuckySearchComponent', () => {
2830
let fixture: ComponentFixture<LuckySearchComponent>;
@@ -98,6 +100,7 @@ describe('LuckySearchComponent', () => {
98100
{provide: BitstreamDataService, useValue: bitstreamDataService},
99101
{provide: HardRedirectService, useValue: hardRedirectService},
100102
{provide: PLATFORM_ID, useValue: 'browser'},
103+
{provide: NotificationsService, useValue: new NotificationsServiceStub()},
101104
],
102105
})
103106
.compileComponents();
@@ -195,7 +198,7 @@ describe('LuckySearchComponent', () => {
195198
bitstreamDataService.findByItem.withArgs(itemUUID, 'ORIGINAL', metadataFilters, {})
196199
.and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList([bitstream])));
197200

198-
component.currentFilter = {identifier: 'test', value: 'test'};
201+
component.currentFilter = {identifier: 'test', value: 'test', bitstreamValue: 'test'};
199202
component.searchOptions$ = observableOf(defaultPagination);
200203

201204
spyOn((component as any), 'getLuckySearchResults').and.returnValue(observableOf(data));
@@ -227,94 +230,81 @@ describe('LuckySearchComponent', () => {
227230
});
228231

229232
describe('one item is available', () => {
230-
beforeEach(() => {
231-
fixture = TestBed.createComponent(LuckySearchComponent);
232-
component = fixture.componentInstance;
233-
234-
const bitstreamSearchTree = new UrlTree();
235-
bitstreamSearchTree.queryParams = {
236-
index: 'testIndex',
237-
value: 'testValue',
238-
bitstreamMetadata: 'testMetadata',
239-
bitstreamValue: 'testMetadataValue'
240-
};
241-
242-
const itemUUID = 'd317835d-7b06-4219-91e2-1191900cb897';
243-
const firstSearchResult = Object.assign(new SearchResult(), {
244-
indexableObject: Object.assign(new DSpaceObject(), {
245-
id: 'd317835d-7b06-4219-91e2-1191900cb897',
246-
uuid: itemUUID,
247-
name: 'My first publication',
248-
metadata: {
249-
'dspace.entity.type': [
250-
{value: 'Publication'}
251-
]
252-
}
253-
})
254-
});
255-
const data = createSuccessfulRemoteDataObject(createPaginatedList([firstSearchResult]));
256-
const metadataFilters = [{metadataName: 'dc.title', metadataValue: 'test.pdf'}] as MetadataFilter[];
257-
component.bitstreamFilters$.next(metadataFilters);
258-
bitstreamDataService.findByItem.withArgs(itemUUID, 'ORIGINAL', metadataFilters, {})
259-
.and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList([bitstream])));
260-
261-
component.currentFilter = {identifier: 'test', value: 'test'};
262-
component.searchOptions$ = observableOf(defaultPagination);
263-
264-
spyOn((component as any), 'getLuckySearchResults').and.returnValue(observableOf(data));
265-
spyOn((component as any), 'loadBitstreamsAndRedirectIfNeeded').and.returnValue(observableOf([bitstream]));
266-
spyOn((component as any), 'hasBitstreamFilters').and.returnValue(true);
267-
spyOn(component, 'redirect');
268-
spyOn(routerStub, 'parseUrl').and.returnValue(bitstreamSearchTree);
269-
270-
component.resultsRD$.next(data);
271-
272-
fixture.detectChanges();
273-
});
233+
beforeEach(() => {
234+
fixture = TestBed.createComponent(LuckySearchComponent);
235+
component = fixture.componentInstance;
274236

275-
it('should create', () => {
276-
expect(component).toBeTruthy();
277-
});
237+
const bitstreamSearchTree = new UrlTree();
238+
bitstreamSearchTree.queryParams = {
239+
index: 'testIndex',
240+
value: 'testValue',
241+
bitstreamMetadata: 'testMetadata',
242+
bitstreamValue: 'testMetadataValue'
243+
};
278244

279-
it('should redirect to item page when only one result is found', () => {
280-
expect(component.redirect).toHaveBeenCalled();
245+
const itemUUID = 'd317835d-7b06-4219-91e2-1191900cb897';
246+
const firstSearchResult = Object.assign(new SearchResult(), {
247+
indexableObject: Object.assign(new DSpaceObject(), {
248+
id: 'd317835d-7b06-4219-91e2-1191900cb897',
249+
uuid: itemUUID,
250+
name: 'My first publication',
251+
metadata: {
252+
'dspace.entity.type': [
253+
{value: 'Publication'}
254+
]
255+
}
256+
})
281257
});
282-
});
258+
const data = createSuccessfulRemoteDataObject(createPaginatedList([firstSearchResult]));
259+
const metadataFilters = [{metadataName: 'dc.title', metadataValue: 'test.pdf'}] as MetadataFilter[];
260+
component.bitstreamFilters$.next(metadataFilters);
261+
bitstreamDataService.findByItem.withArgs(itemUUID, 'ORIGINAL', metadataFilters, {})
262+
.and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList([bitstream])));
283263

284-
it('should not redirect when no bitstreams are found', () => {
285-
const item = Object.assign(new Item(), {uuid: 'item-uuid-1', name: 'Test item 1'});
286-
const data = createSuccessfulRemoteDataObject(createPaginatedList([
287-
{indexableObject: item, hitHighlights: {}}
288-
])) as any;
289-
component.resultsRD$.next(data);
290-
component.bitstreamFilters$.next([{metadataName: 'dc.title', metadataValue: 'Non-existent bitstream'}]);
291-
bitstreamDataService.findByItem.and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList([])));
264+
component.currentFilter = {identifier: 'test', value: 'test', bitstreamValue: 'test'};
265+
component.searchOptions$ = observableOf(defaultPagination);
266+
267+
spyOn((component as any), 'getLuckySearchResults').and.returnValue(observableOf(data));
268+
spyOn((component as any), 'loadBitstreamsAndRedirectIfNeeded').and.returnValue(observableOf([bitstream]));
269+
spyOn((component as any), 'hasBitstreamFilters').and.returnValue(true);
292270
spyOn(component, 'redirect');
271+
spyOn(routerStub, 'parseUrl').and.returnValue(bitstreamSearchTree);
272+
273+
component.resultsRD$.next(data);
274+
293275
fixture.detectChanges();
294-
expect(component.redirect).not.toHaveBeenCalled();
295276
});
296277

297-
it('should update showEmptySearchSection$ and showMultipleSearchSection$ based on search results', () => {
278+
it('should create', () => {
279+
expect(component).toBeTruthy();
280+
});
281+
282+
it('should redirect to item page when only one result is found', () => {
283+
expect(component.redirect).toHaveBeenCalled();
284+
});
285+
});
286+
287+
it('should not redirect when no bitstreams are found', () => {
288+
const item = Object.assign(new Item(), {uuid: 'item-uuid-1', name: 'Test item 1'});
289+
const data = createSuccessfulRemoteDataObject(createPaginatedList([
290+
{indexableObject: item, hitHighlights: {}}
291+
])) as any;
292+
component.resultsRD$.next(data);
293+
component.bitstreamFilters$.next([{metadataName: 'dc.title', metadataValue: 'Non-existent bitstream'}]);
294+
bitstreamDataService.findByItem.and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList([])));
295+
spyOn(component, 'redirect');
296+
fixture.detectChanges();
297+
expect(component.redirect).not.toHaveBeenCalled();
298+
});
299+
300+
it('should update showEmptySearchSection$ when no results are found', () => {
298301
const emptyResults = createSuccessfulRemoteDataObject(createPaginatedList([]));
299-
const multipleResults = createSuccessfulRemoteDataObject(createPaginatedList([
300-
new SearchResult<DSpaceObject>(),
301-
new SearchResult<DSpaceObject>()
302-
]));
303302

304303
spyOn(component as any, 'getLuckySearchResults').and.returnValue(observableOf(emptyResults));
305304
spyOn(component as any, 'processSearchResults').and.returnValue(observableOf(emptyResults));
306305

307306
component.getSearchResults();
308307

309308
expect(component.showEmptySearchSection$.getValue()).toBe(true);
310-
expect(component.showMultipleSearchSection$.getValue()).toBe(false);
311-
312-
(component as any).getLuckySearchResults.and.returnValue(observableOf(multipleResults));
313-
(component as any).processSearchResults.and.returnValue(observableOf(multipleResults));
314-
315-
component.getSearchResults();
316-
317-
expect(component.showEmptySearchSection$.getValue()).toBe(false);
318-
expect(component.showMultipleSearchSection$.getValue()).toBe(true);
319309
});
320310
});

src/app/lucky-search/search/lucky-search.component.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ import { SearchConfigurationService } from '../../core/shared/search/search-conf
1818
import { Item } from '../../core/shared/item.model';
1919
import { BitstreamDataService, MetadataFilter } from '../../core/data/bitstream-data.service';
2020
import { Bitstream } from '../../core/shared/bitstream.model';
21-
import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
21+
import { hasValue, isNotEmpty } from '../../shared/empty.util';
2222
import { getItemPageRoute } from '../../item-page/item-page-routing-paths';
2323
import { getBitstreamDownloadRoute } from '../../app-routing-paths';
2424
import {HardRedirectService} from '../../core/services/hard-redirect.service';
2525
import {isPlatformServer} from '@angular/common';
26+
import {NotificationsService} from '../../shared/notifications/notifications.service';
27+
import {TranslateService} from '@ngx-translate/core';
2628

2729
@Component({
2830
selector: 'ds-lucky-search',
@@ -51,7 +53,8 @@ export class LuckySearchComponent implements OnInit {
5153
*/
5254
currentFilter = {
5355
identifier: '',
54-
value: ''
56+
value: '',
57+
bitstreamValue: '',
5558
};
5659
context: Context = Context.ItemPage;
5760

@@ -71,6 +74,8 @@ export class LuckySearchComponent implements OnInit {
7174
public searchConfigService: SearchConfigurationService,
7275
@Inject(PLATFORM_ID) private platformId: Object,
7376
private hardRedirectService: HardRedirectService,
77+
private notificationService: NotificationsService,
78+
private translateService: TranslateService,
7479
) {}
7580

7681
ngOnInit(): void {
@@ -105,8 +110,6 @@ export class LuckySearchComponent implements OnInit {
105110
switchMap(results => this.processSearchResults(results))
106111
).subscribe(results => {
107112
this.resultsRD$.next(results);
108-
this.showEmptySearchSection$.next(results?.payload?.page?.length === 0);
109-
this.showMultipleSearchSection$.next(results?.payload?.page?.length > 1);
110113
});
111114
}
112115

@@ -117,12 +120,16 @@ export class LuckySearchComponent implements OnInit {
117120
return this.bitstreamFilters$.pipe(
118121
withLatestFrom(of(item)),
119122
mergeMap(([bitstreamFilters, itemOb]) => this.loadBitstreamsAndRedirectIfNeeded(itemOb, bitstreamFilters)),
120-
tap(bitstreams => {
123+
tap((bitstreams: Bitstream[]) => {
121124
this.bitstreams$.next(bitstreams);
122-
this.showEmptySearchSection$.next(isEmpty(bitstreams));
123-
if (isNotEmpty(bitstreams) && bitstreams.length === 1) {
125+
this.showEmptySearchSection$.next(bitstreams.length === 0);
126+
if (bitstreams?.length === 1) {
124127
const bitstreamRoute = getBitstreamDownloadRoute(bitstreams[0]);
125128
this.redirect(bitstreamRoute);
129+
} else if (bitstreams?.length === 0) {
130+
const route = getItemPageRoute(item);
131+
this.redirect(route);
132+
this.notificationService.info(null, this.translateService.get('lucky.search.bitstream.notfound'));
126133
}
127134
}),
128135
map(() => results)
@@ -133,6 +140,13 @@ export class LuckySearchComponent implements OnInit {
133140
const route = getItemPageRoute(item);
134141
this.redirect(route);
135142
}
143+
144+
if (results?.payload?.totalElements === 0) {
145+
this.showEmptySearchSection$.next(true);
146+
} else if (results?.payload?.totalElements > 1) {
147+
this.showMultipleSearchSection$.next(true);
148+
}
149+
136150
return of(results);
137151
}
138152

@@ -179,6 +193,7 @@ export class LuckySearchComponent implements OnInit {
179193
private parseBitstreamFilters(queryParams: Params): MetadataFilter[] {
180194
const metadataName = queryParams?.bitstreamMetadata;
181195
const metadataValue = queryParams?.bitstreamValue;
196+
this.currentFilter.bitstreamValue = metadataValue ?? null;
182197
if (!!metadataName && !!metadataValue) {
183198

184199
const metadataNames = Array.isArray(metadataName) ? metadataName : [metadataName];

src/assets/i18n/en.json5

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7238,6 +7238,8 @@
72387238

72397239
"lucky.search.multiple.records": "may refer to multiple records",
72407240

7241+
"lucky.search.bitstream.notfound": "We are unable to find the file that you have mentioned but it seems to be related to this item.",
7242+
72417243
"invitation.group.msg": "You have been invited to join the following groups",
72427244

72437245
"invitation.accept-btn": "Accept",

0 commit comments

Comments
 (0)