[CST-3088] fixed code

This commit is contained in:
Giuseppe Digilio
2020-07-01 22:57:26 +02:00
parent 7e80bcf9e9
commit 44381d7653
17 changed files with 223 additions and 92 deletions

View File

@@ -251,7 +251,6 @@ export class AuthInterceptor implements HttpInterceptor {
// Pass on the new request instead of the original request.
return next.handle(newReq).pipe(
// tap((response) => console.log('next.handle: ', response)),
map((response) => {
// Intercept a Login/Logout response
if (response instanceof HttpResponse && this.isSuccess(response) && this.isAuthRequest(response)) {

View File

@@ -144,6 +144,7 @@ import { Vocabulary } from './submission/vocabularies/models/vocabulary.model';
import { VocabularyEntriesResponseParsingService } from './submission/vocabularies/vocabulary-entries-response-parsing.service';
import { VocabularyEntryDetail } from './submission/vocabularies/models/vocabulary-entry-detail.model';
import { VocabularyService } from './submission/vocabularies/vocabulary.service';
import { VocabularyTreeviewService } from '../shared/vocabulary-treeview/vocabulary-treeview.service';
/**
* When not in production, endpoint responses can be mocked for testing purposes
@@ -268,7 +269,8 @@ const PROVIDERS = [
FilteredDiscoveryPageResponseParsingService,
{ provide: NativeWindowService, useFactory: NativeWindowFactory },
VocabularyService,
VocabularyEntriesResponseParsingService
VocabularyEntriesResponseParsingService,
VocabularyTreeviewService
];
/**

View File

@@ -115,12 +115,6 @@ export abstract class DataService<T extends CacheableObject> {
result$ = this.getSearchEndpoint(searchMethod);
if (hasValue(options.searchParams)) {
options.searchParams.forEach((param: RequestParam) => {
args.push(`${param.fieldName}=${param.fieldValue}`);
})
}
return result$.pipe(map((result: string) => this.buildHrefFromFindOptions(result, options, args, ...linksToFollow)));
}
@@ -351,7 +345,7 @@ export abstract class DataService<T extends CacheableObject> {
return hrefObs.pipe(
find((href: string) => hasValue(href)),
tap((href: string) => {
this.requestService.removeByHrefSubstring(href);
this.requestService.removeByHrefSubstring(searchMethod);
const request = new FindListRequest(this.requestService.generateRequestId(), href, options);
if (hasValue(this.responseMsToLive)) {
request.responseMsToLive = this.responseMsToLive;

View File

@@ -20,6 +20,7 @@ import { VocabularyService } from './vocabulary.service';
import { getMockRequestService } from '../../../shared/mocks/request.service.mock';
import { getMockRemoteDataBuildService } from '../../../shared/mocks/remote-data-build.service.mock';
import { VocabularyOptions } from './models/vocabulary-options.model';
import { VocabularyFindOptions } from './models/vocabulary-find-options.model';
describe('VocabularyService', () => {
let scheduler: TestScheduler;
@@ -208,9 +209,9 @@ describe('VocabularyService', () => {
const endpointURL = `https://rest.api/rest/api/submission/vocabularies`;
const requestURL = `https://rest.api/rest/api/submission/vocabularies/${vocabulary.id}`;
const entryDetailEndpointURL = `https://rest.api/rest/api/submission/vocabularyEntryDetails`;
const entryDetailRequestURL = `https://rest.api/rest/api/submission/vocabularyEntryDetails/srsc:SCB110/${hierarchicalVocabulary.id}:testValue`;
const entryDetailParentRequestURL = `https://rest.api/rest/api/submission/vocabularyEntryDetails/srsc:SCB110/${hierarchicalVocabulary.id}:testValue/parent`;
const entryDetailChildrenRequestURL = `https://rest.api/rest/api/submission/vocabularyEntryDetails/srsc:SCB110/${hierarchicalVocabulary.id}:testValue/children`;
const entryDetailRequestURL = `https://rest.api/rest/api/submission/vocabularyEntryDetails/${hierarchicalVocabulary.id}:testValue`;
const entryDetailParentRequestURL = `https://rest.api/rest/api/submission/vocabularyEntryDetails/${hierarchicalVocabulary.id}:testValue/parent`;
const entryDetailChildrenRequestURL = `https://rest.api/rest/api/submission/vocabularyEntryDetails/${hierarchicalVocabulary.id}:testValue/children`;
const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a';
const vocabularyId = 'types';
const metadata = 'dc.type';
@@ -490,7 +491,7 @@ describe('VocabularyService', () => {
scheduler = getTestScheduler();
halService = jasmine.createSpyObj('halService', {
getEndpoint: cold('a', { a: endpointURL })
getEndpoint: cold('a', { a: entryDetailEndpointURL })
});
responseCacheEntry = new RequestEntry();
@@ -517,9 +518,10 @@ describe('VocabularyService', () => {
spyOn((service as any).vocabularyEntryDetailDataService, 'findById').and.callThrough();
spyOn((service as any).vocabularyEntryDetailDataService, 'findAll').and.callThrough();
spyOn((service as any).vocabularyEntryDetailDataService, 'findByHref').and.callThrough();
spyOn((service as any).vocabularyEntryDetailDataService, 'findAllByHref').and.callThrough();
spyOn((service as any).vocabularyEntryDetailDataService, 'searchBy').and.callThrough();
spyOn((service as any).vocabularyEntryDetailDataService, 'getSearchByHref').and.returnValue(observableOf(searchRequestURL));
spyOn((service as any).vocabularyEntryDetailDataService, 'getFindAllHref').and.returnValue(observableOf(entriesRequestURL));
spyOn((service as any).vocabularyEntryDetailDataService, 'getFindAllHref').and.returnValue(observableOf(entryDetailChildrenRequestURL));
spyOn((service as any).vocabularyEntryDetailDataService, 'getBrowseEndpoint').and.returnValue(observableOf(entryDetailEndpointURL));
});
@@ -528,7 +530,7 @@ describe('VocabularyService', () => {
});
describe('findEntryDetailByHref', () => {
it('should proxy the call to vocabularyDataService.findVocabularyByHref', () => {
it('should proxy the call to vocabularyDataService.findEntryDetailByHref', () => {
scheduler.schedule(() => service.findEntryDetailByHref(entryDetailRequestURL));
scheduler.flush();
@@ -563,7 +565,7 @@ describe('VocabularyService', () => {
describe('getEntryDetailParent', () => {
it('should proxy the call to vocabularyDataService.getEntryDetailParent', () => {
scheduler.schedule(() => service.getEntryDetailParent('testValue', hierarchicalVocabulary.id));
scheduler.schedule(() => service.getEntryDetailParent('testValue', hierarchicalVocabulary.id).subscribe());
scheduler.flush();
expect((service as any).vocabularyEntryDetailDataService.findByHref).toHaveBeenCalledWith(entryDetailParentRequestURL);
@@ -580,10 +582,20 @@ describe('VocabularyService', () => {
describe('getEntryDetailChildren', () => {
it('should proxy the call to vocabularyDataService.getEntryDetailChildren', () => {
scheduler.schedule(() => service.getEntryDetailChildren('testValue', hierarchicalVocabulary.id, new PageInfo()));
const options: VocabularyFindOptions = new VocabularyFindOptions(
null,
null,
null,
null,
null,
null,
pageInfo.elementsPerPage,
pageInfo.currentPage
);
scheduler.schedule(() => service.getEntryDetailChildren('testValue', hierarchicalVocabulary.id, pageInfo).subscribe());
scheduler.flush();
expect((service as any).vocabularyEntryDetailDataService.findAllByHref).toHaveBeenCalledWith(entryDetailChildrenRequestURL);
expect((service as any).vocabularyEntryDetailDataService.findAllByHref).toHaveBeenCalledWith(entryDetailChildrenRequestURL, options);
});
it('should return a RemoteData<PaginatedList<ResourcePolicy>> for the object with the given URL', () => {
@@ -595,9 +607,19 @@ describe('VocabularyService', () => {
});
});
describe('searchByTop', () => {
it('should proxy the call to vocabularyEntryDetailDataService.searchBy', () => {
const options = new FindListOptions();
const options: VocabularyFindOptions = new VocabularyFindOptions(
null,
null,
null,
null,
null,
null,
pageInfo.elementsPerPage,
pageInfo.currentPage
);
options.searchParams = [new RequestParam('vocabulary', 'srsc')];
scheduler.schedule(() => service.searchTopEntries('srsc', pageInfo));
scheduler.flush();
@@ -615,5 +637,14 @@ describe('VocabularyService', () => {
});
describe('clearSearchTopRequests', () => {
it('should remove requests on the data service\'s endpoint', (done) => {
service.clearSearchTopRequests();
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(`search/${(service as any).searchTopMethod}`);
done();
});
});
});
});

View File

@@ -353,10 +353,26 @@ export class VocabularyService {
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
*/
searchTopEntries(name: string, pageInfo: PageInfo, ...linksToFollow: Array<FollowLinkConfig<VocabularyEntryDetail>>): Observable<RemoteData<PaginatedList<VocabularyEntryDetail>>> {
const options = new FindListOptions();
const options: VocabularyFindOptions = new VocabularyFindOptions(
null,
null,
null,
null,
null,
null,
pageInfo.elementsPerPage,
pageInfo.currentPage
);
options.searchParams = [new RequestParam('vocabulary', name)];
return this.vocabularyEntryDetailDataService.searchBy(this.searchTopMethod, options, ...linksToFollow)
}
/**
* Clear all search Top Requests
*/
clearSearchTopRequests(): void {
this.requestService.removeByHrefSubstring(`search/${this.searchTopMethod}`);
}
}
/**

View File

@@ -59,12 +59,13 @@ export abstract class DsDynamicVocabularyComponent extends DynamicFormControlCom
}
initValue$ = initEntry$.pipe(map((initEntry: VocabularyEntry) => {
if (isNotEmpty(initEntry)) {
return new FormFieldMetadataValueObject(
initEntry.value,
null,
initEntry.authority,
initEntry.display
);
// Integrate FormFieldMetadataValueObject with retrieved information
return Object.assign(new FormFieldMetadataValueObject(), this.model.value, {
value: initEntry.value,
authority: initEntry.authority,
display: initEntry.display,
otherInformation: initEntry.otherInformation
});
} else {
return this.model.value as any;
}

View File

@@ -20,6 +20,7 @@ import { createTestComponent } from '../../../../../testing/utils.test';
import { AuthorityConfidenceStateDirective } from '../../../../../authority-confidence/authority-confidence-state.directive';
import { ObjNgFor } from '../../../../../utils/object-ngfor.pipe';
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
import { createSuccessfulRemoteDataObject$ } from '../../../../../remote-data.utils';
export let TYPEAHEAD_TEST_GROUP;
@@ -34,7 +35,7 @@ function init() {
vocabularyOptions: {
closed: false,
metadata: 'typeahead',
name: 'EVENTAuthority',
name: 'vocabulary',
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
} as VocabularyOptions,
disabled: false,
@@ -50,13 +51,47 @@ function init() {
};
}
describe('DsDynamicTypeaheadComponent test suite', () => {
fdescribe('DsDynamicTypeaheadComponent test suite', () => {
let testComp: TestComponent;
let typeaheadComp: DsDynamicTypeaheadComponent;
let testFixture: ComponentFixture<TestComponent>;
let typeaheadFixture: ComponentFixture<DsDynamicTypeaheadComponent>;
let service: any;
let html;
let vocabulary = {
id: 'vocabulary',
name: 'vocabulary',
scrollable: true,
hierarchical: false,
preloadLevel: 0,
type: 'vocabulary',
_links: {
self: {
url: 'self'
},
entries: {
url: 'entries'
}
}
}
let hierarchicalVocabulary = {
id: 'hierarchicalVocabulary',
name: 'hierarchicalVocabulary',
scrollable: true,
hierarchical: true,
preloadLevel: 2,
type: 'vocabulary',
_links: {
self: {
url: 'self'
},
entries: {
url: 'entries'
}
}
}
// async beforeEach
beforeEach(async(() => {
@@ -113,7 +148,7 @@ describe('DsDynamicTypeaheadComponent test suite', () => {
}));
});
describe('', () => {
describe('not hiearchical', () => {
describe('when init model value is empty', () => {
beforeEach(() => {
@@ -121,6 +156,8 @@ describe('DsDynamicTypeaheadComponent test suite', () => {
typeaheadComp = typeaheadFixture.componentInstance; // FormComponent test instance
typeaheadComp.group = TYPEAHEAD_TEST_GROUP;
typeaheadComp.model = new DynamicTypeaheadModel(TYPEAHEAD_TEST_MODEL_CONFIG);
service = (typeaheadComp as any).vocabularyService;
spyOn(service, 'findVocabularyById').and.returnValue(createSuccessfulRemoteDataObject$(vocabulary));
typeaheadFixture.detectChanges();
});

View File

@@ -119,7 +119,8 @@ export class DsDynamicTypeaheadComponent extends DsDynamicVocabularyComponent im
}
this.vocabulary$ = this.vocabularyService.findVocabularyById(this.model.vocabularyOptions.name).pipe(
getFirstSucceededRemoteDataPayload()
getFirstSucceededRemoteDataPayload(),
distinctUntilChanged()
);
this.group.get(this.model.id).valueChanges.pipe(

View File

@@ -11,6 +11,7 @@ export interface OtherInformation {
* A class representing a specific input-form field's value
*/
export class FormFieldMetadataValueObject implements MetadataValueInterface {
metadata?: string;
value: any;
display: string;
language: any;
@@ -43,6 +44,9 @@ export class FormFieldMetadataValueObject implements MetadataValueInterface {
}
this.place = place;
if (isNotEmpty(metadata)) {
this.metadata = metadata;
}
this.otherInformation = otherInformation;
}

View File

@@ -182,8 +182,8 @@ export abstract class FieldParser {
fieldIds.forEach((id) => {
if (this.initFormValues.hasOwnProperty(id)) {
const valueObj: FormFieldMetadataValueObject = Object.assign(new FormFieldMetadataValueObject(), this.initFormValues[id][innerIndex]);
// valueObj.metadata = id;
// valueObj.value = this.initFormValues[id][innerIndex];
// Set metadata name, used for Qualdrop fields
valueObj.metadata = id;
values.push(valueObj);
}
});

View File

@@ -6,6 +6,7 @@ import { PaginatedList } from '../../core/data/paginated-list';
import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
import { RemoteData } from '../../core/data/remote-data';
import { VocabularyOptions } from '../../core/submission/vocabularies/models/vocabulary-options.model';
import { Vocabulary } from '../../core/submission/vocabularies/models/vocabulary.model';
export class VocabularyServiceStub {
@@ -37,4 +38,8 @@ export class VocabularyServiceStub {
getVocabularyEntryByID(id: string, vocabularyOptions: VocabularyOptions): Observable<VocabularyEntry> {
return observableOf(Object.assign(new VocabularyEntry(), { authority: 1, display: 'one', value: 1 }));
}
findVocabularyById(id: string): Observable<RemoteData<Vocabulary>> {
return;
}
}

View File

@@ -1,5 +1,5 @@
<div class="modal-header">
<h4 class="modal-title">{{'treeview.header' | translate}}</h4>
<h4 class="modal-title">{{'vocabulary-treeview.header' | translate}}</h4>
<button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross click')">
<span aria-hidden="true">&times;</span>
</button>
@@ -13,19 +13,19 @@
<input type="text" class="form-control" [(ngModel)]="searchText" (keyup.enter)="search()">
<div class="input-group-append" id="button-addon4">
<button class="btn btn-outline-primary" type="button" (click)="search()" [disabled]="!isSearchEnabled()">
{{'treeview.search.form.search' | translate}}
{{'vocabulary-treeview.search.form.search' | translate}}
</button>
<button class="btn btn-outline-secondary" type="button" (click)="reset()">
{{'treeview.search.form.reset' | translate}}
{{'vocabulary-treeview.search.form.reset' | translate}}
</button>
</div>
</div>
</div>
</div>
<div class="treeview-container">
<ds-loading *ngIf="searching | async" [showMessage]="false"></ds-loading>
<h4 *ngIf="!(searching | async) && dataSource.data.length === 0" class="text-center text-muted mt-4" >
<span>{{'treeview.search.no-result' | translate}}</span>
<ds-loading *ngIf="loading | async" [showMessage]="false"></ds-loading>
<h4 *ngIf="!(loading | async) && dataSource.data.length === 0" class="text-center text-muted mt-4" >
<span>{{'vocabulary-treeview.search.no-result' | translate}}</span>
</h4>
<cdk-tree [dataSource]="dataSource" [treeControl]="treeControl">
<!-- Leaf node -->
@@ -62,13 +62,13 @@
<cdk-tree-node *cdkTreeNodeDef="let node; when: isLoadMore" cdkTreeNodePadding>
<button class="btn btn-outline-secondary btn-sm" (click)="loadMore(node.loadMoreParentItem)">
{{'treeview.load-more' | translate}}...
{{'vocabulary-treeview.load-more' | translate}}...
</button>
</cdk-tree-node>
<cdk-tree-node *cdkTreeNodeDef="let node; when: isLoadMoreRoot">
<button class="btn btn-outline-secondary btn-sm" (click)="loadMoreRoot(node)">
{{'treeview.load-more' | translate}}...
{{'vocabulary-treeview.load-more' | translate}}...
</button>
</cdk-tree-node>
</cdk-tree>

View File

@@ -16,6 +16,7 @@ import { TreeviewFlatNode } from './vocabulary-treeview-node.model';
import { FormFieldMetadataValueObject } from '../form/builder/models/form-field-metadata-value.model';
import { VocabularyOptions } from '../../core/submission/vocabularies/models/vocabulary-options.model';
import { PageInfo } from '../../core/shared/page-info.model';
import { VocabularyEntry } from '../../core/submission/vocabularies/models/vocabulary-entry.model';
describe('VocabularyTreeviewComponent test suite', () => {
@@ -37,7 +38,7 @@ describe('VocabularyTreeviewComponent test suite', () => {
getData: jasmine.createSpy('getData'),
loadMore: jasmine.createSpy('loadMore'),
loadMoreRoot: jasmine.createSpy('loadMoreRoot'),
isSearching: jasmine.createSpy('isSearching'),
isLoading: jasmine.createSpy('isLoading'),
searchByQuery: jasmine.createSpy('searchByQuery'),
restoreNodes: jasmine.createSpy('restoreNodes'),
cleanTree: jasmine.createSpy('cleanTree'),
@@ -100,7 +101,7 @@ describe('VocabularyTreeviewComponent test suite', () => {
comp = fixture.componentInstance;
compAsAny = comp;
vocabularyTreeviewServiceStub.getData.and.returnValue(observableOf([]));
vocabularyTreeviewServiceStub.isSearching.and.returnValue(observableOf(false));
vocabularyTreeviewServiceStub.isLoading.and.returnValue(observableOf(false));
comp.vocabularyOptions = vocabularyOptions;
comp.selectedItem = null;
});
@@ -118,19 +119,27 @@ describe('VocabularyTreeviewComponent test suite', () => {
});
it('should should init component properly with init value as FormFieldMetadataValueObject', () => {
comp.selectedItem = new FormFieldMetadataValueObject('test', null, 'auth001');
const currentValue = new FormFieldMetadataValueObject();
currentValue.value = 'testValue';
currentValue.otherInformation = {
id: 'entryID'
};
comp.selectedItem = currentValue;
fixture.detectChanges();
expect(comp.dataSource.data).toEqual([]);
expect(vocabularyTreeviewServiceStub.initialize).toHaveBeenCalledWith(comp.vocabularyOptions, new PageInfo(), 'auth001');
expect(vocabularyTreeviewServiceStub.initialize).toHaveBeenCalledWith(comp.vocabularyOptions, new PageInfo(), 'entryID');
});
it('should should init component properly with init value as AuthorityEntry', () => {
const authority = new VocabularyEntryDetail();
authority.id = 'auth001';
comp.selectedItem = authority;
it('should should init component properly with init value as VocabularyEntry', () => {
const currentValue = new VocabularyEntry();
currentValue.value = 'testValue';
currentValue.otherInformation = {
id: 'entryID'
};
comp.selectedItem = currentValue;
fixture.detectChanges();
expect(comp.dataSource.data).toEqual([]);
expect(vocabularyTreeviewServiceStub.initialize).toHaveBeenCalledWith(comp.vocabularyOptions, new PageInfo(), 'auth001');
expect(vocabularyTreeviewServiceStub.initialize).toHaveBeenCalledWith(comp.vocabularyOptions, new PageInfo(), 'entryID');
});
it('should call loadMore function', () => {

View File

@@ -16,6 +16,7 @@ import { VocabularyTreeviewService } from './vocabulary-treeview.service';
import { LOAD_MORE, LOAD_MORE_ROOT, TreeviewFlatNode, TreeviewNode } from './vocabulary-treeview-node.model';
import { VocabularyOptions } from '../../core/submission/vocabularies/models/vocabulary-options.model';
import { PageInfo } from '../../core/shared/page-info.model';
import { VocabularyEntry } from '../../core/submission/vocabularies/models/vocabulary-entry.model';
/**
* Component that show a hierarchical vocabulary in a tree view
@@ -78,9 +79,9 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit {
searchText: string;
/**
* A boolean representing if a search operation is pending
* A boolean representing if tree is loading
*/
searching: Observable<boolean>;
loading: Observable<boolean>;
/**
* An event fired when a vocabulary entry is selected.
@@ -198,7 +199,7 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit {
})
);
const descriptionLabel = 'tree.description.' + this.vocabularyOptions.name;
const descriptionLabel = 'vocabulary-treeview.tree.description.' + this.vocabularyOptions.name;
this.description = this.translate.get(descriptionLabel).pipe(
filter((msg) => msg !== descriptionLabel),
startWith('')
@@ -207,13 +208,13 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit {
// set isAuthenticated
this.isAuthenticated = this.store.pipe(select(isAuthenticated));
this.searching = this.vocabularyTreeviewService.isSearching();
this.loading = this.vocabularyTreeviewService.isLoading();
this.isAuthenticated.pipe(
find((isAuth) => isAuth)
).subscribe(() => {
const valueId: string = (this.selectedItem) ? (this.selectedItem.authority || this.selectedItem.id) : null;
this.vocabularyTreeviewService.initialize(this.vocabularyOptions, new PageInfo(), valueId);
const entryId: string = (this.selectedItem) ? this.getEntryId(this.selectedItem) : null;
this.vocabularyTreeviewService.initialize(this.vocabularyOptions, new PageInfo(), entryId);
});
}
@@ -292,4 +293,11 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit {
.filter((sub) => hasValue(sub))
.forEach((sub) => sub.unsubscribe());
}
/**
* Return an id for a given {@link VocabularyEntry}
*/
private getEntryId(entry: VocabularyEntry): string {
return entry.authority || entry.otherInformation.id || undefined;
}
}

View File

@@ -50,39 +50,47 @@ describe('VocabularyTreeviewService test suite', () => {
let nodeMapWithChildren: Map<string, TreeviewNode>;
let searchNodeMap: Map<string, TreeviewNode>;
let vocabularyOptions;
let pageInfo: PageInfo;
const vocabularyServiceStub = jasmine.createSpyObj('VocabularyService', {
getVocabularyEntriesByValue: jasmine.createSpy('getVocabularyEntriesByValue'),
getEntryDetailParent: jasmine.createSpy('getEntryDetailParent'),
findEntryDetailByValue: jasmine.createSpy('findEntryDetailByValue'),
searchTopEntries: jasmine.createSpy('searchTopEntries'),
getEntryDetailChildren: jasmine.createSpy('getEntryDetailChildren')
getEntryDetailChildren: jasmine.createSpy('getEntryDetailChildren'),
clearSearchTopRequests: jasmine.createSpy('clearSearchTopRequests')
});
function init() {
pageInfo = Object.assign(new PageInfo(), {
elementsPerPage: 1,
totalElements: 3,
totalPages: 1,
currentPage: 1
});
loadMoreNode = new TreeviewNode(LOAD_MORE_NODE, false, new PageInfo(), item);
loadMoreRootNode = new TreeviewNode(LOAD_MORE_ROOT_NODE, false, new PageInfo(), null);
loadMoreRootFlatNode = new TreeviewFlatNode(LOAD_MORE_ROOT_NODE, 1, false, new PageInfo(), null);
item = new VocabularyEntryDetail();
item.id = item.value = item.display = 'root1';
item.otherInformation = { children: 'root1-child1::root1-child2', id: 'root1' };
itemNode = new TreeviewNode(item, true);
item.otherInformation = { hasChildren: 'true', id: 'root1' };
itemNode = new TreeviewNode(item, true, pageInfo);
searchItemNode = new TreeviewNode(item, true, new PageInfo(), null, true);
item2 = new VocabularyEntryDetail();
item2.id = item2.value = item2.display = 'root2';
item2.otherInformation = { id: 'root2' };
itemNode2 = new TreeviewNode(item2);
itemNode2 = new TreeviewNode(item2, false, pageInfo);
item3 = new VocabularyEntryDetail();
item3.id = item3.value = item3.display = 'root3';
item3.otherInformation = { id: 'root3' };
itemNode3 = new TreeviewNode(item3);
itemNode3 = new TreeviewNode(item3, false, pageInfo);
child = new VocabularyEntryDetail();
child.id = child.value = child.display = 'root1-child1';
child.otherInformation = { parent: 'root1', children: 'root1-child1-child1', id: 'root1-child1' };
child.otherInformation = { parent: 'root1', hasChildren: 'true', id: 'root1-child1' };
childNode = new TreeviewNode(child);
searchChildNode = new TreeviewNode(child, true, new PageInfo(), item, true);
@@ -167,12 +175,6 @@ describe('VocabularyTreeviewService test suite', () => {
describe('initialize', () => {
it('should set vocabularyName and call retrieveTopNodes method', () => {
const pageInfo = Object.assign(new PageInfo(), {
elementsPerPage: 1,
totalElements: 3,
totalPages: 1,
currentPage: 1
});
serviceAsAny.vocabularyService.searchTopEntries.and.returnValue(hot('-a', {
a: createSuccessfulRemoteDataObject(new PaginatedList(pageInfo, [item, item2, item3]))
}));
@@ -181,16 +183,12 @@ describe('VocabularyTreeviewService test suite', () => {
scheduler.flush();
expect(serviceAsAny.vocabularyName).toEqual(vocabularyOptions.name);
expect(serviceAsAny.pageInfo).toEqual(pageInfo);
console.log(serviceAsAny.dataChange.value[0].pageInfo, itemNode.pageInfo);
expect(serviceAsAny.dataChange.value).toEqual([itemNode, itemNode2, itemNode3]);
});
it('should set initValueHierarchy', () => {
const pageInfo = Object.assign(new PageInfo(), {
elementsPerPage: 1,
totalElements: 3,
totalPages: 1,
currentPage: 1
});
serviceAsAny.vocabularyService.searchTopEntries.and.returnValue(hot('-c', {
a: createSuccessfulRemoteDataObject(new PaginatedList(pageInfo, [item, item2, item3]))
}));
@@ -239,7 +237,7 @@ describe('VocabularyTreeviewService test suite', () => {
});
it('should add children nodes properly', () => {
const pageInfo = Object.assign(new PageInfo(), {
pageInfo = Object.assign(new PageInfo(), {
elementsPerPage: 1,
totalElements: 2,
totalPages: 2,
@@ -260,7 +258,7 @@ describe('VocabularyTreeviewService test suite', () => {
});
it('should add loadMore node properly', () => {
const pageInfo = Object.assign(new PageInfo(), {
pageInfo = Object.assign(new PageInfo(), {
elementsPerPage: 1,
totalElements: 2,
totalPages: 2,
@@ -285,7 +283,7 @@ describe('VocabularyTreeviewService test suite', () => {
describe('searchByQuery', () => {
it('should set tree data properly after a search', () => {
const pageInfo = Object.assign(new PageInfo(), {
pageInfo = Object.assign(new PageInfo(), {
elementsPerPage: 1,
totalElements: 1,
totalPages: 1,

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
import { flatMap, map, merge, scan, take, tap } from 'rxjs/operators';
import { flatMap, map, merge, scan } from 'rxjs/operators';
import { findIndex } from 'lodash';
import { LOAD_MORE_NODE, LOAD_MORE_ROOT_NODE, TreeviewFlatNode, TreeviewNode } from './vocabulary-treeview-node.model';
@@ -56,14 +56,18 @@ export class VocabularyTreeviewService {
private initValueHierarchy: string[] = [];
/**
* A boolean representing if a search operation is pending
* A boolean representing if any operation is pending
*/
private searching = new BehaviorSubject<boolean>(false);
private loading = new BehaviorSubject<boolean>(false);
/**
* An observable to change the searching status
* The {@link PageInfo} object
*/
private hideSearchingWhenUnsubscribed$ = new Observable(() => () => this.searching.next(false));
private pageInfo: PageInfo;
/**
* An observable to change the loading status
*/
private hideSearchingWhenUnsubscribed$ = new Observable(() => () => this.loading.next(false));
/**
* Initialize instance variables
@@ -92,8 +96,10 @@ export class VocabularyTreeviewService {
* @param initValueId The entry id of the node to mark as selected, if any
*/
initialize(options: VocabularyOptions, pageInfo: PageInfo, initValueId?: string): void {
this.loading.next(true);
this.vocabularyOptions = options;
this.vocabularyName = options.name;
this.pageInfo = pageInfo;
if (isNotEmpty(initValueId)) {
this.getNodeHierarchyById(initValueId)
.subscribe((hierarchy: string[]) => {
@@ -160,17 +166,17 @@ export class VocabularyTreeviewService {
}
/**
* Check if a search operation is pending
* Check if any operation is pending
*/
isSearching(): Observable<boolean> {
return this.searching;
isLoading(): Observable<boolean> {
return this.loading;
}
/**
* Perform a search operation by query
*/
searchByQuery(query: string) {
this.searching.next(true);
this.loading.next(true);
if (isEmpty(this.storedNodes)) {
this.storedNodes = this.dataChange.value;
this.storedNodeMap = this.nodeMap;
@@ -192,7 +198,7 @@ export class VocabularyTreeviewService {
merge(this.hideSearchingWhenUnsubscribed$)
).subscribe((nodes: TreeviewNode[]) => {
this.dataChange.next(nodes);
this.searching.next(false);
this.loading.next(false);
})
}
@@ -200,7 +206,7 @@ export class VocabularyTreeviewService {
* Reset tree state with the one before the search
*/
restoreNodes() {
this.searching.next(false);
this.loading.next(false);
this.dataChange.next(this.storedNodes);
this.nodeMap = this.storedNodeMap;
@@ -224,8 +230,8 @@ export class VocabularyTreeviewService {
const entryDetail: VocabularyEntryDetail = Object.assign(new VocabularyEntryDetail(), entry, {
id: entryId
});
const hasChildren = entry.hasOtherInformation() && isNotEmpty((entry.otherInformation as any).children);
const pageInfo: PageInfo = new PageInfo();
const hasChildren = entry.hasOtherInformation() && (entry.otherInformation as any)!.hasChildren == 'true';
const pageInfo: PageInfo = this.pageInfo;
const isInInitValueHierarchy = this.initValueHierarchy.includes(entryId);
const result = new TreeviewNode(
entryDetail,
@@ -295,8 +301,10 @@ export class VocabularyTreeviewService {
this.vocabularyService.searchTopEntries(this.vocabularyName, pageInfo).pipe(
getFirstSucceededRemoteDataPayload()
).subscribe((list: PaginatedList<VocabularyEntryDetail>) => {
const newNodes: TreeviewNode[] = list.page.map((entry: VocabularyEntryDetail) => this._generateNode(entry))
this.vocabularyService.clearSearchTopRequests();
const newNodes: TreeviewNode[] = list.page.map((entry: VocabularyEntryDetail) => this._generateNode(entry));
nodes.push(...newNodes);
if ((list.pageInfo.currentPage + 1) <= list.pageInfo.totalPages) {
// Need a new load more node
const newPageInfo: PageInfo = Object.assign(new PageInfo(), list.pageInfo, {
@@ -306,6 +314,7 @@ export class VocabularyTreeviewService {
loadMoreNode.updatePageInfo(newPageInfo);
nodes.push(loadMoreNode);
}
this.loading.next(false);
// Notify the change.
this.dataChange.next(nodes);
});
@@ -328,8 +337,7 @@ export class VocabularyTreeviewService {
if (isNotEmpty(children)) {
const newChildren = children
.filter((entry: TreeviewNode) => {
const ii = findIndex(node.children, (nodeEntry) => nodeEntry.item.id === entry.item.id);
return ii === -1;
return findIndex(node.children, (nodeEntry) => nodeEntry.item.id === entry.item.id) === -1;
});
newChildren.forEach((entry: TreeviewNode) => {
entry.loadMoreParentItem = node.item

View File

@@ -2712,6 +2712,24 @@
"title": "DSpace",
"vocabulary-treeview.header": "Hierarchical tree view",
"vocabulary-treeview.load-more": "Load more",
"vocabulary-treeview.search.form.reset": "Reset",
"vocabulary-treeview.search.form.search": "Search",
"vocabulary-treeview.search.no-result": "There were no items to show",
"vocabulary-treeview.tree.description.nsi": "The Norwegian Science Index",
"vocabulary-treeview.tree.description.srsc": "Research Subject Categories",
"administrativeView.search.results.head": "Administrative Search",
"menu.section.admin_search": "Admin Search",