Skip to content

Commit 48550ca

Browse files
kshepherdstrawburster
authored andcommitted
[TLC-790] Solr-based suggestion in onebox
1 parent 346bdb8 commit 48550ca

17 files changed

Lines changed: 333 additions & 194 deletions

File tree

src/app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export class BrowseByTaxonomyComponent implements OnInit, OnChanges, OnDestroy {
128128
this.selectedItems = [];
129129
this.facetType = browseDefinition.facetType;
130130
this.vocabularyName = browseDefinition.vocabulary;
131-
this.vocabularyOptions = { name: this.vocabularyName, closed: true };
131+
this.vocabularyOptions = { name: this.vocabularyName, closed: true, type: 'xml' };
132132
this.description = this.translate.instant(`browse.metadata.${this.vocabularyName}.tree.description`);
133133
}));
134134
this.subs.push(this.scope$.subscribe(() => {

src/app/core/shared/form/models/form-field.model.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ export interface SelectableMetadata {
2828
* A boolean representing if value is closely related to the controlled vocabulary entry or not
2929
*/
3030
closed: boolean;
31+
32+
/**
33+
* The type (source) of the vocabulary: xml, authority, suggest
34+
*/
35+
vocabularyType: string;
3136
}
3237

3338
/**

src/app/core/submission/vocabularies/models/vocabulary-options.model.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,15 @@ export class VocabularyOptions {
1313
*/
1414
closed: boolean;
1515

16+
/**
17+
* The type of the vocabulary (source): xml, authority, suggest
18+
*/
19+
type?: string;
20+
1621
constructor(name: string,
17-
closed: boolean = false) {
22+
closed: boolean = false, type: string = 'xml') {
1823
this.name = name;
1924
this.closed = closed;
25+
this.type = type;
2026
}
2127
}

src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-authority-field/dso-edit-metadata-authority-field.component.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ import { Item } from '@dspace/core/shared/item.model';
1515
import { MetadataValue } from '@dspace/core/shared/metadata.models';
1616
import { Vocabulary } from '@dspace/core/submission/vocabularies/models/vocabulary.model';
1717
import { VocabularyService } from '@dspace/core/submission/vocabularies/vocabulary.service';
18+
import { SearchServiceStub } from '@dspace/core/testing/search-service.stub';
1819
import { createPaginatedList } from '@dspace/core/testing/utils.test';
1920
import { VocabularyServiceStub } from '@dspace/core/testing/vocabulary-service.stub';
2021
import { createSuccessfulRemoteDataObject$ } from '@dspace/core/utilities/remote-data.utils';
2122
import { TranslateModule } from '@ngx-translate/core';
23+
import { SearchService } from 'src/app/shared/search/search.service';
2224

2325
import { RegistryService } from '../../../../admin/admin-registries/registry/registry.service';
2426
import { DynamicOneboxModel } from '../../../../shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.model';
@@ -32,6 +34,7 @@ describe('DsoEditMetadataAuthorityFieldComponent', () => {
3234
let fixture: ComponentFixture<DsoEditMetadataAuthorityFieldComponent>;
3335

3436
let vocabularyService: any;
37+
let searchService: any;
3538
let itemService: ItemDataService;
3639
let registryService: RegistryService;
3740
let notificationsService: NotificationsService;
@@ -112,6 +115,7 @@ describe('DsoEditMetadataAuthorityFieldComponent', () => {
112115
findByHref: createSuccessfulRemoteDataObject$(item),
113116
});
114117
vocabularyService = new VocabularyServiceStub();
118+
searchService = new SearchServiceStub();
115119
registryService = jasmine.createSpyObj('registryService', {
116120
queryMetadataFields: createSuccessfulRemoteDataObject$(createPaginatedList(metadataFields)),
117121
});
@@ -150,6 +154,7 @@ describe('DsoEditMetadataAuthorityFieldComponent', () => {
150154
],
151155
providers: [
152156
{ provide: VocabularyService, useValue: vocabularyService },
157+
{ provide: SearchService, useValue: searchService },
153158
{ provide: ItemDataService, useValue: itemService },
154159
{ provide: RegistryService, useValue: registryService },
155160
{ provide: NotificationsService, useValue: notificationsService },
Lines changed: 87 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,94 @@
1-
<ng-template #rt let-listEntry="result" let-t="term">
2-
<ng-container
3-
[ngTemplateOutlet]="(listEntry.hasOtherInformation()) ? hasInfo : noInfo"
4-
[ngTemplateOutletContext]="{entry: listEntry}">
5-
</ng-container>
6-
</ng-template>
7-
8-
<ng-template #hasInfo let-entry="entry">
1+
<ng-template #rt let-listEntry="result">
92
<ul class="list-unstyled mb-0">
10-
<li class="list-item text-truncate text-primary fw-bold">{{entry.value}}</li>
11-
@for (item of entry.otherInformation | dsObjNgFor; track item) {
12-
<li class="list-item text-truncate text-secondary" >
13-
{{ 'form.other-information.' + item.key | translate }} : {{item.value}}
14-
</li>
3+
@if (listEntry.display) {
4+
<li
5+
class="list-item text-truncate text-primary"
6+
[innerHtml]="listEntry.display">
7+
</li>
8+
} @else {
9+
<li class="list-item text-truncate text-primary fw-bold">
10+
{{listEntry.value}}
11+
</li>
1512
}
16-
</ul>
17-
</ng-template>
1813

19-
<ng-template #noInfo let-entry="entry">
20-
<ul class="list-unstyled mb-0">
21-
<li class="list-item text-truncate text-primary fw-bold">{{entry.value}}</li>
14+
@if (listEntry.hasOtherInformation()) {
15+
@for (item of listEntry.otherInformation | dsObjNgFor; track item.key) {
16+
17+
<li class="list-item text-truncate text-secondary" >
18+
{{ 'form.other-information.' + item.key | translate }} : {{item.value}}
19+
</li>
20+
21+
}}
2222
</ul>
2323
</ng-template>
2424

25-
@if ((isHierarchicalVocabulary() | async) !== true) {
26-
<div class="position-relative right-addon">
27-
<div class="authority-icons position-absolute d-flex align-items-center">
28-
@if (searching || loadingInitialValue) {
29-
<i class="fas fa-circle-notch fa-spin fa-2x fa-fw text-primary my-auto p-0" aria-hidden="true"></i>
30-
}
31-
@if (!searching && !loadingInitialValue) {
32-
<i
33-
dsAuthorityConfidenceState
34-
class="far fa-circle fa-2x fa-fw my-auto p-0"
35-
aria-hidden="true"
36-
[authorityValue]="currentValue"
37-
(whenClickOnConfidenceNotAccepted)="whenClickOnConfidenceNotAccepted($event)"></i>
38-
}
39-
</div>
40-
<input #instance="ngbTypeahead"
41-
class="form-control"
42-
[attr.aria-labelledby]="'label_' + model.id"
43-
[attr.autoComplete]="model.autoComplete"
44-
[attr.aria-label]="model.label | translate"
45-
[class.is-invalid]="showErrorMessages"
46-
[id]="model.id"
47-
[inputFormatter]="formatter"
48-
[name]="model.name"
49-
[ngbTypeahead]="search"
50-
[placeholder]="model.placeholder"
51-
[readonly]="model.readOnly"
52-
[disabled]="model.readOnly"
53-
[resultTemplate]="rt"
54-
[type]="model.inputType"
55-
[(ngModel)]="currentValue"
56-
(blur)="onBlur($event)"
57-
(focus)="onFocus($event)"
58-
(change)="onChange($event)"
59-
(input)="onInput($event)"
60-
(selectItem)="onSelectItem($event)">
61-
@if (searchFailed) {
62-
<div class="invalid-feedback">Sorry, suggestions could not be loaded.</div>
63-
}
64-
</div>
65-
}
25+
<div class="position-relative right-addon">
26+
27+
@if ((vocabulary$ | async)?.hierarchical) {
28+
29+
<i class="dropdown-toggle position-absolute tree-toggle" (click)="openTree($event)"
30+
aria-hidden="true"></i>
31+
<input class="form-control"
32+
[attr.aria-labelledby]="'label_' + model.id"
33+
[attr.autoComplete]="model.autoComplete"
34+
[attr.aria-label]="model.label | translate"
35+
[class.is-invalid]="showErrorMessages"
36+
[class.tree-input]="!model.readOnly"
37+
[id]="id"
38+
[name]="model.name"
39+
[placeholder]="model.placeholder"
40+
[readonly]="true"
41+
[disabled]="model.readOnly"
42+
[type]="model.inputType"
43+
[value]="currentValue?.display"
44+
(focus)="onFocus($event)"
45+
(change)="onChange($event)"
46+
(click)="openTree($event)"
47+
(keydown)="$event.preventDefault()"
48+
(keypress)="$event.preventDefault()"
49+
(keyup)="$event.preventDefault()">
50+
51+
} @else {
52+
53+
@if (loading) {
54+
<i
55+
class="fas fa-circle-notch fa-spin fa-2x fa-fw text-primary position-absolute mt-1 p-0"
56+
aria-hidden="true">
57+
</i>
58+
} @else {
59+
<i
60+
dsAuthorityConfidenceState
61+
class="far fa-circle fa-2x fa-fw position-absolute mt-1 p-0"
62+
aria-hidden="true"
63+
[authorityValue]="currentValue"
64+
(whenClickOnConfidenceNotAccepted)="whenClickOnConfidenceNotAccepted($event)">
65+
</i>
66+
}
67+
68+
<input #typeahead="ngbTypeahead"
69+
class="form-control"
70+
[attr.aria-labelledby]="'label_' + model.id"
71+
[attr.autoComplete]="model.autoComplete"
72+
[attr.aria-label]="model.label | translate"
73+
[class.is-invalid]="showErrorMessages"
74+
[id]="model.id"
75+
[name]="model.name"
76+
[ngbTypeahead]="search"
77+
[placeholder]="model.placeholder"
78+
[readonly]="model.readOnly"
79+
[disabled]="model.readOnly"
80+
[resultTemplate]="rt"
81+
[type]="model.inputType"
82+
[(ngModel)]="currentValue"
83+
(blur)="onBlur($event)"
84+
(focus)="onFocus($event)"
85+
(change)="onChange($event)"
86+
(input)="onInput($event)"
87+
(selectItem)="onSelectItem($event)">
88+
89+
@if (searchFailed) {
90+
<div class="invalid-feedback">Sorry, suggestions could not be loaded.</div>
91+
}
6692

67-
@if ((isHierarchicalVocabulary() | async)) {
68-
<div class="position-relative right-addon">
69-
<i class="dropdown-toggle position-absolute tree-toggle" (click)="openTree($event)"
70-
aria-hidden="true"></i>
71-
<input class="form-control"
72-
[attr.aria-labelledby]="'label_' + model.id"
73-
[attr.autoComplete]="model.autoComplete"
74-
[attr.aria-label]="model.label | translate"
75-
[class.is-invalid]="showErrorMessages"
76-
[class.tree-input]="!model.readOnly"
77-
[id]="id"
78-
[name]="model.name"
79-
[placeholder]="model.placeholder"
80-
[readonly]="true"
81-
[disabled]="model.readOnly"
82-
[type]="model.inputType"
83-
[value]="currentValue?.display"
84-
(focus)="onFocus($event)"
85-
(change)="onChange($event)"
86-
(click)="openTree($event)"
87-
(keydown)="$event.preventDefault()"
88-
(keypress)="$event.preventDefault()"
89-
(keyup)="$event.preventDefault()">
90-
</div>
91-
}
93+
}
94+
</div>

src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.spec.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
mockDynamicFormLayoutService,
2929
mockDynamicFormValidationService,
3030
} from '@dspace/core/testing/dynamic-form-mock-services';
31+
import { SearchServiceStub } from '@dspace/core/testing/search-service.stub';
3132
import { createTestComponent } from '@dspace/core/testing/utils.test';
3233
import { VocabularyServiceStub } from '@dspace/core/testing/vocabulary-service.stub';
3334
import { createSuccessfulRemoteDataObject$ } from '@dspace/core/utilities/remote-data.utils';
@@ -44,6 +45,7 @@ import { TranslateModule } from '@ngx-translate/core';
4445
import { getTestScheduler } from 'jasmine-marbles';
4546
import { of } from 'rxjs';
4647
import { TestScheduler } from 'rxjs/testing';
48+
import { SearchService } from 'src/app/shared/search/search.service';
4749

4850
import { ObjNgFor } from '../../../../../utils/object-ngfor.pipe';
4951
import { AuthorityConfidenceStateDirective } from '../../../../directives/authority-confidence-state.directive';
@@ -97,6 +99,7 @@ describe('DsDynamicOneboxComponent test suite', () => {
9799
let testFixture: ComponentFixture<TestComponent>;
98100
let oneboxCompFixture: ComponentFixture<DsDynamicOneboxComponent>;
99101
let vocabularyServiceStub: any;
102+
let searchServiceStub: any;
100103
let modalService: any;
101104
let html;
102105
let modal;
@@ -137,6 +140,7 @@ describe('DsDynamicOneboxComponent test suite', () => {
137140
// waitForAsync beforeEach
138141
beforeEach(() => {
139142
vocabularyServiceStub = new VocabularyServiceStub();
143+
searchServiceStub = new SearchServiceStub();
140144

141145
modal = jasmine.createSpyObj('modal',
142146
{
@@ -164,6 +168,7 @@ describe('DsDynamicOneboxComponent test suite', () => {
164168
ChangeDetectorRef,
165169
DsDynamicOneboxComponent,
166170
{ provide: VocabularyService, useValue: vocabularyServiceStub },
171+
{ provide: SearchService, useValue: searchServiceStub },
167172
{ provide: DynamicFormLayoutService, useValue: mockDynamicFormLayoutService },
168173
{ provide: DynamicFormValidationService, useValue: mockDynamicFormValidationService },
169174
{ provide: NgbModal, useValue: modal },
@@ -259,14 +264,14 @@ describe('DsDynamicOneboxComponent test suite', () => {
259264

260265
it('should emit blur Event onBlur when popup is closed', () => {
261266
spyOn(oneboxComponent.blur, 'emit');
262-
spyOn(oneboxComponent.instance, 'isPopupOpen').and.returnValue(false);
267+
spyOn(oneboxComponent.typeahead, 'isPopupOpen').and.returnValue(false);
263268
oneboxComponent.onBlur(new Event('blur'));
264269
expect(oneboxComponent.blur.emit).toHaveBeenCalled();
265270
});
266271

267272
it('should not emit blur Event onBlur when popup is opened', () => {
268273
spyOn(oneboxComponent.blur, 'emit');
269-
spyOn(oneboxComponent.instance, 'isPopupOpen').and.returnValue(true);
274+
spyOn(oneboxComponent.typeahead, 'isPopupOpen').and.returnValue(true);
270275
const input = oneboxCompFixture.debugElement.query(By.css('input'));
271276

272277
input.nativeElement.blur();
@@ -278,7 +283,7 @@ describe('DsDynamicOneboxComponent test suite', () => {
278283
oneboxCompFixture.detectChanges();
279284
spyOn(oneboxComponent.blur, 'emit');
280285
spyOn(oneboxComponent.change, 'emit');
281-
spyOn(oneboxComponent.instance, 'isPopupOpen').and.returnValue(false);
286+
spyOn(oneboxComponent.typeahead, 'isPopupOpen').and.returnValue(false);
282287
oneboxComponent.onBlur(new Event('blur'));
283288
expect(oneboxComponent.change.emit).toHaveBeenCalled();
284289
expect(oneboxComponent.blur.emit).toHaveBeenCalled();
@@ -291,7 +296,7 @@ describe('DsDynamicOneboxComponent test suite', () => {
291296
oneboxCompFixture.detectChanges();
292297
spyOn(oneboxComponent.blur, 'emit');
293298
spyOn(oneboxComponent.change, 'emit');
294-
spyOn(oneboxComponent.instance, 'isPopupOpen').and.returnValue(false);
299+
spyOn(oneboxComponent.typeahead, 'isPopupOpen').and.returnValue(false);
295300
oneboxComponent.onBlur(new Event('blur'));
296301
expect(oneboxComponent.change.emit).not.toHaveBeenCalled();
297302
expect(oneboxComponent.blur.emit).toHaveBeenCalled();
@@ -304,7 +309,7 @@ describe('DsDynamicOneboxComponent test suite', () => {
304309
oneboxCompFixture.detectChanges();
305310
spyOn(oneboxComponent.blur, 'emit');
306311
spyOn(oneboxComponent.change, 'emit');
307-
spyOn(oneboxComponent.instance, 'isPopupOpen').and.returnValue(false);
312+
spyOn(oneboxComponent.typeahead, 'isPopupOpen').and.returnValue(false);
308313
oneboxComponent.onBlur(new Event('blur'));
309314
expect(oneboxComponent.change.emit).not.toHaveBeenCalled();
310315
expect(oneboxComponent.blur.emit).toHaveBeenCalled();

0 commit comments

Comments
 (0)