mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge branch 'main' into Hard-redirect-after-log-in
This commit is contained in:
@@ -4,7 +4,6 @@ import { SharedModule } from '../shared/shared.module';
|
||||
import { CommunityListPageComponent } from './community-list-page.component';
|
||||
import { CommunityListPageRoutingModule } from './community-list-page.routing.module';
|
||||
import { CommunityListComponent } from './community-list/community-list.component';
|
||||
import { CdkTreeModule } from '@angular/cdk/tree';
|
||||
|
||||
/**
|
||||
* The page which houses a title and the community list, as described in community-list.component
|
||||
@@ -13,8 +12,7 @@ import { CdkTreeModule } from '@angular/cdk/tree';
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
CommunityListPageRoutingModule,
|
||||
CdkTreeModule,
|
||||
CommunityListPageRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
CommunityListPageComponent,
|
||||
|
@@ -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)) {
|
||||
|
12
src/app/core/cache/response.models.ts
vendored
12
src/app/core/cache/response.models.ts
vendored
@@ -5,7 +5,6 @@ import { PageInfo } from '../shared/page-info.model';
|
||||
import { ConfigObject } from '../config/models/config.model';
|
||||
import { FacetValue } from '../../shared/search/facet-value.model';
|
||||
import { SearchFilterConfig } from '../../shared/search/search-filter-config.model';
|
||||
import { IntegrationModel } from '../integration/models/integration.model';
|
||||
import { PaginatedList } from '../data/paginated-list';
|
||||
import { SubmissionObject } from '../submission/models/submission-object.model';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
@@ -181,17 +180,6 @@ export class TokenResponse extends RestResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export class IntegrationSuccessResponse extends RestResponse {
|
||||
constructor(
|
||||
public dataDefinition: PaginatedList<IntegrationModel>,
|
||||
public statusCode: number,
|
||||
public statusText: string,
|
||||
public pageInfo?: PageInfo
|
||||
) {
|
||||
super(true, statusCode, statusText);
|
||||
}
|
||||
}
|
||||
|
||||
export class PostPatchSuccessResponse extends RestResponse {
|
||||
constructor(
|
||||
public dataDefinition: any,
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
|
||||
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
|
||||
|
||||
import { DynamicFormLayoutService, DynamicFormService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
|
||||
@@ -15,8 +16,8 @@ import { MenuService } from '../shared/menu/menu.service';
|
||||
import { EndpointMockingRestService } from '../shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service';
|
||||
import {
|
||||
MOCK_RESPONSE_MAP,
|
||||
ResponseMapMock,
|
||||
mockResponseMap
|
||||
mockResponseMap,
|
||||
ResponseMapMock
|
||||
} from '../shared/mocks/dspace-rest-v2/mocks/response-map.mock';
|
||||
import { NotificationsService } from '../shared/notifications/notifications.service';
|
||||
import { SelectableListService } from '../shared/object-list/selectable-list/selectable-list.service';
|
||||
@@ -80,9 +81,6 @@ import { EPersonDataService } from './eperson/eperson-data.service';
|
||||
import { EpersonResponseParsingService } from './eperson/eperson-response-parsing.service';
|
||||
import { EPerson } from './eperson/models/eperson.model';
|
||||
import { Group } from './eperson/models/group.model';
|
||||
import { AuthorityService } from './integration/authority.service';
|
||||
import { IntegrationResponseParsingService } from './integration/integration-response-parsing.service';
|
||||
import { AuthorityValue } from './integration/models/authority.value';
|
||||
import { JsonPatchOperationsBuilder } from './json-patch/builder/json-patch-operations-builder';
|
||||
import { MetadataField } from './metadata/metadata-field.model';
|
||||
import { MetadataSchema } from './metadata/metadata-schema.model';
|
||||
@@ -160,6 +158,12 @@ import { SubmissionCcLicenseDataService } from './submission/submission-cc-licen
|
||||
import { SubmissionCcLicence } from './submission/models/submission-cc-license.model';
|
||||
import { SubmissionCcLicenceUrl } from './submission/models/submission-cc-license-url.model';
|
||||
import { SubmissionCcLicenseUrlDataService } from './submission/submission-cc-license-url-data.service';
|
||||
import { VocabularyEntry } from './submission/vocabularies/models/vocabulary-entry.model';
|
||||
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';
|
||||
import { ConfigurationDataService } from './data/configuration-data.service';
|
||||
import { ConfigurationProperty } from './shared/configuration-property.model';
|
||||
import { ReloadGuard } from './reload/reload.guard';
|
||||
@@ -196,7 +200,7 @@ const PROVIDERS = [
|
||||
SiteDataService,
|
||||
DSOResponseParsingService,
|
||||
{ provide: MOCK_RESPONSE_MAP, useValue: mockResponseMap },
|
||||
{ provide: DSpaceRESTv2Service, useFactory: restServiceFactory, deps: [MOCK_RESPONSE_MAP, HttpClient]},
|
||||
{ provide: DSpaceRESTv2Service, useFactory: restServiceFactory, deps: [MOCK_RESPONSE_MAP, HttpClient] },
|
||||
DynamicFormLayoutService,
|
||||
DynamicFormService,
|
||||
DynamicFormValidationService,
|
||||
@@ -238,8 +242,6 @@ const PROVIDERS = [
|
||||
SubmissionResponseParsingService,
|
||||
SubmissionJsonPatchOperationsService,
|
||||
JsonPatchOperationsBuilder,
|
||||
AuthorityService,
|
||||
IntegrationResponseParsingService,
|
||||
UploaderService,
|
||||
UUIDService,
|
||||
NotificationsService,
|
||||
@@ -305,7 +307,10 @@ const PROVIDERS = [
|
||||
},
|
||||
NotificationsService,
|
||||
FilteredDiscoveryPageResponseParsingService,
|
||||
{ provide: NativeWindowService, useFactory: NativeWindowFactory }
|
||||
{ provide: NativeWindowService, useFactory: NativeWindowFactory },
|
||||
VocabularyService,
|
||||
VocabularyEntriesResponseParsingService,
|
||||
VocabularyTreeviewService
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -336,7 +341,6 @@ export const models =
|
||||
SubmissionSectionModel,
|
||||
SubmissionUploadsModel,
|
||||
AuthStatus,
|
||||
AuthorityValue,
|
||||
BrowseEntry,
|
||||
BrowseDefinition,
|
||||
ClaimedTask,
|
||||
@@ -356,6 +360,9 @@ export const models =
|
||||
Feature,
|
||||
Authorization,
|
||||
Registration,
|
||||
Vocabulary,
|
||||
VocabularyEntry,
|
||||
VocabularyEntryDetail,
|
||||
ConfigurationProperty
|
||||
];
|
||||
|
||||
|
@@ -1,40 +1,22 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { ErrorResponse, GenericSuccessResponse, RestResponse } from '../cache/response.models';
|
||||
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||
import { DSpaceSerializer } from '../dspace-rest-v2/dspace.serializer';
|
||||
import { BrowseEntry } from '../shared/browse-entry.model';
|
||||
import { BaseResponseParsingService } from './base-response-parsing.service';
|
||||
import { ResponseParsingService } from './parsing.service';
|
||||
import { RestRequest } from './request.models';
|
||||
import { EntriesResponseParsingService } from './entries-response-parsing.service';
|
||||
import { GenericConstructor } from '../shared/generic-constructor';
|
||||
|
||||
@Injectable()
|
||||
export class BrowseEntriesResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
|
||||
export class BrowseEntriesResponseParsingService extends EntriesResponseParsingService<BrowseEntry> {
|
||||
|
||||
protected toCache = false;
|
||||
|
||||
constructor(
|
||||
protected objectCache: ObjectCacheService,
|
||||
) { super();
|
||||
) {
|
||||
super(objectCache);
|
||||
}
|
||||
|
||||
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
||||
if (isNotEmpty(data.payload)) {
|
||||
let browseEntries = [];
|
||||
if (isNotEmpty(data.payload._embedded) && Array.isArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]])) {
|
||||
const serializer = new DSpaceSerializer(BrowseEntry);
|
||||
browseEntries = serializer.deserializeArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]]);
|
||||
}
|
||||
return new GenericSuccessResponse(browseEntries, data.statusCode, data.statusText, this.processPageInfo(data.payload));
|
||||
} else {
|
||||
return new ErrorResponse(
|
||||
Object.assign(
|
||||
new Error('Unexpected response from browse endpoint'),
|
||||
{ statusCode: data.statusCode, statusText: data.statusText }
|
||||
)
|
||||
);
|
||||
}
|
||||
getSerializerModel(): GenericConstructor<BrowseEntry> {
|
||||
return BrowseEntry;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,21 +1,11 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { compare, Operation } from 'fast-json-patch';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { followLink } from '../../shared/utils/follow-link-config.model';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { SortDirection, SortOptions } from '../cache/models/sort-options.model';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { Item } from '../shared/item.model';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
import { ChangeAnalyzer } from './change-analyzer';
|
||||
import { DataService } from './data.service';
|
||||
import { FindListOptions, PatchRequest } from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
||||
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
||||
import { BundleDataService } from './bundle-data.service';
|
||||
|
@@ -6,18 +6,18 @@ import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-servic
|
||||
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
|
||||
import { getMockTranslateService } from '../../shared/mocks/translate.service.mock';
|
||||
import { fakeAsync, tick } from '@angular/core/testing';
|
||||
import { ContentSourceRequest, GetRequest, RequestError, UpdateContentSourceRequest } from './request.models';
|
||||
import { ContentSourceRequest, GetRequest, UpdateContentSourceRequest } from './request.models';
|
||||
import { ContentSource } from '../shared/content-source.model';
|
||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||
import { RequestEntry } from './request.reducer';
|
||||
import { ErrorResponse, RestResponse } from '../cache/response.models';
|
||||
import { ErrorResponse } from '../cache/response.models';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { Collection } from '../shared/collection.model';
|
||||
import { PageInfo } from '../shared/page-info.model';
|
||||
import { PaginatedList } from './paginated-list';
|
||||
import { createSuccessfulRemoteDataObject } from 'src/app/shared/remote-data.utils';
|
||||
import { hot, getTestScheduler, cold } from 'jasmine-marbles';
|
||||
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
|
||||
const url = 'fake-url';
|
||||
|
@@ -24,7 +24,7 @@ import { RequestService } from './request.service';
|
||||
@dataService(COMMUNITY)
|
||||
export class CommunityDataService extends ComColDataService<Community> {
|
||||
protected linkPath = 'communities';
|
||||
protected topLinkPath = 'communities/search/top';
|
||||
protected topLinkPath = 'search/top';
|
||||
protected cds = this;
|
||||
|
||||
constructor(
|
||||
|
@@ -18,6 +18,7 @@ import { FindListOptions, PatchRequest } from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
||||
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
||||
import { RequestParam } from '../cache/models/request-param.model';
|
||||
|
||||
const endpoint = 'https://rest.api/core';
|
||||
|
||||
@@ -150,7 +151,8 @@ describe('DataService', () => {
|
||||
currentPage: 6,
|
||||
elementsPerPage: 10,
|
||||
sort: sortOptions,
|
||||
startsWith: 'ab'
|
||||
startsWith: 'ab',
|
||||
|
||||
};
|
||||
const expected = `${endpoint}?page=${options.currentPage - 1}&size=${options.elementsPerPage}` +
|
||||
`&sort=${sortOptions.field},${sortOptions.direction}&startsWith=${options.startsWith}`;
|
||||
@@ -160,6 +162,26 @@ describe('DataService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should include all searchParams in href if any provided in options', () => {
|
||||
options = { searchParams: [
|
||||
new RequestParam('param1', 'test'),
|
||||
new RequestParam('param2', 'test2'),
|
||||
] };
|
||||
const expected = `${endpoint}?param1=test¶m2=test2`;
|
||||
|
||||
(service as any).getFindAllHref(options).subscribe((value) => {
|
||||
expect(value).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should include linkPath in href if any provided', () => {
|
||||
const expected = `${endpoint}/test/entries`;
|
||||
|
||||
(service as any).getFindAllHref({}, 'test/entries').subscribe((value) => {
|
||||
expect(value).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should include single linksToFollow as embed', () => {
|
||||
const expected = `${endpoint}?embed=bundles`;
|
||||
|
||||
|
@@ -71,13 +71,17 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
||||
* Return an observable that emits created HREF
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
protected getFindAllHref(options: FindListOptions = {}, linkPath?: string, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<string> {
|
||||
let result$: Observable<string>;
|
||||
public getFindAllHref(options: FindListOptions = {}, linkPath?: string, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<string> {
|
||||
let endpoint$: Observable<string>;
|
||||
const args = [];
|
||||
|
||||
result$ = this.getBrowseEndpoint(options, linkPath).pipe(distinctUntilChanged());
|
||||
endpoint$ = this.getBrowseEndpoint(options).pipe(
|
||||
filter((href: string) => isNotEmpty(href)),
|
||||
map((href: string) => isNotEmpty(linkPath) ? `${href}/${linkPath}` : href),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
|
||||
return result$.pipe(map((result: string) => this.buildHrefFromFindOptions(result, options, args, ...linksToFollow)));
|
||||
return endpoint$.pipe(map((result: string) => this.buildHrefFromFindOptions(result, options, args, ...linksToFollow)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,18 +93,12 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
||||
* Return an observable that emits created HREF
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
protected getSearchByHref(searchMethod: string, options: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<string> {
|
||||
public getSearchByHref(searchMethod: string, options: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<string> {
|
||||
let result$: Observable<string>;
|
||||
const args = [];
|
||||
|
||||
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)));
|
||||
}
|
||||
|
||||
@@ -114,7 +112,7 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
||||
* Return an observable that emits created HREF
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
protected buildHrefFromFindOptions(href: string, options: FindListOptions, extraArgs: string[] = [], ...linksToFollow: Array<FollowLinkConfig<T>>): string {
|
||||
public buildHrefFromFindOptions(href: string, options: FindListOptions, extraArgs: string[] = [], ...linksToFollow: Array<FollowLinkConfig<T>>): string {
|
||||
let args = [...extraArgs];
|
||||
|
||||
if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
|
||||
@@ -130,6 +128,11 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
||||
if (hasValue(options.startsWith)) {
|
||||
args = [...args, `startsWith=${options.startsWith}`];
|
||||
}
|
||||
if (hasValue(options.searchParams)) {
|
||||
options.searchParams.forEach((param: RequestParam) => {
|
||||
args = [...args, `${param.fieldName}=${param.fieldValue}`];
|
||||
})
|
||||
}
|
||||
args = this.addEmbedParams(args, ...linksToFollow);
|
||||
if (isNotEmpty(args)) {
|
||||
return new URLCombiner(href, `?${args.join('&')}`).toString();
|
||||
|
54
src/app/core/data/entries-response-parsing.service.ts
Normal file
54
src/app/core/data/entries-response-parsing.service.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { ErrorResponse, GenericSuccessResponse, RestResponse } from '../cache/response.models';
|
||||
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||
import { DSpaceSerializer } from '../dspace-rest-v2/dspace.serializer';
|
||||
import { BaseResponseParsingService } from './base-response-parsing.service';
|
||||
import { ResponseParsingService } from './parsing.service';
|
||||
import { RestRequest } from './request.models';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { GenericConstructor } from '../shared/generic-constructor';
|
||||
|
||||
/**
|
||||
* An abstract class to extend, responsible for parsing data for an entries response
|
||||
*/
|
||||
export abstract class EntriesResponseParsingService<T extends CacheableObject> extends BaseResponseParsingService implements ResponseParsingService {
|
||||
|
||||
protected toCache = false;
|
||||
|
||||
constructor(
|
||||
protected objectCache: ObjectCacheService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method to implement that must return the dspace serializer Constructor to use during parse
|
||||
*/
|
||||
abstract getSerializerModel(): GenericConstructor<T>;
|
||||
|
||||
/**
|
||||
* Parse response
|
||||
*
|
||||
* @param request
|
||||
* @param data
|
||||
*/
|
||||
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
||||
if (isNotEmpty(data.payload)) {
|
||||
let entries = [];
|
||||
if (isNotEmpty(data.payload._embedded) && Array.isArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]])) {
|
||||
const serializer = new DSpaceSerializer(this.getSerializerModel());
|
||||
entries = serializer.deserializeArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]]);
|
||||
}
|
||||
return new GenericSuccessResponse(entries, data.statusCode, data.statusText, this.processPageInfo(data.payload));
|
||||
} else {
|
||||
return new ErrorResponse(
|
||||
Object.assign(
|
||||
new Error('Unexpected response from browse endpoint'),
|
||||
{ statusCode: data.statusCode, statusText: data.statusText }
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -9,7 +9,6 @@ import { ConfigResponseParsingService } from '../config/config-response-parsing.
|
||||
import { AuthResponseParsingService } from '../auth/auth-response-parsing.service';
|
||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||
import { SubmissionResponseParsingService } from '../submission/submission-response-parsing.service';
|
||||
import { IntegrationResponseParsingService } from '../integration/integration-response-parsing.service';
|
||||
import { RestRequestMethod } from './rest-request-method';
|
||||
import { RequestParam } from '../cache/models/request-param.model';
|
||||
import { EpersonResponseParsingService } from '../eperson/eperson-response-parsing.service';
|
||||
@@ -20,12 +19,13 @@ import { ContentSourceResponseParsingService } from './content-source-response-p
|
||||
import { MappedCollectionsReponseParsingService } from './mapped-collections-reponse-parsing.service';
|
||||
import { ProcessFilesResponseParsingService } from './process-files-response-parsing.service';
|
||||
import { TokenResponseParsingService } from '../auth/token-response-parsing.service';
|
||||
import { VocabularyEntriesResponseParsingService } from '../submission/vocabularies/vocabulary-entries-response-parsing.service';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
|
||||
// uuid and handle requests have separate endpoints
|
||||
export enum IdentifierType {
|
||||
UUID ='uuid',
|
||||
UUID = 'uuid',
|
||||
HANDLE = 'handle'
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ export class GetRequest extends RestRequest {
|
||||
public href: string,
|
||||
public body?: any,
|
||||
public options?: HttpOptions
|
||||
) {
|
||||
) {
|
||||
super(uuid, href, RestRequestMethod.GET, body, options)
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ export class PostRequest extends RestRequest {
|
||||
public href: string,
|
||||
public body?: any,
|
||||
public options?: HttpOptions
|
||||
) {
|
||||
) {
|
||||
super(uuid, href, RestRequestMethod.POST, body)
|
||||
}
|
||||
}
|
||||
@@ -97,7 +97,7 @@ export class PutRequest extends RestRequest {
|
||||
public href: string,
|
||||
public body?: any,
|
||||
public options?: HttpOptions
|
||||
) {
|
||||
) {
|
||||
super(uuid, href, RestRequestMethod.PUT, body)
|
||||
}
|
||||
}
|
||||
@@ -108,7 +108,7 @@ export class DeleteRequest extends RestRequest {
|
||||
public href: string,
|
||||
public body?: any,
|
||||
public options?: HttpOptions
|
||||
) {
|
||||
) {
|
||||
super(uuid, href, RestRequestMethod.DELETE, body)
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ export class OptionsRequest extends RestRequest {
|
||||
public href: string,
|
||||
public body?: any,
|
||||
public options?: HttpOptions
|
||||
) {
|
||||
) {
|
||||
super(uuid, href, RestRequestMethod.OPTIONS, body)
|
||||
}
|
||||
}
|
||||
@@ -130,7 +130,7 @@ export class HeadRequest extends RestRequest {
|
||||
public href: string,
|
||||
public body?: any,
|
||||
public options?: HttpOptions
|
||||
) {
|
||||
) {
|
||||
super(uuid, href, RestRequestMethod.HEAD, body)
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ export class PatchRequest extends RestRequest {
|
||||
public href: string,
|
||||
public body?: any,
|
||||
public options?: HttpOptions
|
||||
) {
|
||||
) {
|
||||
super(uuid, href, RestRequestMethod.PATCH, body)
|
||||
}
|
||||
}
|
||||
@@ -276,16 +276,6 @@ export class TokenPostRequest extends PostRequest {
|
||||
}
|
||||
}
|
||||
|
||||
export class IntegrationRequest extends GetRequest {
|
||||
constructor(uuid: string, href: string) {
|
||||
super(uuid, href);
|
||||
}
|
||||
|
||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||
return IntegrationResponseParsingService;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a submission HTTP GET request object
|
||||
*/
|
||||
@@ -425,6 +415,15 @@ export class MyDSpaceRequest extends GetRequest {
|
||||
public responseMsToLive = 10 * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to get vocabulary entries
|
||||
*/
|
||||
export class VocabularyEntriesRequest extends FindListRequest {
|
||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||
return VocabularyEntriesResponseParsingService;
|
||||
}
|
||||
}
|
||||
|
||||
export class RequestError extends Error {
|
||||
statusCode: number;
|
||||
statusText: string;
|
||||
|
@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
|
||||
|
||||
import { createSelector, select, Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, filter, map, switchMap, take, tap } from 'rxjs/operators';
|
||||
import { filter, map, take, tap } from 'rxjs/operators';
|
||||
import {
|
||||
GroupRegistryCancelGroupAction,
|
||||
GroupRegistryEditGroupAction
|
||||
@@ -21,18 +21,12 @@ import { DataService } from '../data/data.service';
|
||||
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
|
||||
import { PaginatedList } from '../data/paginated-list';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import {
|
||||
CreateRequest,
|
||||
DeleteRequest,
|
||||
FindListOptions,
|
||||
FindListRequest,
|
||||
PostRequest
|
||||
} from '../data/request.models';
|
||||
import { CreateRequest, DeleteRequest, FindListOptions, FindListRequest, PostRequest } from '../data/request.models';
|
||||
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { configureRequest, getResponseFromEntry} from '../shared/operators';
|
||||
import { getResponseFromEntry } from '../shared/operators';
|
||||
import { EPerson } from './models/eperson.model';
|
||||
import { Group } from './models/group.model';
|
||||
import { dataService } from '../cache/builders/build-decorators';
|
||||
|
@@ -1,21 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { IntegrationService } from './integration.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthorityService extends IntegrationService {
|
||||
protected linkPath = 'authorities';
|
||||
protected entriesEndpoint = 'entries';
|
||||
protected entryValueEndpoint = 'entryValues';
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected halService: HALEndpointService) {
|
||||
super();
|
||||
}
|
||||
|
||||
}
|
@@ -1,12 +0,0 @@
|
||||
import { PageInfo } from '../shared/page-info.model';
|
||||
import { IntegrationModel } from './models/integration.model';
|
||||
|
||||
/**
|
||||
* A class to represent the data retrieved by an Integration service
|
||||
*/
|
||||
export class IntegrationData {
|
||||
constructor(
|
||||
public pageInfo: PageInfo,
|
||||
public payload: IntegrationModel[]
|
||||
) { }
|
||||
}
|
@@ -1,221 +0,0 @@
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { ErrorResponse, IntegrationSuccessResponse } from '../cache/response.models';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { PaginatedList } from '../data/paginated-list';
|
||||
import { IntegrationRequest } from '../data/request.models';
|
||||
import { PageInfo } from '../shared/page-info.model';
|
||||
import { IntegrationResponseParsingService } from './integration-response-parsing.service';
|
||||
import { AuthorityValue } from './models/authority.value';
|
||||
|
||||
describe('IntegrationResponseParsingService', () => {
|
||||
let service: IntegrationResponseParsingService;
|
||||
|
||||
const store = {} as Store<CoreState>;
|
||||
const objectCacheService = new ObjectCacheService(store, undefined);
|
||||
const name = 'type';
|
||||
const metadata = 'dc.type';
|
||||
const query = '';
|
||||
const uuid = 'd9d30c0c-69b7-4369-8397-ca67c888974d';
|
||||
const integrationEndpoint = 'https://rest.api/integration/authorities';
|
||||
const entriesEndpoint = `${integrationEndpoint}/${name}/entries?query=${query}&metadata=${metadata}&uuid=${uuid}`;
|
||||
let validRequest;
|
||||
|
||||
let validResponse;
|
||||
|
||||
let invalidResponse1;
|
||||
let invalidResponse2;
|
||||
let pageInfo;
|
||||
let definitions;
|
||||
|
||||
function initVars() {
|
||||
pageInfo = Object.assign(new PageInfo(), {
|
||||
elementsPerPage: 5,
|
||||
totalElements: 5,
|
||||
totalPages: 1,
|
||||
currentPage: 1,
|
||||
_links: {
|
||||
self: { href: 'https://rest.api/integration/authorities/type/entries' }
|
||||
}
|
||||
});
|
||||
definitions = new PaginatedList(pageInfo, [
|
||||
Object.assign(new AuthorityValue(), {
|
||||
type: 'authority',
|
||||
display: 'One',
|
||||
id: 'One',
|
||||
otherInformation: undefined,
|
||||
value: 'One'
|
||||
}),
|
||||
Object.assign(new AuthorityValue(), {
|
||||
type: 'authority',
|
||||
display: 'Two',
|
||||
id: 'Two',
|
||||
otherInformation: undefined,
|
||||
value: 'Two'
|
||||
}),
|
||||
Object.assign(new AuthorityValue(), {
|
||||
type: 'authority',
|
||||
display: 'Three',
|
||||
id: 'Three',
|
||||
otherInformation: undefined,
|
||||
value: 'Three'
|
||||
}),
|
||||
Object.assign(new AuthorityValue(), {
|
||||
type: 'authority',
|
||||
display: 'Four',
|
||||
id: 'Four',
|
||||
otherInformation: undefined,
|
||||
value: 'Four'
|
||||
}),
|
||||
Object.assign(new AuthorityValue(), {
|
||||
type: 'authority',
|
||||
display: 'Five',
|
||||
id: 'Five',
|
||||
otherInformation: undefined,
|
||||
value: 'Five'
|
||||
})
|
||||
]);
|
||||
validRequest = new IntegrationRequest('69f375b5-19f4-4453-8c7a-7dc5c55aafbb', entriesEndpoint);
|
||||
|
||||
validResponse = {
|
||||
payload: {
|
||||
page: {
|
||||
number: 0,
|
||||
size: 5,
|
||||
totalElements: 5,
|
||||
totalPages: 1
|
||||
},
|
||||
_embedded: {
|
||||
authorityEntries: [
|
||||
{
|
||||
display: 'One',
|
||||
id: 'One',
|
||||
otherInformation: {},
|
||||
type: 'authority',
|
||||
value: 'One'
|
||||
},
|
||||
{
|
||||
display: 'Two',
|
||||
id: 'Two',
|
||||
otherInformation: {},
|
||||
type: 'authority',
|
||||
value: 'Two'
|
||||
},
|
||||
{
|
||||
display: 'Three',
|
||||
id: 'Three',
|
||||
otherInformation: {},
|
||||
type: 'authority',
|
||||
value: 'Three'
|
||||
},
|
||||
{
|
||||
display: 'Four',
|
||||
id: 'Four',
|
||||
otherInformation: {},
|
||||
type: 'authority',
|
||||
value: 'Four'
|
||||
},
|
||||
{
|
||||
display: 'Five',
|
||||
id: 'Five',
|
||||
otherInformation: {},
|
||||
type: 'authority',
|
||||
value: 'Five'
|
||||
},
|
||||
],
|
||||
|
||||
},
|
||||
_links: {
|
||||
self: { href: 'https://rest.api/integration/authorities/type/entries' }
|
||||
}
|
||||
},
|
||||
statusCode: 200,
|
||||
statusText: 'OK'
|
||||
};
|
||||
|
||||
invalidResponse1 = {
|
||||
payload: {},
|
||||
statusCode: 400,
|
||||
statusText: 'Bad Request'
|
||||
};
|
||||
|
||||
invalidResponse2 = {
|
||||
payload: {
|
||||
page: {
|
||||
number: 0,
|
||||
size: 5,
|
||||
totalElements: 5,
|
||||
totalPages: 1
|
||||
},
|
||||
_embedded: {
|
||||
authorityEntries: [
|
||||
{
|
||||
display: 'One',
|
||||
id: 'One',
|
||||
otherInformation: {},
|
||||
type: 'authority',
|
||||
value: 'One'
|
||||
},
|
||||
{
|
||||
display: 'Two',
|
||||
id: 'Two',
|
||||
otherInformation: {},
|
||||
type: 'authority',
|
||||
value: 'Two'
|
||||
},
|
||||
{
|
||||
display: 'Three',
|
||||
id: 'Three',
|
||||
otherInformation: {},
|
||||
type: 'authority',
|
||||
value: 'Three'
|
||||
},
|
||||
{
|
||||
display: 'Four',
|
||||
id: 'Four',
|
||||
otherInformation: {},
|
||||
type: 'authority',
|
||||
value: 'Four'
|
||||
},
|
||||
{
|
||||
display: 'Five',
|
||||
id: 'Five',
|
||||
otherInformation: {},
|
||||
type: 'authority',
|
||||
value: 'Five'
|
||||
},
|
||||
],
|
||||
|
||||
},
|
||||
_links: {}
|
||||
},
|
||||
statusCode: 500,
|
||||
statusText: 'Internal Server Error'
|
||||
};
|
||||
}
|
||||
beforeEach(() => {
|
||||
initVars();
|
||||
service = new IntegrationResponseParsingService(objectCacheService);
|
||||
});
|
||||
|
||||
describe('parse', () => {
|
||||
it('should return a IntegrationSuccessResponse if data contains a valid endpoint response', () => {
|
||||
const response = service.parse(validRequest, validResponse);
|
||||
expect(response.constructor).toBe(IntegrationSuccessResponse);
|
||||
});
|
||||
|
||||
it('should return an ErrorResponse if data contains an invalid config endpoint response', () => {
|
||||
const response1 = service.parse(validRequest, invalidResponse1);
|
||||
const response2 = service.parse(validRequest, invalidResponse2);
|
||||
expect(response1.constructor).toBe(ErrorResponse);
|
||||
expect(response2.constructor).toBe(ErrorResponse);
|
||||
});
|
||||
|
||||
it('should return a IntegrationSuccessResponse with data definition', () => {
|
||||
const response = service.parse(validRequest, validResponse);
|
||||
expect((response as any).dataDefinition).toEqual(definitions);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@@ -1,50 +0,0 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { RestRequest } from '../data/request.models';
|
||||
import { ResponseParsingService } from '../data/parsing.service';
|
||||
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||
import { ErrorResponse, IntegrationSuccessResponse, RestResponse } from '../cache/response.models';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
|
||||
import { BaseResponseParsingService } from '../data/base-response-parsing.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { IntegrationModel } from './models/integration.model';
|
||||
import { AuthorityValue } from './models/authority.value';
|
||||
import { PaginatedList } from '../data/paginated-list';
|
||||
|
||||
@Injectable()
|
||||
export class IntegrationResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
|
||||
|
||||
protected toCache = true;
|
||||
|
||||
constructor(
|
||||
protected objectCache: ObjectCacheService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
||||
if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links)) {
|
||||
const dataDefinition = this.process<IntegrationModel>(data.payload, request);
|
||||
return new IntegrationSuccessResponse(this.processResponse(dataDefinition), data.statusCode, data.statusText, this.processPageInfo(data.payload));
|
||||
} else {
|
||||
return new ErrorResponse(
|
||||
Object.assign(
|
||||
new Error('Unexpected response from Integration endpoint'),
|
||||
{statusCode: data.statusCode, statusText: data.statusText}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected processResponse(data: PaginatedList<IntegrationModel>): any {
|
||||
const returnList = Array.of();
|
||||
data.page.forEach((item, index) => {
|
||||
if (item.type === AuthorityValue.type.value) {
|
||||
data.page[index] = Object.assign(new AuthorityValue(), item);
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
@@ -1,96 +0,0 @@
|
||||
import { cold, getTestScheduler } from 'jasmine-marbles';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
||||
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { IntegrationRequest } from '../data/request.models';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
||||
import { IntegrationService } from './integration.service';
|
||||
import { IntegrationSearchOptions } from './models/integration-options.model';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { getMockRemoteDataBuildService } from '../../shared/mocks/remote-data-build.service.mock';
|
||||
|
||||
const LINK_NAME = 'authorities';
|
||||
const ENTRIES = 'entries';
|
||||
const ENTRY_VALUE = 'entryValue';
|
||||
|
||||
class TestService extends IntegrationService {
|
||||
protected linkPath = LINK_NAME;
|
||||
protected entriesEndpoint = ENTRIES;
|
||||
protected entryValueEndpoint = ENTRY_VALUE;
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected halService: HALEndpointService) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
describe('IntegrationService', () => {
|
||||
let scheduler: TestScheduler;
|
||||
let service: TestService;
|
||||
let requestService: RequestService;
|
||||
let rdbService: RemoteDataBuildService;
|
||||
let halService: any;
|
||||
let findOptions: IntegrationSearchOptions;
|
||||
|
||||
const name = 'type';
|
||||
const metadata = 'dc.type';
|
||||
const query = '';
|
||||
const value = 'test';
|
||||
const uuid = 'd9d30c0c-69b7-4369-8397-ca67c888974d';
|
||||
const integrationEndpoint = 'https://rest.api/integration';
|
||||
const serviceEndpoint = `${integrationEndpoint}/${LINK_NAME}`;
|
||||
const entriesEndpoint = `${serviceEndpoint}/${name}/entries?query=${query}&metadata=${metadata}&uuid=${uuid}`;
|
||||
const entryValueEndpoint = `${serviceEndpoint}/${name}/entryValue/${value}?metadata=${metadata}`;
|
||||
|
||||
findOptions = new IntegrationSearchOptions(uuid, name, metadata);
|
||||
|
||||
function initTestService(): TestService {
|
||||
return new TestService(
|
||||
requestService,
|
||||
rdbService,
|
||||
halService
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
requestService = getMockRequestService();
|
||||
rdbService = getMockRemoteDataBuildService();
|
||||
scheduler = getTestScheduler();
|
||||
halService = new HALEndpointServiceStub(integrationEndpoint);
|
||||
findOptions = new IntegrationSearchOptions(uuid, name, metadata, query);
|
||||
service = initTestService();
|
||||
|
||||
});
|
||||
|
||||
describe('getEntriesByName', () => {
|
||||
|
||||
it('should configure a new IntegrationRequest', () => {
|
||||
const expected = new IntegrationRequest(requestService.generateRequestId(), entriesEndpoint);
|
||||
scheduler.schedule(() => service.getEntriesByName(findOptions).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEntryByValue', () => {
|
||||
|
||||
it('should configure a new IntegrationRequest', () => {
|
||||
findOptions = new IntegrationSearchOptions(
|
||||
null,
|
||||
name,
|
||||
metadata,
|
||||
value);
|
||||
|
||||
const expected = new IntegrationRequest(requestService.generateRequestId(), entryValueEndpoint);
|
||||
scheduler.schedule(() => service.getEntryByValue(findOptions).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,121 +0,0 @@
|
||||
import { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs';
|
||||
import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { IntegrationSuccessResponse } from '../cache/response.models';
|
||||
import { GetRequest, IntegrationRequest } from '../data/request.models';
|
||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { IntegrationData } from './integration-data';
|
||||
import { IntegrationSearchOptions } from './models/integration-options.model';
|
||||
import { getResponseFromEntry } from '../shared/operators';
|
||||
|
||||
export abstract class IntegrationService {
|
||||
protected request: IntegrationRequest;
|
||||
protected abstract requestService: RequestService;
|
||||
protected abstract linkPath: string;
|
||||
protected abstract entriesEndpoint: string;
|
||||
protected abstract entryValueEndpoint: string;
|
||||
protected abstract halService: HALEndpointService;
|
||||
|
||||
protected getData(request: GetRequest): Observable<IntegrationData> {
|
||||
return this.requestService.getByHref(request.href).pipe(
|
||||
getResponseFromEntry(),
|
||||
mergeMap((response: IntegrationSuccessResponse) => {
|
||||
if (response.isSuccessful && isNotEmpty(response)) {
|
||||
return observableOf(new IntegrationData(
|
||||
response.pageInfo,
|
||||
(response.dataDefinition) ? response.dataDefinition.page : []
|
||||
));
|
||||
} else if (!response.isSuccessful) {
|
||||
return observableThrowError(new Error(`Couldn't retrieve the integration data`));
|
||||
}
|
||||
}),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
}
|
||||
|
||||
protected getEntriesHref(endpoint, options: IntegrationSearchOptions = new IntegrationSearchOptions()): string {
|
||||
let result;
|
||||
const args = [];
|
||||
|
||||
if (hasValue(options.name)) {
|
||||
result = `${endpoint}/${options.name}/${this.entriesEndpoint}`;
|
||||
} else {
|
||||
result = endpoint;
|
||||
}
|
||||
|
||||
if (hasValue(options.query)) {
|
||||
args.push(`query=${options.query}`);
|
||||
}
|
||||
|
||||
if (hasValue(options.metadata)) {
|
||||
args.push(`metadata=${options.metadata}`);
|
||||
}
|
||||
|
||||
if (hasValue(options.uuid)) {
|
||||
args.push(`uuid=${options.uuid}`);
|
||||
}
|
||||
|
||||
if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
|
||||
/* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */
|
||||
args.push(`page=${options.currentPage - 1}`);
|
||||
}
|
||||
|
||||
if (hasValue(options.elementsPerPage)) {
|
||||
args.push(`size=${options.elementsPerPage}`);
|
||||
}
|
||||
|
||||
if (hasValue(options.sort)) {
|
||||
args.push(`sort=${options.sort.field},${options.sort.direction}`);
|
||||
}
|
||||
|
||||
if (isNotEmpty(args)) {
|
||||
result = `${result}?${args.join('&')}`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected getEntryValueHref(endpoint, options: IntegrationSearchOptions = new IntegrationSearchOptions()): string {
|
||||
let result;
|
||||
const args = [];
|
||||
|
||||
if (hasValue(options.name) && hasValue(options.query)) {
|
||||
result = `${endpoint}/${options.name}/${this.entryValueEndpoint}/${options.query}`;
|
||||
} else {
|
||||
result = endpoint;
|
||||
}
|
||||
|
||||
if (hasValue(options.metadata)) {
|
||||
args.push(`metadata=${options.metadata}`);
|
||||
}
|
||||
|
||||
if (isNotEmpty(args)) {
|
||||
result = `${result}?${args.join('&')}`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public getEntriesByName(options: IntegrationSearchOptions): Observable<IntegrationData> {
|
||||
return this.halService.getEndpoint(this.linkPath).pipe(
|
||||
map((endpoint: string) => this.getEntriesHref(endpoint, options)),
|
||||
filter((href: string) => isNotEmpty(href)),
|
||||
distinctUntilChanged(),
|
||||
map((endpointURL: string) => new IntegrationRequest(this.requestService.generateRequestId(), endpointURL)),
|
||||
tap((request: GetRequest) => this.requestService.configure(request)),
|
||||
mergeMap((request: GetRequest) => this.getData(request)),
|
||||
distinctUntilChanged());
|
||||
}
|
||||
|
||||
public getEntryByValue(options: IntegrationSearchOptions): Observable<IntegrationData> {
|
||||
return this.halService.getEndpoint(this.linkPath).pipe(
|
||||
map((endpoint: string) => this.getEntryValueHref(endpoint, options)),
|
||||
filter((href: string) => isNotEmpty(href)),
|
||||
distinctUntilChanged(),
|
||||
map((endpointURL: string) => new IntegrationRequest(this.requestService.generateRequestId(), endpointURL)),
|
||||
tap((request: GetRequest) => this.requestService.configure(request)),
|
||||
mergeMap((request: GetRequest) => this.getData(request)),
|
||||
distinctUntilChanged());
|
||||
}
|
||||
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
export class AuthorityOptions {
|
||||
name: string;
|
||||
metadata: string;
|
||||
scope: string;
|
||||
closed: boolean;
|
||||
|
||||
constructor(name: string,
|
||||
metadata: string,
|
||||
scope: string,
|
||||
closed: boolean = false) {
|
||||
this.name = name;
|
||||
this.metadata = metadata;
|
||||
this.scope = scope;
|
||||
this.closed = closed;
|
||||
}
|
||||
}
|
@@ -1,10 +0,0 @@
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
/**
|
||||
* The resource type for AuthorityValue
|
||||
*
|
||||
* Needs to be in a separate file to prevent circular
|
||||
* dependencies in webpack.
|
||||
*/
|
||||
|
||||
export const AUTHORITY_VALUE = new ResourceType('authority');
|
@@ -1,92 +0,0 @@
|
||||
import { autoserialize, deserialize, inheritSerialization } from 'cerialize';
|
||||
import { isNotEmpty } from '../../../shared/empty.util';
|
||||
import { OtherInformation } from '../../../shared/form/builder/models/form-field-metadata-value.model';
|
||||
import { typedObject } from '../../cache/builders/build-decorators';
|
||||
import { HALLink } from '../../shared/hal-link.model';
|
||||
import { MetadataValueInterface } from '../../shared/metadata.models';
|
||||
import { AUTHORITY_VALUE } from './authority.resource-type';
|
||||
import { IntegrationModel } from './integration.model';
|
||||
import { PLACEHOLDER_PARENT_METADATA } from '../../../shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-constants';
|
||||
|
||||
/**
|
||||
* Class representing an authority object
|
||||
*/
|
||||
@typedObject
|
||||
@inheritSerialization(IntegrationModel)
|
||||
export class AuthorityValue extends IntegrationModel implements MetadataValueInterface {
|
||||
static type = AUTHORITY_VALUE;
|
||||
|
||||
/**
|
||||
* The identifier of this authority
|
||||
*/
|
||||
@autoserialize
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The display value of this authority
|
||||
*/
|
||||
@autoserialize
|
||||
display: string;
|
||||
|
||||
/**
|
||||
* The value of this authority
|
||||
*/
|
||||
@autoserialize
|
||||
value: string;
|
||||
|
||||
/**
|
||||
* An object containing additional information related to this authority
|
||||
*/
|
||||
@autoserialize
|
||||
otherInformation: OtherInformation;
|
||||
|
||||
/**
|
||||
* The language code of this authority value
|
||||
*/
|
||||
@autoserialize
|
||||
language: string;
|
||||
|
||||
/**
|
||||
* The {@link HALLink}s for this AuthorityValue
|
||||
*/
|
||||
@deserialize
|
||||
_links: {
|
||||
self: HALLink,
|
||||
};
|
||||
|
||||
/**
|
||||
* This method checks if authority has an identifier value
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
hasAuthority(): boolean {
|
||||
return isNotEmpty(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if authority has a value
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
hasValue(): boolean {
|
||||
return isNotEmpty(this.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if authority has related information object
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
hasOtherInformation(): boolean {
|
||||
return isNotEmpty(this.otherInformation);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if authority has a placeholder as value
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
hasPlaceholder(): boolean {
|
||||
return this.hasValue() && this.value === PLACEHOLDER_PARENT_METADATA;
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
import { SortOptions } from '../../cache/models/sort-options.model';
|
||||
|
||||
export class IntegrationSearchOptions {
|
||||
|
||||
constructor(public uuid: string = '',
|
||||
public name: string = '',
|
||||
public metadata: string = '',
|
||||
public query: string = '',
|
||||
public elementsPerPage?: number,
|
||||
public currentPage?: number,
|
||||
public sort?: SortOptions) {
|
||||
|
||||
}
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
import { autoserialize, deserialize } from 'cerialize';
|
||||
import { CacheableObject } from '../../cache/object-cache.reducer';
|
||||
import { HALLink } from '../../shared/hal-link.model';
|
||||
|
||||
export abstract class IntegrationModel implements CacheableObject {
|
||||
|
||||
@autoserialize
|
||||
self: string;
|
||||
|
||||
@autoserialize
|
||||
uuid: string;
|
||||
|
||||
@autoserialize
|
||||
public type: any;
|
||||
|
||||
@deserialize
|
||||
public _links: {
|
||||
self: HALLink,
|
||||
[name: string]: HALLink
|
||||
}
|
||||
|
||||
}
|
@@ -1,11 +1,16 @@
|
||||
import { Store } from '@ngrx/store';
|
||||
import { CoreState } from '../../core.reducers';
|
||||
import { NewPatchAddOperationAction, NewPatchMoveOperationAction, NewPatchRemoveOperationAction, NewPatchReplaceOperationAction } from '../json-patch-operations.actions';
|
||||
import {
|
||||
NewPatchAddOperationAction,
|
||||
NewPatchMoveOperationAction,
|
||||
NewPatchRemoveOperationAction,
|
||||
NewPatchReplaceOperationAction
|
||||
} from '../json-patch-operations.actions';
|
||||
import { JsonPatchOperationPathObject } from './json-patch-operation-path-combiner';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { hasNoValue, isEmpty, isNotEmpty } from '../../../shared/empty.util';
|
||||
import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../../shared/empty.util';
|
||||
import { dateToISOFormat } from '../../../shared/date.util';
|
||||
import { AuthorityValue } from '../../integration/models/authority.value';
|
||||
import { VocabularyEntry } from '../../submission/vocabularies/models/vocabulary-entry.model';
|
||||
import { FormFieldMetadataValueObject } from '../../../shared/form/builder/models/form-field-metadata-value.model';
|
||||
import { FormFieldLanguageValueObject } from '../../../shared/form/builder/models/form-field-language-value.model';
|
||||
|
||||
@@ -96,7 +101,7 @@ export class JsonPatchOperationsBuilder {
|
||||
|
||||
protected prepareValue(value: any, plain: boolean, first: boolean) {
|
||||
let operationValue: any = null;
|
||||
if (isNotEmpty(value)) {
|
||||
if (hasValue(value)) {
|
||||
if (plain) {
|
||||
operationValue = value;
|
||||
} else {
|
||||
@@ -125,10 +130,12 @@ export class JsonPatchOperationsBuilder {
|
||||
operationValue = value;
|
||||
} else if (value instanceof Date) {
|
||||
operationValue = new FormFieldMetadataValueObject(dateToISOFormat(value));
|
||||
} else if (value instanceof AuthorityValue) {
|
||||
} else if (value instanceof VocabularyEntry) {
|
||||
operationValue = this.prepareAuthorityValue(value);
|
||||
} else if (value instanceof FormFieldLanguageValueObject) {
|
||||
operationValue = new FormFieldMetadataValueObject(value.value, value.language);
|
||||
} else if (value.hasOwnProperty('authority')) {
|
||||
operationValue = new FormFieldMetadataValueObject(value.value, value.language, value.authority);
|
||||
} else if (value.hasOwnProperty('value')) {
|
||||
operationValue = new FormFieldMetadataValueObject(value.value);
|
||||
} else {
|
||||
@@ -144,10 +151,10 @@ export class JsonPatchOperationsBuilder {
|
||||
return operationValue;
|
||||
}
|
||||
|
||||
protected prepareAuthorityValue(value: any) {
|
||||
let operationValue: any = null;
|
||||
if (isNotEmpty(value.id)) {
|
||||
operationValue = new FormFieldMetadataValueObject(value.value, value.language, value.id);
|
||||
protected prepareAuthorityValue(value: any): FormFieldMetadataValueObject {
|
||||
let operationValue: FormFieldMetadataValueObject;
|
||||
if (isNotEmpty(value.authority)) {
|
||||
operationValue = new FormFieldMetadataValueObject(value.value, value.language, value.authority);
|
||||
} else {
|
||||
operationValue = new FormFieldMetadataValueObject(value.value, value.language);
|
||||
}
|
||||
|
@@ -1,9 +1,8 @@
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { getTestScheduler } from 'jasmine-marbles';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { Store, StoreModule } from '@ngrx/store';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
||||
import { RequestService } from '../data/request.service';
|
||||
@@ -22,7 +21,6 @@ import {
|
||||
StartTransactionPatchOperationsAction
|
||||
} from './json-patch-operations.actions';
|
||||
import { RequestEntry } from '../data/request.reducer';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
class TestService extends JsonPatchOperationsService<SubmitDataResponseDefinitionObject, SubmissionPatchRequest> {
|
||||
protected linkPath = '';
|
||||
|
@@ -19,7 +19,7 @@ export enum LANG_ORIGIN {
|
||||
UI,
|
||||
EPERSON,
|
||||
BROWSER
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Service to provide localization handler
|
||||
@@ -75,8 +75,9 @@ export class LocaleService {
|
||||
return obs$.pipe(
|
||||
take(1),
|
||||
flatMap(([isAuthenticated, isLoaded]) => {
|
||||
let epersonLang$: Observable<string[]> = observableOf([]);
|
||||
if (isAuthenticated && isLoaded) {
|
||||
// TODO to enabled again when https://github.com/DSpace/dspace-angular/issues/739 will be resolved
|
||||
const epersonLang$: Observable<string[]> = observableOf([]);
|
||||
/* if (isAuthenticated && isLoaded) {
|
||||
epersonLang$ = this.authService.getAuthenticatedUserFromStore().pipe(
|
||||
take(1),
|
||||
map((eperson) => {
|
||||
@@ -91,7 +92,7 @@ export class LocaleService {
|
||||
return languages;
|
||||
})
|
||||
);
|
||||
}
|
||||
}*/
|
||||
return epersonLang$.pipe(
|
||||
map((epersonLang: string[]) => {
|
||||
const languages: string[] = [];
|
||||
|
@@ -1,8 +1,6 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { SubmissionService } from '../../submission/submission.service';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { SubmissionObject } from './models/submission-object.model';
|
||||
import { WorkspaceItem } from './models/workspaceitem.model';
|
||||
import { SubmissionObjectDataService } from './submission-object-data.service';
|
||||
import { SubmissionScopeType } from './submission-scope-type';
|
||||
import { WorkflowItemDataService } from './workflowitem-data.service';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { deepClone } from 'fast-json-patch';
|
||||
import { DSOResponseParsingService } from '../data/dso-response-parsing.service';
|
||||
|
||||
@@ -113,7 +113,7 @@ export class SubmissionResponseParsingService extends BaseResponseParsingService
|
||||
return new ErrorResponse(
|
||||
Object.assign(
|
||||
new Error('Unexpected response from server'),
|
||||
{statusCode: data.statusCode, statusText: data.statusText}
|
||||
{ statusCode: data.statusCode, statusText: data.statusText }
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -133,7 +133,7 @@ export class SubmissionResponseParsingService extends BaseResponseParsingService
|
||||
|
||||
processedList.forEach((item) => {
|
||||
|
||||
item = Object.assign({}, item);
|
||||
// item = Object.assign({}, item);
|
||||
// In case data is an Instance of WorkspaceItem normalize field value of all the section of type form
|
||||
if (item instanceof WorkspaceItem
|
||||
|| item instanceof WorkflowItem) {
|
||||
|
@@ -0,0 +1,12 @@
|
||||
import { ResourceType } from '../../../shared/resource-type';
|
||||
|
||||
/**
|
||||
* The resource type for vocabulary models
|
||||
*
|
||||
* Needs to be in a separate file to prevent circular
|
||||
* dependencies in webpack.
|
||||
*/
|
||||
|
||||
export const VOCABULARY = new ResourceType('vocabulary');
|
||||
export const VOCABULARY_ENTRY = new ResourceType('vocabularyEntry');
|
||||
export const VOCABULARY_ENTRY_DETAIL = new ResourceType('vocabularyEntryDetail');
|
@@ -0,0 +1,39 @@
|
||||
import { autoserialize, deserialize, inheritSerialization } from 'cerialize';
|
||||
|
||||
import { HALLink } from '../../../shared/hal-link.model';
|
||||
import { VOCABULARY_ENTRY_DETAIL } from './vocabularies.resource-type';
|
||||
import { typedObject } from '../../../cache/builders/build-decorators';
|
||||
import { VocabularyEntry } from './vocabulary-entry.model';
|
||||
|
||||
/**
|
||||
* Model class for a VocabularyEntryDetail
|
||||
*/
|
||||
@typedObject
|
||||
@inheritSerialization(VocabularyEntry)
|
||||
export class VocabularyEntryDetail extends VocabularyEntry {
|
||||
static type = VOCABULARY_ENTRY_DETAIL;
|
||||
|
||||
/**
|
||||
* The unique id of the entry
|
||||
*/
|
||||
@autoserialize
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* In an hierarchical vocabulary representing if entry is selectable as value
|
||||
*/
|
||||
@autoserialize
|
||||
selectable: boolean;
|
||||
|
||||
/**
|
||||
* The {@link HALLink}s for this ExternalSourceEntry
|
||||
*/
|
||||
@deserialize
|
||||
_links: {
|
||||
self: HALLink;
|
||||
vocabulary: HALLink;
|
||||
parent: HALLink;
|
||||
children
|
||||
};
|
||||
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
import { autoserialize, deserialize } from 'cerialize';
|
||||
|
||||
import { HALLink } from '../../../shared/hal-link.model';
|
||||
import { VOCABULARY_ENTRY } from './vocabularies.resource-type';
|
||||
import { typedObject } from '../../../cache/builders/build-decorators';
|
||||
import { excludeFromEquals } from '../../../utilities/equals.decorators';
|
||||
import { PLACEHOLDER_PARENT_METADATA } from '../../../../shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-constants';
|
||||
import { OtherInformation } from '../../../../shared/form/builder/models/form-field-metadata-value.model';
|
||||
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||
import { ListableObject } from '../../../../shared/object-collection/shared/listable-object.model';
|
||||
import { GenericConstructor } from '../../../shared/generic-constructor';
|
||||
|
||||
/**
|
||||
* Model class for a VocabularyEntry
|
||||
*/
|
||||
@typedObject
|
||||
export class VocabularyEntry extends ListableObject {
|
||||
static type = VOCABULARY_ENTRY;
|
||||
|
||||
/**
|
||||
* The identifier of this vocabulary entry
|
||||
*/
|
||||
@autoserialize
|
||||
authority: string;
|
||||
|
||||
/**
|
||||
* The display value of this vocabulary entry
|
||||
*/
|
||||
@autoserialize
|
||||
display: string;
|
||||
|
||||
/**
|
||||
* The value of this vocabulary entry
|
||||
*/
|
||||
@autoserialize
|
||||
value: string;
|
||||
|
||||
/**
|
||||
* An object containing additional information related to this vocabulary entry
|
||||
*/
|
||||
@autoserialize
|
||||
otherInformation: OtherInformation;
|
||||
|
||||
/**
|
||||
* A string representing the kind of vocabulary entry
|
||||
*/
|
||||
@excludeFromEquals
|
||||
@autoserialize
|
||||
public type: any;
|
||||
|
||||
/**
|
||||
* The {@link HALLink}s for this ExternalSourceEntry
|
||||
*/
|
||||
@deserialize
|
||||
_links: {
|
||||
self: HALLink;
|
||||
vocabularyEntryDetail?: HALLink;
|
||||
};
|
||||
|
||||
/**
|
||||
* This method checks if entry has an authority value
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
hasAuthority(): boolean {
|
||||
return isNotEmpty(this.authority);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if entry has a value
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
hasValue(): boolean {
|
||||
return isNotEmpty(this.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if entry has related information object
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
hasOtherInformation(): boolean {
|
||||
return isNotEmpty(this.otherInformation);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if entry has a placeholder as value
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
hasPlaceholder(): boolean {
|
||||
return this.hasValue() && this.value === PLACEHOLDER_PARENT_METADATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that returns as which type of object this object should be rendered
|
||||
*/
|
||||
getRenderTypes(): Array<string | GenericConstructor<ListableObject>> {
|
||||
return [this.constructor as GenericConstructor<ListableObject>];
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
import { SortOptions } from '../../../cache/models/sort-options.model';
|
||||
import { FindListOptions } from '../../../data/request.models';
|
||||
import { RequestParam } from '../../../cache/models/request-param.model';
|
||||
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||
|
||||
/**
|
||||
* Representing properties used to build a vocabulary find request
|
||||
*/
|
||||
export class VocabularyFindOptions extends FindListOptions {
|
||||
|
||||
constructor(public query: string = '',
|
||||
public filter?: string,
|
||||
public exact?: boolean,
|
||||
public entryID?: string,
|
||||
public elementsPerPage?: number,
|
||||
public currentPage?: number,
|
||||
public sort?: SortOptions
|
||||
) {
|
||||
super();
|
||||
|
||||
const searchParams = [];
|
||||
|
||||
if (isNotEmpty(query)) {
|
||||
searchParams.push(new RequestParam('query', query))
|
||||
}
|
||||
if (isNotEmpty(filter)) {
|
||||
searchParams.push(new RequestParam('filter', filter))
|
||||
}
|
||||
if (isNotEmpty(exact)) {
|
||||
searchParams.push(new RequestParam('exact', exact.toString()))
|
||||
}
|
||||
if (isNotEmpty(entryID)) {
|
||||
searchParams.push(new RequestParam('entryID', entryID))
|
||||
}
|
||||
this.searchParams = searchParams;
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Representing vocabulary properties
|
||||
*/
|
||||
export class VocabularyOptions {
|
||||
|
||||
/**
|
||||
* The name of the vocabulary
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* A boolean representing if value is closely related to a vocabulary entry or not
|
||||
*/
|
||||
closed: boolean;
|
||||
|
||||
constructor(name: string,
|
||||
closed: boolean = false) {
|
||||
this.name = name;
|
||||
this.closed = closed;
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
import { autoserialize, deserialize } from 'cerialize';
|
||||
|
||||
import { HALLink } from '../../../shared/hal-link.model';
|
||||
import { VOCABULARY } from './vocabularies.resource-type';
|
||||
import { CacheableObject } from '../../../cache/object-cache.reducer';
|
||||
import { typedObject } from '../../../cache/builders/build-decorators';
|
||||
import { excludeFromEquals } from '../../../utilities/equals.decorators';
|
||||
|
||||
/**
|
||||
* Model class for a Vocabulary
|
||||
*/
|
||||
@typedObject
|
||||
export class Vocabulary implements CacheableObject {
|
||||
static type = VOCABULARY;
|
||||
/**
|
||||
* The identifier of this Vocabulary
|
||||
*/
|
||||
@autoserialize
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The name of this Vocabulary
|
||||
*/
|
||||
@autoserialize
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* True if it is possible to scroll all the entries in the vocabulary without providing a filter parameter
|
||||
*/
|
||||
@autoserialize
|
||||
scrollable: boolean;
|
||||
|
||||
/**
|
||||
* True if the vocabulary exposes a tree structure where some entries are parent of others
|
||||
*/
|
||||
@autoserialize
|
||||
hierarchical: boolean;
|
||||
|
||||
/**
|
||||
* For hierarchical vocabularies express the preference to preload the tree at a specific
|
||||
* level of depth (0 only the top nodes are shown, 1 also their children are preloaded and so on)
|
||||
*/
|
||||
@autoserialize
|
||||
preloadLevel: any;
|
||||
|
||||
/**
|
||||
* A string representing the kind of Vocabulary model
|
||||
*/
|
||||
@excludeFromEquals
|
||||
@autoserialize
|
||||
public type: any;
|
||||
|
||||
/**
|
||||
* The {@link HALLink}s for this Vocabulary
|
||||
*/
|
||||
@deserialize
|
||||
_links: {
|
||||
self: HALLink,
|
||||
entries: HALLink
|
||||
};
|
||||
}
|
@@ -0,0 +1,111 @@
|
||||
import { getMockObjectCacheService } from '../../../shared/mocks/object-cache.service.mock';
|
||||
import { ErrorResponse, GenericSuccessResponse } from '../../cache/response.models';
|
||||
import { DSpaceRESTV2Response } from '../../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||
import { VocabularyEntriesResponseParsingService } from './vocabulary-entries-response-parsing.service';
|
||||
import { VocabularyEntriesRequest } from '../../data/request.models';
|
||||
|
||||
describe('VocabularyEntriesResponseParsingService', () => {
|
||||
let service: VocabularyEntriesResponseParsingService;
|
||||
const metadata = 'dc.type';
|
||||
const collectionUUID = '8b39g7ya-5a4b-438b-851f-be1d5b4a1c5a';
|
||||
const entriesRequestURL = `https://rest.api/rest/api/submission/vocabularies/types/entries?metadata=${metadata}&collection=${collectionUUID}`
|
||||
|
||||
beforeEach(() => {
|
||||
service = new VocabularyEntriesResponseParsingService(getMockObjectCacheService());
|
||||
});
|
||||
|
||||
describe('parse', () => {
|
||||
const request = new VocabularyEntriesRequest('client/f5b4ccb8-fbb0-4548-b558-f234d9fdfad6', entriesRequestURL);
|
||||
|
||||
const validResponse = {
|
||||
payload: {
|
||||
_embedded: {
|
||||
entries: [
|
||||
{
|
||||
display: 'testValue1',
|
||||
value: 'testValue1',
|
||||
otherInformation: {},
|
||||
type: 'vocabularyEntry'
|
||||
},
|
||||
{
|
||||
display: 'testValue2',
|
||||
value: 'testValue2',
|
||||
otherInformation: {},
|
||||
type: 'vocabularyEntry'
|
||||
},
|
||||
{
|
||||
display: 'testValue3',
|
||||
value: 'testValue3',
|
||||
otherInformation: {},
|
||||
type: 'vocabularyEntry'
|
||||
},
|
||||
{
|
||||
authority: 'authorityId1',
|
||||
display: 'testValue1',
|
||||
value: 'testValue1',
|
||||
otherInformation: {
|
||||
id: 'VR131402',
|
||||
parent: 'Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work',
|
||||
hasChildren: 'false',
|
||||
note: 'Familjeforskning'
|
||||
},
|
||||
type: 'vocabularyEntry',
|
||||
_links: {
|
||||
vocabularyEntryDetail: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularyEntryDetails/srsc:VR131402'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
_links: {
|
||||
first: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularies/types/entries/first?page=0&size=5'
|
||||
},
|
||||
self: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularies/types/entries'
|
||||
},
|
||||
next: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularies/types/entries/next?page=1&size=5'
|
||||
},
|
||||
last: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularies/types/entries/last?page=9&size=5'
|
||||
}
|
||||
},
|
||||
page: {
|
||||
size: 5,
|
||||
totalElements: 50,
|
||||
totalPages: 10,
|
||||
number: 0
|
||||
}
|
||||
},
|
||||
statusCode: 200,
|
||||
statusText: 'OK'
|
||||
} as DSpaceRESTV2Response;
|
||||
|
||||
const invalidResponseNotAList = {
|
||||
statusCode: 200,
|
||||
statusText: 'OK'
|
||||
} as DSpaceRESTV2Response;
|
||||
|
||||
const invalidResponseStatusCode = {
|
||||
payload: {}, statusCode: 500, statusText: 'Internal Server Error'
|
||||
} as DSpaceRESTV2Response;
|
||||
|
||||
it('should return a GenericSuccessResponse if data contains a valid browse entries response', () => {
|
||||
const response = service.parse(request, validResponse);
|
||||
expect(response.constructor).toBe(GenericSuccessResponse);
|
||||
});
|
||||
|
||||
it('should return an ErrorResponse if data contains an invalid browse entries response', () => {
|
||||
const response = service.parse(request, invalidResponseNotAList);
|
||||
expect(response.constructor).toBe(ErrorResponse);
|
||||
});
|
||||
|
||||
it('should return an ErrorResponse if data contains a statuscode other than 200', () => {
|
||||
const response = service.parse(request, invalidResponseStatusCode);
|
||||
expect(response.constructor).toBe(ErrorResponse);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@@ -0,0 +1,26 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { ObjectCacheService } from '../../cache/object-cache.service';
|
||||
import { VocabularyEntry } from './models/vocabulary-entry.model';
|
||||
import { EntriesResponseParsingService } from '../../data/entries-response-parsing.service';
|
||||
import { GenericConstructor } from '../../shared/generic-constructor';
|
||||
|
||||
/**
|
||||
* A service responsible for parsing data for a vocabulary entries response
|
||||
*/
|
||||
@Injectable()
|
||||
export class VocabularyEntriesResponseParsingService extends EntriesResponseParsingService<VocabularyEntry> {
|
||||
|
||||
protected toCache = false;
|
||||
|
||||
constructor(
|
||||
protected objectCache: ObjectCacheService,
|
||||
) {
|
||||
super(objectCache);
|
||||
}
|
||||
|
||||
getSerializerModel(): GenericConstructor<VocabularyEntry> {
|
||||
return VocabularyEntry;
|
||||
}
|
||||
|
||||
}
|
569
src/app/core/submission/vocabularies/vocabulary.service.spec.ts
Normal file
569
src/app/core/submission/vocabularies/vocabulary.service.spec.ts
Normal file
@@ -0,0 +1,569 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
||||
import { ObjectCacheService } from '../../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../../shared/hal-endpoint.service';
|
||||
import { RequestService } from '../../data/request.service';
|
||||
import { VocabularyEntriesRequest } from '../../data/request.models';
|
||||
import { RequestParam } from '../../cache/models/request-param.model';
|
||||
import { PageInfo } from '../../shared/page-info.model';
|
||||
import { PaginatedList } from '../../data/paginated-list';
|
||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||
import { RequestEntry } from '../../data/request.reducer';
|
||||
import { RestResponse } from '../../cache/response.models';
|
||||
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;
|
||||
let service: VocabularyService;
|
||||
let requestService: RequestService;
|
||||
let rdbService: RemoteDataBuildService;
|
||||
let objectCache: ObjectCacheService;
|
||||
let halService: HALEndpointService;
|
||||
let responseCacheEntry: RequestEntry;
|
||||
|
||||
const vocabulary: any = {
|
||||
id: 'types',
|
||||
name: 'types',
|
||||
scrollable: true,
|
||||
hierarchical: false,
|
||||
preloadLevel: 1,
|
||||
type: 'vocabulary',
|
||||
uuid: 'vocabulary-types',
|
||||
_links: {
|
||||
self: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularies/types'
|
||||
},
|
||||
entries: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularies/types/entries'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const hierarchicalVocabulary: any = {
|
||||
id: 'srsc',
|
||||
name: 'srsc',
|
||||
scrollable: false,
|
||||
hierarchical: true,
|
||||
preloadLevel: 2,
|
||||
type: 'vocabulary',
|
||||
uuid: 'vocabulary-srsc',
|
||||
_links: {
|
||||
self: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularies/types'
|
||||
},
|
||||
entries: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularies/types/entries'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const vocabularyEntry: any = {
|
||||
display: 'testValue1',
|
||||
value: 'testValue1',
|
||||
otherInformation: {},
|
||||
type: 'vocabularyEntry'
|
||||
};
|
||||
|
||||
const vocabularyEntry2: any = {
|
||||
display: 'testValue2',
|
||||
value: 'testValue2',
|
||||
otherInformation: {},
|
||||
type: 'vocabularyEntry'
|
||||
};
|
||||
|
||||
const vocabularyEntry3: any = {
|
||||
display: 'testValue3',
|
||||
value: 'testValue3',
|
||||
otherInformation: {},
|
||||
type: 'vocabularyEntry'
|
||||
};
|
||||
|
||||
const vocabularyEntryParentDetail: any = {
|
||||
authority: 'authorityId2',
|
||||
display: 'testParent',
|
||||
value: 'testParent',
|
||||
otherInformation: {
|
||||
id: 'authorityId2',
|
||||
hasChildren: 'true',
|
||||
note: 'Familjeforskning'
|
||||
},
|
||||
type: 'vocabularyEntryDetail',
|
||||
_links: {
|
||||
self: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularyEntryDetails/srsc:VR131402'
|
||||
},
|
||||
parent: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularyEntryDetails/srsc:parent'
|
||||
},
|
||||
children: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularyEntryDetails/srsc:children'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const vocabularyEntryChildDetail: any = {
|
||||
authority: 'authoritytestChild1',
|
||||
display: 'testChild1',
|
||||
value: 'testChild1',
|
||||
otherInformation: {
|
||||
id: 'authoritytestChild1',
|
||||
hasChildren: 'true',
|
||||
note: 'Familjeforskning'
|
||||
},
|
||||
type: 'vocabularyEntryDetail',
|
||||
_links: {
|
||||
self: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularyEntryDetails/srsc:authoritytestChild1'
|
||||
},
|
||||
parent: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularyEntryDetails/srsc:parent'
|
||||
},
|
||||
children: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularyEntryDetails/srsc:children'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const vocabularyEntryChild2Detail: any = {
|
||||
authority: 'authoritytestChild2',
|
||||
display: 'testChild2',
|
||||
value: 'testChild2',
|
||||
otherInformation: {
|
||||
id: 'authoritytestChild2',
|
||||
hasChildren: 'true',
|
||||
note: 'Familjeforskning'
|
||||
},
|
||||
type: 'vocabularyEntryDetail',
|
||||
_links: {
|
||||
self: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularyEntryDetails/srsc:authoritytestChild2'
|
||||
},
|
||||
parent: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularyEntryDetails/srsc:parent'
|
||||
},
|
||||
children: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularyEntryDetails/srsc:children'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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/${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';
|
||||
const collectionUUID = '8b39g7ya-5a4b-438b-851f-be1d5b4a1c5a';
|
||||
const entryID = 'dsfsfsdf-5a4b-438b-851f-be1d5b4a1c5a';
|
||||
const searchRequestURL = `https://rest.api/rest/api/submission/vocabularies/search/byMetadataAndCollection?metadata=${metadata}&collection=${collectionUUID}`;
|
||||
const entriesRequestURL = `https://rest.api/rest/api/submission/vocabularies/${vocabulary.id}/entries`;
|
||||
const entriesByValueRequestURL = `https://rest.api/rest/api/submission/vocabularies/${vocabulary.id}/entries?filter=test&exact=false`;
|
||||
const entryByValueRequestURL = `https://rest.api/rest/api/submission/vocabularies/${vocabulary.id}/entries?filter=test&exact=true`;
|
||||
const entryByIDRequestURL = `https://rest.api/rest/api/submission/vocabularies/${vocabulary.id}/entries?entryID=${entryID}`;
|
||||
const vocabularyOptions: VocabularyOptions = {
|
||||
name: vocabularyId,
|
||||
closed: false
|
||||
}
|
||||
const pageInfo = new PageInfo();
|
||||
const array = [vocabulary, hierarchicalVocabulary];
|
||||
const arrayEntries = [vocabularyEntry, vocabularyEntry2, vocabularyEntry3];
|
||||
const childrenEntries = [vocabularyEntryChildDetail, vocabularyEntryChild2Detail];
|
||||
const paginatedList = new PaginatedList(pageInfo, array);
|
||||
const paginatedListEntries = new PaginatedList(pageInfo, arrayEntries);
|
||||
const childrenPaginatedList = new PaginatedList(pageInfo, childrenEntries);
|
||||
const vocabularyRD = createSuccessfulRemoteDataObject(vocabulary);
|
||||
const vocabularyRD$ = createSuccessfulRemoteDataObject$(vocabulary);
|
||||
const vocabularyEntriesRD = createSuccessfulRemoteDataObject$(paginatedListEntries);
|
||||
const vocabularyEntryDetailParentRD = createSuccessfulRemoteDataObject(vocabularyEntryParentDetail);
|
||||
const vocabularyEntryChildrenRD = createSuccessfulRemoteDataObject(childrenPaginatedList);
|
||||
const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
|
||||
const getRequestEntries$ = (successful: boolean) => {
|
||||
return observableOf({
|
||||
response: { isSuccessful: successful, payload: arrayEntries } as any
|
||||
} as RequestEntry)
|
||||
};
|
||||
objectCache = {} as ObjectCacheService;
|
||||
const notificationsService = {} as NotificationsService;
|
||||
const http = {} as HttpClient;
|
||||
const comparator = {} as any;
|
||||
const comparatorEntry = {} as any;
|
||||
|
||||
function initTestService() {
|
||||
return new VocabularyService(
|
||||
requestService,
|
||||
rdbService,
|
||||
objectCache,
|
||||
halService,
|
||||
notificationsService,
|
||||
http,
|
||||
comparator,
|
||||
comparatorEntry
|
||||
);
|
||||
}
|
||||
|
||||
describe('vocabularies endpoint', () => {
|
||||
beforeEach(() => {
|
||||
scheduler = getTestScheduler();
|
||||
|
||||
halService = jasmine.createSpyObj('halService', {
|
||||
getEndpoint: cold('a', { a: endpointURL })
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
service = null;
|
||||
});
|
||||
|
||||
describe('', () => {
|
||||
beforeEach(() => {
|
||||
responseCacheEntry = new RequestEntry();
|
||||
responseCacheEntry.request = { href: 'https://rest.api/' } as any;
|
||||
responseCacheEntry.completed = true;
|
||||
responseCacheEntry.response = new RestResponse(true, 200, 'Success');
|
||||
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
generateRequestId: requestUUID,
|
||||
configure: true,
|
||||
removeByHrefSubstring: {},
|
||||
getByHref: observableOf(responseCacheEntry),
|
||||
getByUUID: observableOf(responseCacheEntry),
|
||||
});
|
||||
rdbService = jasmine.createSpyObj('rdbService', {
|
||||
buildSingle: hot('a|', {
|
||||
a: vocabularyRD
|
||||
}),
|
||||
buildList: hot('a|', {
|
||||
a: paginatedListRD
|
||||
}),
|
||||
});
|
||||
|
||||
service = initTestService();
|
||||
|
||||
spyOn((service as any).vocabularyDataService, 'findById').and.callThrough();
|
||||
spyOn((service as any).vocabularyDataService, 'findAll').and.callThrough();
|
||||
spyOn((service as any).vocabularyDataService, 'findByHref').and.callThrough();
|
||||
spyOn((service as any).vocabularyDataService, 'searchBy').and.callThrough();
|
||||
spyOn((service as any).vocabularyDataService, 'getSearchByHref').and.returnValue(observableOf(searchRequestURL));
|
||||
spyOn((service as any).vocabularyDataService, 'getFindAllHref').and.returnValue(observableOf(entriesRequestURL));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
service = null;
|
||||
});
|
||||
|
||||
describe('findVocabularyById', () => {
|
||||
it('should proxy the call to vocabularyDataService.findVocabularyById', () => {
|
||||
scheduler.schedule(() => service.findVocabularyById(vocabularyId));
|
||||
scheduler.flush();
|
||||
|
||||
expect((service as any).vocabularyDataService.findById).toHaveBeenCalledWith(vocabularyId);
|
||||
});
|
||||
|
||||
it('should return a RemoteData<Vocabulary> for the object with the given id', () => {
|
||||
const result = service.findVocabularyById(vocabularyId);
|
||||
const expected = cold('a|', {
|
||||
a: vocabularyRD
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findVocabularyByHref', () => {
|
||||
it('should proxy the call to vocabularyDataService.findVocabularyByHref', () => {
|
||||
scheduler.schedule(() => service.findVocabularyByHref(requestURL));
|
||||
scheduler.flush();
|
||||
|
||||
expect((service as any).vocabularyDataService.findByHref).toHaveBeenCalledWith(requestURL);
|
||||
});
|
||||
|
||||
it('should return a RemoteData<Vocabulary> for the object with the given URL', () => {
|
||||
const result = service.findVocabularyByHref(requestURL);
|
||||
const expected = cold('a|', {
|
||||
a: vocabularyRD
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAllVocabularies', () => {
|
||||
it('should proxy the call to vocabularyDataService.findAllVocabularies', () => {
|
||||
scheduler.schedule(() => service.findAllVocabularies());
|
||||
scheduler.flush();
|
||||
|
||||
expect((service as any).vocabularyDataService.findAll).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return a RemoteData<PaginatedList<Vocabulary>>', () => {
|
||||
const result = service.findAllVocabularies();
|
||||
const expected = cold('a|', {
|
||||
a: paginatedListRD
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
requestService = getMockRequestService(getRequestEntries$(true));
|
||||
rdbService = getMockRemoteDataBuildService(undefined, vocabularyEntriesRD);
|
||||
spyOn(rdbService, 'toRemoteDataObservable').and.callThrough();
|
||||
service = initTestService();
|
||||
spyOn(service, 'findVocabularyById').and.returnValue(vocabularyRD$);
|
||||
});
|
||||
|
||||
describe('getVocabularyEntries', () => {
|
||||
|
||||
it('should configure a new VocabularyEntriesRequest', () => {
|
||||
const expected = new VocabularyEntriesRequest(requestService.generateRequestId(), entriesRequestURL);
|
||||
|
||||
scheduler.schedule(() => service.getVocabularyEntries(vocabularyOptions, pageInfo).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
});
|
||||
|
||||
it('should call RemoteDataBuildService to create the RemoteData Observable', () => {
|
||||
scheduler.schedule(() => service.getVocabularyEntries(vocabularyOptions, pageInfo));
|
||||
scheduler.flush();
|
||||
|
||||
expect(rdbService.toRemoteDataObservable).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getVocabularyEntriesByValue', () => {
|
||||
|
||||
it('should configure a new VocabularyEntriesRequest', () => {
|
||||
const expected = new VocabularyEntriesRequest(requestService.generateRequestId(), entriesByValueRequestURL);
|
||||
|
||||
scheduler.schedule(() => service.getVocabularyEntriesByValue('test', false, vocabularyOptions, pageInfo).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
});
|
||||
|
||||
it('should call RemoteDataBuildService to create the RemoteData Observable', () => {
|
||||
scheduler.schedule(() => service.getVocabularyEntriesByValue('test', false, vocabularyOptions, pageInfo));
|
||||
scheduler.flush();
|
||||
|
||||
expect(rdbService.toRemoteDataObservable).toHaveBeenCalled();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('getVocabularyEntryByValue', () => {
|
||||
|
||||
it('should configure a new VocabularyEntriesRequest', () => {
|
||||
const expected = new VocabularyEntriesRequest(requestService.generateRequestId(), entryByValueRequestURL);
|
||||
|
||||
scheduler.schedule(() => service.getVocabularyEntryByValue('test', vocabularyOptions).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
});
|
||||
|
||||
it('should call RemoteDataBuildService to create the RemoteData Observable', () => {
|
||||
scheduler.schedule(() => service.getVocabularyEntryByValue('test', vocabularyOptions));
|
||||
scheduler.flush();
|
||||
|
||||
expect(rdbService.toRemoteDataObservable).toHaveBeenCalled();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('getVocabularyEntryByID', () => {
|
||||
it('should configure a new VocabularyEntriesRequest', () => {
|
||||
const expected = new VocabularyEntriesRequest(requestService.generateRequestId(), entryByIDRequestURL);
|
||||
|
||||
scheduler.schedule(() => service.getVocabularyEntryByID(entryID, vocabularyOptions).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
});
|
||||
|
||||
it('should call RemoteDataBuildService to create the RemoteData Observable', () => {
|
||||
scheduler.schedule(() => service.getVocabularyEntryByID('test', vocabularyOptions));
|
||||
scheduler.flush();
|
||||
|
||||
expect(rdbService.toRemoteDataObservable).toHaveBeenCalled();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('vocabularyEntryDetails endpoint', () => {
|
||||
beforeEach(() => {
|
||||
scheduler = getTestScheduler();
|
||||
|
||||
halService = jasmine.createSpyObj('halService', {
|
||||
getEndpoint: cold('a', { a: entryDetailEndpointURL })
|
||||
});
|
||||
|
||||
responseCacheEntry = new RequestEntry();
|
||||
responseCacheEntry.request = { href: 'https://rest.api/' } as any;
|
||||
responseCacheEntry.completed = true;
|
||||
responseCacheEntry.response = new RestResponse(true, 200, 'Success');
|
||||
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
generateRequestId: requestUUID,
|
||||
configure: true,
|
||||
removeByHrefSubstring: {},
|
||||
getByHref: observableOf(responseCacheEntry),
|
||||
getByUUID: observableOf(responseCacheEntry),
|
||||
});
|
||||
rdbService = jasmine.createSpyObj('rdbService', {
|
||||
buildSingle: hot('a|', {
|
||||
a: vocabularyEntryDetailParentRD
|
||||
}),
|
||||
buildList: hot('a|', {
|
||||
a: vocabularyEntryChildrenRD
|
||||
}),
|
||||
});
|
||||
|
||||
service = initTestService();
|
||||
|
||||
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(entryDetailChildrenRequestURL));
|
||||
spyOn((service as any).vocabularyEntryDetailDataService, 'getBrowseEndpoint').and.returnValue(observableOf(entryDetailEndpointURL));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
service = null;
|
||||
});
|
||||
|
||||
describe('findEntryDetailByHref', () => {
|
||||
it('should proxy the call to vocabularyDataService.findEntryDetailByHref', () => {
|
||||
scheduler.schedule(() => service.findEntryDetailByHref(entryDetailRequestURL));
|
||||
scheduler.flush();
|
||||
|
||||
expect((service as any).vocabularyEntryDetailDataService.findByHref).toHaveBeenCalledWith(entryDetailRequestURL);
|
||||
});
|
||||
|
||||
it('should return a RemoteData<VocabularyEntryDetail> for the object with the given URL', () => {
|
||||
const result = service.findEntryDetailByHref(entryDetailRequestURL);
|
||||
const expected = cold('a|', {
|
||||
a: vocabularyEntryDetailParentRD
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findEntryDetailById', () => {
|
||||
it('should proxy the call to vocabularyDataService.findVocabularyById', () => {
|
||||
scheduler.schedule(() => service.findEntryDetailById('testValue', hierarchicalVocabulary.id));
|
||||
scheduler.flush();
|
||||
const expectedId = `${hierarchicalVocabulary.id}:testValue`
|
||||
expect((service as any).vocabularyEntryDetailDataService.findById).toHaveBeenCalledWith(expectedId);
|
||||
});
|
||||
|
||||
it('should return a RemoteData<VocabularyEntryDetail> for the object with the given id', () => {
|
||||
const result = service.findEntryDetailById('testValue', hierarchicalVocabulary.id);
|
||||
const expected = cold('a|', {
|
||||
a: vocabularyEntryDetailParentRD
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEntryDetailParent', () => {
|
||||
it('should proxy the call to vocabularyDataService.getEntryDetailParent', () => {
|
||||
scheduler.schedule(() => service.getEntryDetailParent('testValue', hierarchicalVocabulary.id).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect((service as any).vocabularyEntryDetailDataService.findByHref).toHaveBeenCalledWith(entryDetailParentRequestURL);
|
||||
});
|
||||
|
||||
it('should return a RemoteData<VocabularyEntryDetail> for the object with the given URL', () => {
|
||||
const result = service.getEntryDetailParent('testValue', hierarchicalVocabulary.id);
|
||||
const expected = cold('a|', {
|
||||
a: vocabularyEntryDetailParentRD
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEntryDetailChildren', () => {
|
||||
it('should proxy the call to vocabularyDataService.getEntryDetailChildren', () => {
|
||||
const options: VocabularyFindOptions = new VocabularyFindOptions(
|
||||
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, options);
|
||||
});
|
||||
|
||||
it('should return a RemoteData<PaginatedList<ResourcePolicy>> for the object with the given URL', () => {
|
||||
const result = service.getEntryDetailChildren('testValue', hierarchicalVocabulary.id, new PageInfo());
|
||||
const expected = cold('a|', {
|
||||
a: vocabularyEntryChildrenRD
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchByTop', () => {
|
||||
it('should proxy the call to vocabularyEntryDetailDataService.searchBy', () => {
|
||||
const options: VocabularyFindOptions = new VocabularyFindOptions(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
pageInfo.elementsPerPage,
|
||||
pageInfo.currentPage
|
||||
);
|
||||
options.searchParams = [new RequestParam('vocabulary', 'srsc')];
|
||||
scheduler.schedule(() => service.searchTopEntries('srsc', pageInfo));
|
||||
scheduler.flush();
|
||||
|
||||
expect((service as any).vocabularyEntryDetailDataService.searchBy).toHaveBeenCalledWith((service as any).searchTopMethod, options);
|
||||
});
|
||||
|
||||
it('should return a RemoteData<PaginatedList<ResourcePolicy>> for the search', () => {
|
||||
const result = service.searchTopEntries('srsc', pageInfo);
|
||||
const expected = cold('a|', {
|
||||
a: vocabularyEntryChildrenRD
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
389
src/app/core/submission/vocabularies/vocabulary.service.ts
Normal file
389
src/app/core/submission/vocabularies/vocabulary.service.ts
Normal file
@@ -0,0 +1,389 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, first, flatMap, map } from 'rxjs/operators';
|
||||
|
||||
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
|
||||
import { dataService } from '../../cache/builders/build-decorators';
|
||||
import { DataService } from '../../data/data.service';
|
||||
import { RequestService } from '../../data/request.service';
|
||||
import { FindListOptions, RestRequest, VocabularyEntriesRequest } from '../../data/request.models';
|
||||
import { HALEndpointService } from '../../shared/hal-endpoint.service';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
||||
import { CoreState } from '../../core.reducers';
|
||||
import { ObjectCacheService } from '../../cache/object-cache.service';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { ChangeAnalyzer } from '../../data/change-analyzer';
|
||||
import { DefaultChangeAnalyzer } from '../../data/default-change-analyzer.service';
|
||||
import { PaginatedList } from '../../data/paginated-list';
|
||||
import { Vocabulary } from './models/vocabulary.model';
|
||||
import { VOCABULARY } from './models/vocabularies.resource-type';
|
||||
import { VocabularyEntry } from './models/vocabulary-entry.model';
|
||||
import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util';
|
||||
import {
|
||||
configureRequest,
|
||||
filterSuccessfulResponses,
|
||||
getFirstSucceededRemoteDataPayload,
|
||||
getFirstSucceededRemoteListPayload,
|
||||
getRequestFromRequestHref
|
||||
} from '../../shared/operators';
|
||||
import { GenericSuccessResponse } from '../../cache/response.models';
|
||||
import { VocabularyFindOptions } from './models/vocabulary-find-options.model';
|
||||
import { VocabularyEntryDetail } from './models/vocabulary-entry-detail.model';
|
||||
import { RequestParam } from '../../cache/models/request-param.model';
|
||||
import { VocabularyOptions } from './models/vocabulary-options.model';
|
||||
import { PageInfo } from '../../shared/page-info.model';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* A private DataService implementation to delegate specific methods to.
|
||||
*/
|
||||
class VocabularyDataServiceImpl extends DataService<Vocabulary> {
|
||||
protected linkPath = 'vocabularies';
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected store: Store<CoreState>,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: ChangeAnalyzer<Vocabulary>) {
|
||||
super();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A private DataService implementation to delegate specific methods to.
|
||||
*/
|
||||
class VocabularyEntryDetailDataServiceImpl extends DataService<VocabularyEntryDetail> {
|
||||
protected linkPath = 'vocabularyEntryDetails';
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected store: Store<CoreState>,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: ChangeAnalyzer<VocabularyEntryDetail>) {
|
||||
super();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A service responsible for fetching/sending data from/to the REST API on the vocabularies endpoint
|
||||
*/
|
||||
@Injectable()
|
||||
@dataService(VOCABULARY)
|
||||
export class VocabularyService {
|
||||
protected searchByMetadataAndCollectionMethod = 'byMetadataAndCollection';
|
||||
protected searchTopMethod = 'top';
|
||||
private vocabularyDataService: VocabularyDataServiceImpl;
|
||||
private vocabularyEntryDetailDataService: VocabularyEntryDetailDataServiceImpl;
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparatorVocabulary: DefaultChangeAnalyzer<Vocabulary>,
|
||||
protected comparatorEntry: DefaultChangeAnalyzer<VocabularyEntryDetail>) {
|
||||
this.vocabularyDataService = new VocabularyDataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparatorVocabulary);
|
||||
this.vocabularyEntryDetailDataService = new VocabularyEntryDetailDataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparatorEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable of {@link RemoteData} of a {@link Vocabulary}, based on an href, with a list of {@link FollowLinkConfig},
|
||||
* to automatically resolve {@link HALLink}s of the {@link Vocabulary}
|
||||
* @param href The url of {@link Vocabulary} we want to retrieve
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<Vocabulary>>}
|
||||
* Return an observable that emits vocabulary object
|
||||
*/
|
||||
findVocabularyByHref(href: string, ...linksToFollow: Array<FollowLinkConfig<Vocabulary>>): Observable<RemoteData<any>> {
|
||||
return this.vocabularyDataService.findByHref(href, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable of {@link RemoteData} of a {@link Vocabulary}, based on its ID, with a list of {@link FollowLinkConfig},
|
||||
* to automatically resolve {@link HALLink}s of the object
|
||||
* @param name The name of {@link Vocabulary} we want to retrieve
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<Vocabulary>>}
|
||||
* Return an observable that emits vocabulary object
|
||||
*/
|
||||
findVocabularyById(name: string, ...linksToFollow: Array<FollowLinkConfig<Vocabulary>>): Observable<RemoteData<Vocabulary>> {
|
||||
return this.vocabularyDataService.findById(name, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link RemoteData} of all object with a list of {@link FollowLinkConfig}, to indicate which embedded
|
||||
* info should be added to the objects
|
||||
*
|
||||
* @param options Find list options object
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<PaginatedList<Vocabulary>>>}
|
||||
* Return an observable that emits object list
|
||||
*/
|
||||
findAllVocabularies(options: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<Vocabulary>>): Observable<RemoteData<PaginatedList<Vocabulary>>> {
|
||||
return this.vocabularyDataService.findAll(options, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link VocabularyEntry} list for a given {@link Vocabulary}
|
||||
*
|
||||
* @param vocabularyOptions The {@link VocabularyOptions} for the request to which the entries belong
|
||||
* @param pageInfo The {@link PageInfo} for the request
|
||||
* @return {Observable<RemoteData<PaginatedList<VocabularyEntry>>>}
|
||||
* Return an observable that emits object list
|
||||
*/
|
||||
getVocabularyEntries(vocabularyOptions: VocabularyOptions, pageInfo: PageInfo): Observable<RemoteData<PaginatedList<VocabularyEntry>>> {
|
||||
|
||||
const options: VocabularyFindOptions = new VocabularyFindOptions(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
pageInfo.elementsPerPage,
|
||||
pageInfo.currentPage
|
||||
);
|
||||
|
||||
return this.findVocabularyById(vocabularyOptions.name).pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
map((vocabulary: Vocabulary) => this.vocabularyDataService.buildHrefFromFindOptions(vocabulary._links.entries.href, options)),
|
||||
isNotEmptyOperator(),
|
||||
distinctUntilChanged(),
|
||||
getVocabularyEntriesFor(this.requestService, this.rdbService)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link VocabularyEntry} list for a given value
|
||||
*
|
||||
* @param value The entry value to retrieve
|
||||
* @param exact If true force the vocabulary to provide only entries that match exactly with the value
|
||||
* @param vocabularyOptions The {@link VocabularyOptions} for the request to which the entries belong
|
||||
* @param pageInfo The {@link PageInfo} for the request
|
||||
* @return {Observable<RemoteData<PaginatedList<VocabularyEntry>>>}
|
||||
* Return an observable that emits object list
|
||||
*/
|
||||
getVocabularyEntriesByValue(value: string, exact: boolean, vocabularyOptions: VocabularyOptions, pageInfo: PageInfo): Observable<RemoteData<PaginatedList<VocabularyEntry>>> {
|
||||
const options: VocabularyFindOptions = new VocabularyFindOptions(
|
||||
null,
|
||||
value,
|
||||
exact,
|
||||
null,
|
||||
pageInfo.elementsPerPage,
|
||||
pageInfo.currentPage
|
||||
);
|
||||
|
||||
return this.findVocabularyById(vocabularyOptions.name).pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
map((vocabulary: Vocabulary) => this.vocabularyDataService.buildHrefFromFindOptions(vocabulary._links.entries.href, options)),
|
||||
isNotEmptyOperator(),
|
||||
distinctUntilChanged(),
|
||||
getVocabularyEntriesFor(this.requestService, this.rdbService)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link VocabularyEntry} list for a given value
|
||||
*
|
||||
* @param value The entry value to retrieve
|
||||
* @param vocabularyOptions The {@link VocabularyOptions} for the request to which the entry belongs
|
||||
* @return {Observable<RemoteData<PaginatedList<VocabularyEntry>>>}
|
||||
* Return an observable that emits {@link VocabularyEntry} object
|
||||
*/
|
||||
getVocabularyEntryByValue(value: string, vocabularyOptions: VocabularyOptions): Observable<VocabularyEntry> {
|
||||
|
||||
return this.getVocabularyEntriesByValue(value, true, vocabularyOptions, new PageInfo()).pipe(
|
||||
getFirstSucceededRemoteListPayload(),
|
||||
map((list: VocabularyEntry[]) => {
|
||||
if (isNotEmpty(list)) {
|
||||
return list[0]
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link VocabularyEntry} list for a given ID
|
||||
*
|
||||
* @param ID The entry ID to retrieve
|
||||
* @param vocabularyOptions The {@link VocabularyOptions} for the request to which the entry belongs
|
||||
* @return {Observable<RemoteData<PaginatedList<VocabularyEntry>>>}
|
||||
* Return an observable that emits {@link VocabularyEntry} object
|
||||
*/
|
||||
getVocabularyEntryByID(ID: string, vocabularyOptions: VocabularyOptions): Observable<VocabularyEntry> {
|
||||
const pageInfo = new PageInfo()
|
||||
const options: VocabularyFindOptions = new VocabularyFindOptions(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
ID,
|
||||
pageInfo.elementsPerPage,
|
||||
pageInfo.currentPage
|
||||
);
|
||||
|
||||
return this.findVocabularyById(vocabularyOptions.name).pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
map((vocabulary: Vocabulary) => this.vocabularyDataService.buildHrefFromFindOptions(vocabulary._links.entries.href, options)),
|
||||
isNotEmptyOperator(),
|
||||
distinctUntilChanged(),
|
||||
getVocabularyEntriesFor(this.requestService, this.rdbService),
|
||||
getFirstSucceededRemoteListPayload(),
|
||||
map((list: VocabularyEntry[]) => {
|
||||
if (isNotEmpty(list)) {
|
||||
return list[0]
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable of {@link RemoteData} of a {@link VocabularyEntryDetail}, based on an href, with a list of {@link FollowLinkConfig},
|
||||
* to automatically resolve {@link HALLink}s of the {@link VocabularyEntryDetail}
|
||||
* @param href The url of {@link VocabularyEntryDetail} we want to retrieve
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<VocabularyEntryDetail>>}
|
||||
* Return an observable that emits vocabulary object
|
||||
*/
|
||||
findEntryDetailByHref(href: string, ...linksToFollow: Array<FollowLinkConfig<VocabularyEntryDetail>>): Observable<RemoteData<VocabularyEntryDetail>> {
|
||||
return this.vocabularyEntryDetailDataService.findByHref(href, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable of {@link RemoteData} of a {@link VocabularyEntryDetail}, based on its ID, with a list of {@link FollowLinkConfig},
|
||||
* to automatically resolve {@link HALLink}s of the object
|
||||
* @param id The entry id for which to provide detailed information.
|
||||
* @param name The name of {@link Vocabulary} to which the entry belongs
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<VocabularyEntryDetail>>}
|
||||
* Return an observable that emits VocabularyEntryDetail object
|
||||
*/
|
||||
findEntryDetailById(id: string, name: string, ...linksToFollow: Array<FollowLinkConfig<VocabularyEntryDetail>>): Observable<RemoteData<VocabularyEntryDetail>> {
|
||||
const findId = `${name}:${id}`;
|
||||
return this.vocabularyEntryDetailDataService.findById(findId, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent detail entry for a given detail entry, with a list of {@link FollowLinkConfig},
|
||||
* to automatically resolve {@link HALLink}s of the object
|
||||
* @param value The entry value for which to provide parent.
|
||||
* @param name The name of {@link Vocabulary} to which the entry belongs
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<PaginatedList<VocabularyEntryDetail>>>}
|
||||
* Return an observable that emits a PaginatedList of VocabularyEntryDetail
|
||||
*/
|
||||
getEntryDetailParent(value: string, name: string, ...linksToFollow: Array<FollowLinkConfig<VocabularyEntryDetail>>): Observable<RemoteData<VocabularyEntryDetail>> {
|
||||
const linkPath = `${name}:${value}/parent`;
|
||||
|
||||
return this.vocabularyEntryDetailDataService.getBrowseEndpoint().pipe(
|
||||
map((href: string) => `${href}/${linkPath}`),
|
||||
flatMap((href) => this.vocabularyEntryDetailDataService.findByHref(href, ...linksToFollow))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of children detail entries for a given detail entry, with a list of {@link FollowLinkConfig},
|
||||
* to automatically resolve {@link HALLink}s of the object
|
||||
* @param value The entry value for which to provide children list.
|
||||
* @param name The name of {@link Vocabulary} to which the entry belongs
|
||||
* @param pageInfo The {@link PageInfo} for the request
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<PaginatedList<VocabularyEntryDetail>>>}
|
||||
* Return an observable that emits a PaginatedList of VocabularyEntryDetail
|
||||
*/
|
||||
getEntryDetailChildren(value: string, name: string, pageInfo: PageInfo, ...linksToFollow: Array<FollowLinkConfig<VocabularyEntryDetail>>): Observable<RemoteData<PaginatedList<VocabularyEntryDetail>>> {
|
||||
const linkPath = `${name}:${value}/children`;
|
||||
const options: VocabularyFindOptions = new VocabularyFindOptions(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
pageInfo.elementsPerPage,
|
||||
pageInfo.currentPage
|
||||
);
|
||||
return this.vocabularyEntryDetailDataService.getFindAllHref(options, linkPath).pipe(
|
||||
flatMap((href) => this.vocabularyEntryDetailDataService.findAllByHref(href, options, ...linksToFollow))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the top level {@link VocabularyEntryDetail} list for a given hierarchical vocabulary
|
||||
*
|
||||
* @param name The name of hierarchical {@link Vocabulary} to which the entries belongs
|
||||
* @param pageInfo The {@link PageInfo} for the request
|
||||
* @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: VocabularyFindOptions = new VocabularyFindOptions(
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Operator for turning a href into a PaginatedList of VocabularyEntry
|
||||
* @param requestService
|
||||
* @param rdb
|
||||
*/
|
||||
export const getVocabularyEntriesFor = (requestService: RequestService, rdb: RemoteDataBuildService) =>
|
||||
(source: Observable<string>): Observable<RemoteData<PaginatedList<VocabularyEntry>>> =>
|
||||
source.pipe(
|
||||
map((href: string) => new VocabularyEntriesRequest(requestService.generateRequestId(), href)),
|
||||
configureRequest(requestService),
|
||||
toRDPaginatedVocabularyEntries(requestService, rdb)
|
||||
);
|
||||
|
||||
/**
|
||||
* Operator for turning a RestRequest into a PaginatedList of VocabularyEntry
|
||||
* @param requestService
|
||||
* @param rdb
|
||||
*/
|
||||
export const toRDPaginatedVocabularyEntries = (requestService: RequestService, rdb: RemoteDataBuildService) =>
|
||||
(source: Observable<RestRequest>): Observable<RemoteData<PaginatedList<VocabularyEntry>>> => {
|
||||
const href$ = source.pipe(map((request: RestRequest) => request.href));
|
||||
|
||||
const requestEntry$ = href$.pipe(getRequestFromRequestHref(requestService));
|
||||
|
||||
const payload$ = requestEntry$.pipe(
|
||||
filterSuccessfulResponses(),
|
||||
map((response: GenericSuccessResponse<VocabularyEntry[]>) => new PaginatedList(response.pageInfo, response.payload)),
|
||||
map((list: PaginatedList<VocabularyEntry>) => Object.assign(list, {
|
||||
page: list.page ? list.page.map((entry: VocabularyEntry) => Object.assign(new VocabularyEntry(), entry)) : list.page
|
||||
})),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
|
||||
return rdb.toRemoteDataObservable(requestEntry$, payload$);
|
||||
};
|
@@ -9,7 +9,7 @@ import { DataService } from '../data/data.service';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { WorkflowItem } from './models/workflowitem.model';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { DeleteByIDRequest, FindListOptions } from '../data/request.models';
|
||||
import { DeleteByIDRequest } from '../data/request.models';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
|
||||
|
@@ -8,7 +8,6 @@ import { CoreState } from '../core.reducers';
|
||||
import { DataService } from '../data/data.service';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { FindListOptions } from '../data/request.models';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
|
||||
|
@@ -3,7 +3,7 @@ import { ExternalSourceEntry } from '../../../../../core/shared/external-source-
|
||||
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||
import { Context } from '../../../../../core/shared/context.model';
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Metadata } from '../../../../../core/shared/metadata.utils';
|
||||
import { MetadataValue } from '../../../../../core/shared/metadata.models';
|
||||
|
||||
|
@@ -13,12 +13,13 @@ import {
|
||||
|
||||
import { findIndex } from 'lodash';
|
||||
|
||||
import { AuthorityValue } from '../../core/integration/models/authority.value';
|
||||
import { VocabularyEntry } from '../../core/submission/vocabularies/models/vocabulary-entry.model';
|
||||
import { FormFieldMetadataValueObject } from '../form/builder/models/form-field-metadata-value.model';
|
||||
import { ConfidenceType } from '../../core/integration/models/confidence-type';
|
||||
import { ConfidenceType } from '../../core/shared/confidence-type';
|
||||
import { isNotEmpty, isNull } from '../empty.util';
|
||||
import { ConfidenceIconConfig } from '../../../config/submission-config.interface';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
||||
|
||||
/**
|
||||
* Directive to add to the element a bootstrap utility class based on metadata confidence value
|
||||
@@ -31,7 +32,7 @@ export class AuthorityConfidenceStateDirective implements OnChanges, AfterViewIn
|
||||
/**
|
||||
* The metadata value
|
||||
*/
|
||||
@Input() authorityValue: AuthorityValue | FormFieldMetadataValueObject | string;
|
||||
@Input() authorityValue: VocabularyEntry | FormFieldMetadataValueObject | string;
|
||||
|
||||
/**
|
||||
* A boolean representing if to show html icon if authority value is empty
|
||||
@@ -65,7 +66,6 @@ export class AuthorityConfidenceStateDirective implements OnChanges, AfterViewIn
|
||||
/**
|
||||
* Initialize instance variables
|
||||
*
|
||||
* @param {GlobalConfig} EnvConfig
|
||||
* @param {ElementRef} elem
|
||||
* @param {Renderer2} renderer
|
||||
*/
|
||||
@@ -114,7 +114,8 @@ export class AuthorityConfidenceStateDirective implements OnChanges, AfterViewIn
|
||||
private getConfidenceByValue(value: any): ConfidenceType {
|
||||
let confidence: ConfidenceType = ConfidenceType.CF_UNSET;
|
||||
|
||||
if (isNotEmpty(value) && value instanceof AuthorityValue && value.hasAuthority()) {
|
||||
if (isNotEmpty(value) && (value instanceof VocabularyEntry || value instanceof VocabularyEntryDetail)
|
||||
&& value.hasAuthority()) {
|
||||
confidence = ConfidenceType.CF_ACCEPTED;
|
||||
}
|
||||
|
||||
|
@@ -11,7 +11,7 @@ import { FormFieldMetadataValueObject } from '../form/builder/models/form-field-
|
||||
import { createTestComponent } from '../testing/utils.test';
|
||||
import { AuthorityConfidenceStateDirective } from '../authority-confidence/authority-confidence-state.directive';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ConfidenceType } from '../../core/integration/models/confidence-type';
|
||||
import { ConfidenceType } from '../../core/shared/confidence-type';
|
||||
import { SortablejsModule } from 'ngx-sortablejs';
|
||||
import { environment } from '../../../environments/environment';
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { isObject, uniqueId } from 'lodash';
|
||||
import { hasValue, isNotEmpty } from '../../empty.util';
|
||||
import { FormFieldMetadataValueObject } from '../../form/builder/models/form-field-metadata-value.model';
|
||||
import { ConfidenceType } from '../../../core/integration/models/confidence-type';
|
||||
import { ConfidenceType } from '../../../core/shared/confidence-type';
|
||||
import { PLACEHOLDER_PARENT_METADATA } from '../../form/builder/ds-dynamic-form-ui/ds-dynamic-form-constants';
|
||||
|
||||
export interface ChipsItemIcon {
|
||||
|
@@ -4,7 +4,7 @@ import { ChipsItem, ChipsItemIcon } from './chips-item.model';
|
||||
import { hasValue, isNotEmpty } from '../../empty.util';
|
||||
import { MetadataIconConfig } from '../../../../config/submission-config.interface';
|
||||
import { FormFieldMetadataValueObject } from '../../form/builder/models/form-field-metadata-value.model';
|
||||
import { AuthorityValue } from '../../../core/integration/models/authority.value';
|
||||
import { VocabularyEntry } from '../../../core/submission/vocabularies/models/vocabulary-entry.model';
|
||||
import { PLACEHOLDER_PARENT_METADATA } from '../../form/builder/ds-dynamic-form-ui/ds-dynamic-form-constants';
|
||||
|
||||
export class Chips {
|
||||
@@ -102,7 +102,7 @@ export class Chips {
|
||||
|
||||
private getChipsIcons(item) {
|
||||
const icons = [];
|
||||
if (typeof item === 'string' || item instanceof FormFieldMetadataValueObject || item instanceof AuthorityValue) {
|
||||
if (typeof item === 'string' || item instanceof FormFieldMetadataValueObject || item instanceof VocabularyEntry) {
|
||||
return icons;
|
||||
}
|
||||
|
||||
|
@@ -44,15 +44,15 @@ import { SharedModule } from '../../../shared.module';
|
||||
import { DynamicDsDatePickerModel } from './models/date-picker/date-picker.model';
|
||||
import { DynamicRelationGroupModel } from './models/relation-group/dynamic-relation-group.model';
|
||||
import { DynamicListCheckboxGroupModel } from './models/list/dynamic-list-checkbox-group.model';
|
||||
import { AuthorityOptions } from '../../../../core/integration/models/authority-options.model';
|
||||
import { VocabularyOptions } from '../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||
import { DynamicListRadioGroupModel } from './models/list/dynamic-list-radio-group.model';
|
||||
import { DynamicLookupModel } from './models/lookup/dynamic-lookup.model';
|
||||
import { DynamicScrollableDropdownModel } from './models/scrollable-dropdown/dynamic-scrollable-dropdown.model';
|
||||
import { DynamicTagModel } from './models/tag/dynamic-tag.model';
|
||||
import { DynamicTypeaheadModel } from './models/typeahead/dynamic-typeahead.model';
|
||||
import { DynamicOneboxModel } from './models/onebox/dynamic-onebox.model';
|
||||
import { DynamicQualdropModel } from './models/ds-dynamic-qualdrop.model';
|
||||
import { DynamicLookupNameModel } from './models/lookup/dynamic-lookup-name.model';
|
||||
import { DsDynamicTypeaheadComponent } from './models/typeahead/dynamic-typeahead.component';
|
||||
import { DsDynamicOneboxComponent } from './models/onebox/dynamic-onebox.component';
|
||||
import { DsDynamicScrollableDropdownComponent } from './models/scrollable-dropdown/dynamic-scrollable-dropdown.component';
|
||||
import { DsDynamicTagComponent } from './models/tag/dynamic-tag.component';
|
||||
import { DsDynamicListComponent } from './models/list/dynamic-list.component';
|
||||
@@ -77,11 +77,9 @@ import { FormBuilderService } from '../form-builder.service';
|
||||
|
||||
describe('DsDynamicFormControlContainerComponent test suite', () => {
|
||||
|
||||
const authorityOptions: AuthorityOptions = {
|
||||
closed: false,
|
||||
metadata: 'list',
|
||||
const vocabularyOptions: VocabularyOptions = {
|
||||
name: 'type_programme',
|
||||
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
|
||||
closed: false
|
||||
};
|
||||
const formModel = [
|
||||
new DynamicCheckboxModel({ id: 'checkbox' }),
|
||||
@@ -104,10 +102,10 @@ describe('DsDynamicFormControlContainerComponent test suite', () => {
|
||||
new DynamicSwitchModel({ id: 'switch' }),
|
||||
new DynamicTextAreaModel({ id: 'textarea' }),
|
||||
new DynamicTimePickerModel({ id: 'timepicker' }),
|
||||
new DynamicTypeaheadModel({ id: 'typeahead', metadataFields: [], repeatable: false, submissionId: '1234', hasSelectableMetadata: false }),
|
||||
new DynamicOneboxModel({ id: 'typeahead', metadataFields: [], repeatable: false, submissionId: '1234', hasSelectableMetadata: false }),
|
||||
new DynamicScrollableDropdownModel({
|
||||
id: 'scrollableDropdown',
|
||||
authorityOptions: authorityOptions,
|
||||
vocabularyOptions: vocabularyOptions,
|
||||
metadataFields: [],
|
||||
repeatable: false,
|
||||
submissionId: '1234',
|
||||
@@ -116,12 +114,12 @@ describe('DsDynamicFormControlContainerComponent test suite', () => {
|
||||
new DynamicTagModel({ id: 'tag', metadataFields: [], repeatable: false, submissionId: '1234', hasSelectableMetadata: false }),
|
||||
new DynamicListCheckboxGroupModel({
|
||||
id: 'checkboxList',
|
||||
authorityOptions: authorityOptions,
|
||||
vocabularyOptions: vocabularyOptions,
|
||||
repeatable: true
|
||||
}),
|
||||
new DynamicListRadioGroupModel({
|
||||
id: 'radioList',
|
||||
authorityOptions: authorityOptions,
|
||||
vocabularyOptions: vocabularyOptions,
|
||||
repeatable: false
|
||||
}),
|
||||
new DynamicRelationGroupModel({
|
||||
@@ -319,7 +317,7 @@ describe('DsDynamicFormControlContainerComponent test suite', () => {
|
||||
expect(testFn(formModel[13])).toBeNull();
|
||||
expect(testFn(formModel[14])).toEqual(DynamicNGBootstrapTextAreaComponent);
|
||||
expect(testFn(formModel[15])).toEqual(DynamicNGBootstrapTimePickerComponent);
|
||||
expect(testFn(formModel[16])).toEqual(DsDynamicTypeaheadComponent);
|
||||
expect(testFn(formModel[16])).toEqual(DsDynamicOneboxComponent);
|
||||
expect(testFn(formModel[17])).toEqual(DsDynamicScrollableDropdownComponent);
|
||||
expect(testFn(formModel[18])).toEqual(DsDynamicTagComponent);
|
||||
expect(testFn(formModel[19])).toEqual(DsDynamicListComponent);
|
||||
|
@@ -58,7 +58,7 @@ import {
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ReorderableRelationship } from './existing-metadata-list-element/existing-metadata-list-element.component';
|
||||
|
||||
import { DYNAMIC_FORM_CONTROL_TYPE_TYPEAHEAD } from './models/typeahead/dynamic-typeahead.model';
|
||||
import { DYNAMIC_FORM_CONTROL_TYPE_ONEBOX } from './models/onebox/dynamic-onebox.model';
|
||||
import { DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN } from './models/scrollable-dropdown/dynamic-scrollable-dropdown.model';
|
||||
import { DYNAMIC_FORM_CONTROL_TYPE_TAG } from './models/tag/dynamic-tag.model';
|
||||
import { DYNAMIC_FORM_CONTROL_TYPE_DSDATEPICKER } from './models/date-picker/date-picker.model';
|
||||
@@ -70,7 +70,7 @@ import { DYNAMIC_FORM_CONTROL_TYPE_LOOKUP_NAME } from './models/lookup/dynamic-l
|
||||
import { DsDynamicTagComponent } from './models/tag/dynamic-tag.component';
|
||||
import { DsDatePickerComponent } from './models/date-picker/date-picker.component';
|
||||
import { DsDynamicListComponent } from './models/list/dynamic-list.component';
|
||||
import { DsDynamicTypeaheadComponent } from './models/typeahead/dynamic-typeahead.component';
|
||||
import { DsDynamicOneboxComponent } from './models/onebox/dynamic-onebox.component';
|
||||
import { DsDynamicScrollableDropdownComponent } from './models/scrollable-dropdown/dynamic-scrollable-dropdown.component';
|
||||
import { DsDynamicLookupComponent } from './models/lookup/dynamic-lookup.component';
|
||||
import { DsDynamicFormGroupComponent } from './models/form-group/dynamic-form-group.component';
|
||||
@@ -89,7 +89,13 @@ import { SelectableListService } from '../../../object-list/selectable-list/sele
|
||||
import { DsDynamicDisabledComponent } from './models/disabled/dynamic-disabled.component';
|
||||
import { DYNAMIC_FORM_CONTROL_TYPE_DISABLED } from './models/disabled/dynamic-disabled.model';
|
||||
import { DsDynamicLookupRelationModalComponent } from './relation-lookup-modal/dynamic-lookup-relation-modal.component';
|
||||
import { getAllSucceededRemoteData, getFirstSucceededRemoteDataPayload, getPaginatedListPayload, getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||
import {
|
||||
getAllSucceededRemoteData,
|
||||
getFirstSucceededRemoteDataPayload,
|
||||
getPaginatedListPayload,
|
||||
getRemoteDataPayload,
|
||||
getSucceededRemoteData
|
||||
} from '../../../../core/shared/operators';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||
@@ -110,6 +116,7 @@ import { paginatedRelationsToItems } from '../../../../+item-page/simple/item-ty
|
||||
import { RelationshipOptions } from '../models/relationship-options.model';
|
||||
import { FormBuilderService } from '../form-builder.service';
|
||||
import { DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP } from './ds-dynamic-form-constants';
|
||||
import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model';
|
||||
|
||||
export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type<DynamicFormControl> | null {
|
||||
switch (model.type) {
|
||||
@@ -145,8 +152,8 @@ export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type<
|
||||
case DYNAMIC_FORM_CONTROL_TYPE_TIMEPICKER:
|
||||
return DynamicNGBootstrapTimePickerComponent;
|
||||
|
||||
case DYNAMIC_FORM_CONTROL_TYPE_TYPEAHEAD:
|
||||
return DsDynamicTypeaheadComponent;
|
||||
case DYNAMIC_FORM_CONTROL_TYPE_ONEBOX:
|
||||
return DsDynamicOneboxComponent;
|
||||
|
||||
case DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN:
|
||||
return DsDynamicScrollableDropdownComponent;
|
||||
@@ -297,9 +304,9 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
||||
}
|
||||
|
||||
if (hasValue(this.model.metadataValue)) {
|
||||
this.value = Object.assign(new MetadataValue(), this.model.metadataValue);
|
||||
this.value = Object.assign(new FormFieldMetadataValueObject(), this.model.metadataValue);
|
||||
} else {
|
||||
this.value = Object.assign(new MetadataValue(), this.model.value);
|
||||
this.value = Object.assign(new FormFieldMetadataValueObject(), this.model.value);
|
||||
}
|
||||
|
||||
if (hasValue(this.value) && this.value.isVirtual) {
|
||||
|
@@ -1,9 +1,12 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ExistingMetadataListElementComponent, Reorderable, ReorderableRelationship } from './existing-metadata-list-element.component';
|
||||
import {
|
||||
ExistingMetadataListElementComponent,
|
||||
ReorderableRelationship
|
||||
} from './existing-metadata-list-element.component';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { SelectableListService } from '../../../../object-list/selectable-list/selectable-list.service';
|
||||
import { select, Store } from '@ngrx/store';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
import { Relationship } from '../../../../../core/shared/item-relationships/relationship.model';
|
||||
import { RelationshipOptions } from '../../models/relationship-options.model';
|
||||
@@ -11,7 +14,6 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils
|
||||
import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions';
|
||||
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { RelationshipService } from '../../../../../core/data/relationship.service';
|
||||
|
||||
describe('ExistingMetadataListElementComponent', () => {
|
||||
let component: ExistingMetadataListElementComponent;
|
||||
|
@@ -3,7 +3,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ExistingRelationListElementComponent } from './existing-relation-list-element.component';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { SelectableListService } from '../../../../object-list/selectable-list/selectable-list.service';
|
||||
import { select, Store } from '@ngrx/store';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
import { Relationship } from '../../../../../core/shared/item-relationships/relationship.model';
|
||||
import { RelationshipOptions } from '../../models/relationship-options.model';
|
||||
|
@@ -8,10 +8,6 @@ import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dyna
|
||||
|
||||
import { DsDatePickerComponent } from './date-picker.component';
|
||||
import { DynamicDsDatePickerModel } from './date-picker.model';
|
||||
import { FormBuilderService } from '../../../form-builder.service';
|
||||
|
||||
import { FormComponent } from '../../../../form.component';
|
||||
import { FormService } from '../../../../form.service';
|
||||
import { createTestComponent } from '../../../../../testing/utils.test';
|
||||
|
||||
export const DATE_TEST_GROUP = new FormGroup({
|
||||
@@ -20,7 +16,7 @@ export const DATE_TEST_GROUP = new FormGroup({
|
||||
|
||||
export const DATE_TEST_MODEL_CONFIG = {
|
||||
disabled: false,
|
||||
errorMessages: {required: 'You must enter at least the year.'},
|
||||
errorMessages: { required: 'You must enter at least the year.' },
|
||||
id: 'date',
|
||||
label: 'Date',
|
||||
name: 'date',
|
||||
@@ -52,8 +48,8 @@ describe('DsDatePickerComponent test suite', () => {
|
||||
providers: [
|
||||
ChangeDetectorRef,
|
||||
DsDatePickerComponent,
|
||||
{provide: DynamicFormLayoutService, useValue: {}},
|
||||
{provide: DynamicFormValidationService, useValue: {}}
|
||||
{ provide: DynamicFormLayoutService, useValue: {} },
|
||||
{ provide: DynamicFormValidationService, useValue: {} }
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
});
|
||||
|
@@ -20,10 +20,6 @@ export class DsDatePickerComponent extends DynamicFormControlComponent implement
|
||||
@Input() bindId = true;
|
||||
@Input() group: FormGroup;
|
||||
@Input() model: DynamicDsDatePickerModel;
|
||||
// @Input()
|
||||
// minDate;
|
||||
// @Input()
|
||||
// maxDate;
|
||||
|
||||
@Output() selected = new EventEmitter<number>();
|
||||
@Output() remove = new EventEmitter<number>();
|
||||
@@ -65,7 +61,7 @@ export class DsDatePickerComponent extends DynamicFormControlComponent implement
|
||||
this.initialMonth = now.getMonth() + 1;
|
||||
this.initialDay = now.getDate();
|
||||
|
||||
if (this.model.value && this.model.value !== null) {
|
||||
if (this.model && this.model.value !== null) {
|
||||
const values = this.model.value.toString().split(DS_DATE_PICKER_SEPARATOR);
|
||||
if (values.length > 0) {
|
||||
this.initialYear = parseInt(values[0], 10);
|
||||
|
@@ -1,15 +1,19 @@
|
||||
import { DynamicFormControlLayout, DynamicInputModel, DynamicInputModelConfig, serializable } from '@ng-dynamic-forms/core';
|
||||
import {
|
||||
DynamicFormControlLayout,
|
||||
DynamicInputModel,
|
||||
DynamicInputModelConfig,
|
||||
serializable
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
import { LanguageCode } from '../../models/form-field-language-value.model';
|
||||
import { AuthorityOptions } from '../../../../../core/integration/models/authority-options.model';
|
||||
import { VocabularyOptions } from '../../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||
import { hasValue } from '../../../../empty.util';
|
||||
import { FormFieldMetadataValueObject } from '../../models/form-field-metadata-value.model';
|
||||
import { RelationshipOptions } from '../../models/relationship-options.model';
|
||||
import { MetadataValue } from '../../../../../core/shared/metadata.models';
|
||||
|
||||
export interface DsDynamicInputModelConfig extends DynamicInputModelConfig {
|
||||
authorityOptions?: AuthorityOptions;
|
||||
vocabularyOptions?: VocabularyOptions;
|
||||
languageCodes?: LanguageCode[];
|
||||
language?: string;
|
||||
place?: number;
|
||||
@@ -19,13 +23,13 @@ export interface DsDynamicInputModelConfig extends DynamicInputModelConfig {
|
||||
metadataFields: string[];
|
||||
submissionId: string;
|
||||
hasSelectableMetadata: boolean;
|
||||
metadataValue?: MetadataValue;
|
||||
metadataValue?: FormFieldMetadataValueObject;
|
||||
|
||||
}
|
||||
|
||||
export class DsDynamicInputModel extends DynamicInputModel {
|
||||
|
||||
@serializable() authorityOptions: AuthorityOptions;
|
||||
@serializable() vocabularyOptions: VocabularyOptions;
|
||||
@serializable() private _languageCodes: LanguageCode[];
|
||||
@serializable() private _language: string;
|
||||
@serializable() languageUpdates: Subject<string>;
|
||||
@@ -34,7 +38,7 @@ export class DsDynamicInputModel extends DynamicInputModel {
|
||||
@serializable() metadataFields: string[];
|
||||
@serializable() submissionId: string;
|
||||
@serializable() hasSelectableMetadata: boolean;
|
||||
@serializable() metadataValue: MetadataValue;
|
||||
@serializable() metadataValue: FormFieldMetadataValueObject;
|
||||
|
||||
constructor(config: DsDynamicInputModelConfig, layout?: DynamicFormControlLayout) {
|
||||
super(config, layout);
|
||||
@@ -50,7 +54,7 @@ export class DsDynamicInputModel extends DynamicInputModel {
|
||||
|
||||
this.language = config.language;
|
||||
if (!this.language) {
|
||||
// TypeAhead
|
||||
// Onebox
|
||||
if (config.value instanceof FormFieldMetadataValueObject) {
|
||||
this.language = config.value.language;
|
||||
} else if (Array.isArray(config.value)) {
|
||||
@@ -67,11 +71,11 @@ export class DsDynamicInputModel extends DynamicInputModel {
|
||||
this.language = lang;
|
||||
});
|
||||
|
||||
this.authorityOptions = config.authorityOptions;
|
||||
this.vocabularyOptions = config.vocabularyOptions;
|
||||
}
|
||||
|
||||
get hasAuthority(): boolean {
|
||||
return this.authorityOptions && hasValue(this.authorityOptions.name);
|
||||
return this.vocabularyOptions && hasValue(this.vocabularyOptions.name);
|
||||
}
|
||||
|
||||
get hasLanguages(): boolean {
|
||||
@@ -92,7 +96,7 @@ export class DsDynamicInputModel extends DynamicInputModel {
|
||||
|
||||
set languageCodes(languageCodes: LanguageCode[]) {
|
||||
this._languageCodes = languageCodes;
|
||||
if (!this.language || this.language === null || this.language === '') {
|
||||
if (!this.language || this.language === '') {
|
||||
this.language = this.languageCodes ? this.languageCodes[0].code : null;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,134 @@
|
||||
import { EventEmitter, Input, Output } from '@angular/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
import {
|
||||
DynamicFormControlComponent,
|
||||
DynamicFormLayoutService,
|
||||
DynamicFormValidationService
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
|
||||
import { VocabularyService } from '../../../../../core/submission/vocabularies/vocabulary.service';
|
||||
import { isNotEmpty } from '../../../../empty.util';
|
||||
import { FormFieldMetadataValueObject } from '../../models/form-field-metadata-value.model';
|
||||
import { VocabularyEntry } from '../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
|
||||
import { DsDynamicInputModel } from './ds-dynamic-input.model';
|
||||
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
||||
|
||||
/**
|
||||
* An abstract class to be extended by form components that handle vocabulary
|
||||
*/
|
||||
export abstract class DsDynamicVocabularyComponent extends DynamicFormControlComponent {
|
||||
|
||||
@Input() abstract bindId = true;
|
||||
@Input() abstract group: FormGroup;
|
||||
@Input() abstract model: DsDynamicInputModel;
|
||||
|
||||
@Output() abstract blur: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output() abstract change: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output() abstract focus: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
public abstract pageInfo: PageInfo;
|
||||
|
||||
protected constructor(protected vocabularyService: VocabularyService,
|
||||
protected layoutService: DynamicFormLayoutService,
|
||||
protected validationService: DynamicFormValidationService
|
||||
) {
|
||||
super(layoutService, validationService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current value with the given value.
|
||||
* @param value The value to set.
|
||||
* @param init Representing if is init value or not.
|
||||
*/
|
||||
public abstract setCurrentValue(value: any, init?: boolean);
|
||||
|
||||
/**
|
||||
* Retrieves the init form value from model
|
||||
*/
|
||||
getInitValueFromModel(): Observable<FormFieldMetadataValueObject> {
|
||||
let initValue$: Observable<FormFieldMetadataValueObject>;
|
||||
if (isNotEmpty(this.model.value) && (this.model.value instanceof FormFieldMetadataValueObject)) {
|
||||
let initEntry$: Observable<VocabularyEntry>;
|
||||
if (this.model.value.hasAuthority()) {
|
||||
initEntry$ = this.vocabularyService.getVocabularyEntryByID(this.model.value.authority, this.model.vocabularyOptions)
|
||||
} else {
|
||||
initEntry$ = this.vocabularyService.getVocabularyEntryByValue(this.model.value.value, this.model.vocabularyOptions)
|
||||
}
|
||||
initValue$ = initEntry$.pipe(map((initEntry: VocabularyEntry) => {
|
||||
if (isNotEmpty(initEntry)) {
|
||||
// Integrate FormFieldMetadataValueObject with retrieved information
|
||||
return new FormFieldMetadataValueObject(
|
||||
initEntry.value,
|
||||
null,
|
||||
initEntry.authority,
|
||||
initEntry.display,
|
||||
(this.model.value as any).place,
|
||||
null,
|
||||
initEntry.otherInformation || null
|
||||
);
|
||||
} else {
|
||||
return this.model.value as any;
|
||||
}
|
||||
}));
|
||||
} else if (isNotEmpty(this.model.value) && (this.model.value instanceof VocabularyEntry)) {
|
||||
initValue$ = observableOf(
|
||||
new FormFieldMetadataValueObject(
|
||||
this.model.value.value,
|
||||
null,
|
||||
this.model.value.authority,
|
||||
this.model.value.display,
|
||||
0,
|
||||
null,
|
||||
this.model.value.otherInformation || null
|
||||
)
|
||||
);
|
||||
} else {
|
||||
initValue$ = observableOf(new FormFieldMetadataValueObject(this.model.value));
|
||||
}
|
||||
return initValue$;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a blur event containing a given value.
|
||||
* @param event The value to emit.
|
||||
*/
|
||||
onBlur(event: Event) {
|
||||
this.blur.emit(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a focus event containing a given value.
|
||||
* @param event The value to emit.
|
||||
*/
|
||||
onFocus(event) {
|
||||
this.focus.emit(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a change event and updates model value.
|
||||
* @param updateValue
|
||||
*/
|
||||
dispatchUpdate(updateValue: any) {
|
||||
this.model.valueUpdates.next(updateValue);
|
||||
this.change.emit(updateValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the page info object
|
||||
* @param elementsPerPage
|
||||
* @param currentPage
|
||||
* @param totalElements
|
||||
* @param totalPages
|
||||
*/
|
||||
protected updatePageInfo(elementsPerPage: number, currentPage: number, totalElements?: number, totalPages?: number) {
|
||||
this.pageInfo = Object.assign(new PageInfo(), {
|
||||
elementsPerPage: elementsPerPage,
|
||||
currentPage: currentPage,
|
||||
totalElements: totalElements,
|
||||
totalPages: totalPages
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,16 +1,17 @@
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
import {
|
||||
DynamicCheckboxGroupModel, DynamicFormControlLayout,
|
||||
DynamicCheckboxGroupModel,
|
||||
DynamicFormControlLayout,
|
||||
DynamicFormGroupModelConfig,
|
||||
serializable
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { AuthorityValue } from '../../../../../../core/integration/models/authority.value';
|
||||
import { AuthorityOptions } from '../../../../../../core/integration/models/authority-options.model';
|
||||
|
||||
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
|
||||
import { VocabularyOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||
import { hasValue } from '../../../../../empty.util';
|
||||
|
||||
export interface DynamicListCheckboxGroupModelConfig extends DynamicFormGroupModelConfig {
|
||||
authorityOptions: AuthorityOptions;
|
||||
vocabularyOptions: VocabularyOptions;
|
||||
groupLength?: number;
|
||||
repeatable: boolean;
|
||||
value?: any;
|
||||
@@ -18,41 +19,41 @@ export interface DynamicListCheckboxGroupModelConfig extends DynamicFormGroupMod
|
||||
|
||||
export class DynamicListCheckboxGroupModel extends DynamicCheckboxGroupModel {
|
||||
|
||||
@serializable() authorityOptions: AuthorityOptions;
|
||||
@serializable() vocabularyOptions: VocabularyOptions;
|
||||
@serializable() repeatable: boolean;
|
||||
@serializable() groupLength: number;
|
||||
@serializable() _value: AuthorityValue[];
|
||||
@serializable() _value: VocabularyEntry[];
|
||||
isListGroup = true;
|
||||
valueUpdates: Subject<any>;
|
||||
|
||||
constructor(config: DynamicListCheckboxGroupModelConfig, layout?: DynamicFormControlLayout) {
|
||||
super(config, layout);
|
||||
|
||||
this.authorityOptions = config.authorityOptions;
|
||||
this.vocabularyOptions = config.vocabularyOptions;
|
||||
this.groupLength = config.groupLength || 5;
|
||||
this._value = [];
|
||||
this.repeatable = config.repeatable;
|
||||
|
||||
this.valueUpdates = new Subject<any>();
|
||||
this.valueUpdates.subscribe((value: AuthorityValue | AuthorityValue[]) => this.value = value);
|
||||
this.valueUpdates.subscribe((value: VocabularyEntry | VocabularyEntry[]) => this.value = value);
|
||||
this.valueUpdates.next(config.value);
|
||||
}
|
||||
|
||||
get hasAuthority(): boolean {
|
||||
return this.authorityOptions && hasValue(this.authorityOptions.name);
|
||||
return this.vocabularyOptions && hasValue(this.vocabularyOptions.name);
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(value: AuthorityValue | AuthorityValue[]) {
|
||||
set value(value: VocabularyEntry | VocabularyEntry[]) {
|
||||
if (value) {
|
||||
if (Array.isArray(value)) {
|
||||
this._value = value;
|
||||
} else {
|
||||
// _value is non extendible so assign it a new array
|
||||
const newValue = (this.value as AuthorityValue[]).concat([value]);
|
||||
const newValue = (this.value as VocabularyEntry[]).concat([value]);
|
||||
this._value = newValue
|
||||
}
|
||||
}
|
||||
|
@@ -4,11 +4,11 @@ import {
|
||||
DynamicRadioGroupModelConfig,
|
||||
serializable
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { AuthorityOptions } from '../../../../../../core/integration/models/authority-options.model';
|
||||
import { VocabularyOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||
import { hasValue } from '../../../../../empty.util';
|
||||
|
||||
export interface DynamicListModelConfig extends DynamicRadioGroupModelConfig<any> {
|
||||
authorityOptions: AuthorityOptions;
|
||||
vocabularyOptions: VocabularyOptions;
|
||||
groupLength?: number;
|
||||
repeatable: boolean;
|
||||
value?: any;
|
||||
@@ -16,7 +16,7 @@ export interface DynamicListModelConfig extends DynamicRadioGroupModelConfig<any
|
||||
|
||||
export class DynamicListRadioGroupModel extends DynamicRadioGroupModel<any> {
|
||||
|
||||
@serializable() authorityOptions: AuthorityOptions;
|
||||
@serializable() vocabularyOptions: VocabularyOptions;
|
||||
@serializable() repeatable: boolean;
|
||||
@serializable() groupLength: number;
|
||||
isListGroup = true;
|
||||
@@ -24,13 +24,13 @@ export class DynamicListRadioGroupModel extends DynamicRadioGroupModel<any> {
|
||||
constructor(config: DynamicListModelConfig, layout?: DynamicFormControlLayout) {
|
||||
super(config, layout);
|
||||
|
||||
this.authorityOptions = config.authorityOptions;
|
||||
this.vocabularyOptions = config.vocabularyOptions;
|
||||
this.groupLength = config.groupLength || 5;
|
||||
this.repeatable = config.repeatable;
|
||||
this.valueUpdates.next(config.value);
|
||||
}
|
||||
|
||||
get hasAuthority(): boolean {
|
||||
return this.authorityOptions && hasValue(this.authorityOptions.name);
|
||||
return this.vocabularyOptions && hasValue(this.vocabularyOptions.name);
|
||||
}
|
||||
}
|
||||
|
@@ -2,25 +2,25 @@
|
||||
import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { async, ComponentFixture, inject, TestBed, } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
import { DsDynamicListComponent } from './dynamic-list.component';
|
||||
import { DynamicListCheckboxGroupModel } from './dynamic-list-checkbox-group.model';
|
||||
import { AuthorityOptions } from '../../../../../../core/integration/models/authority-options.model';
|
||||
import { FormBuilderService } from '../../../form-builder.service';
|
||||
import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap';
|
||||
import {
|
||||
DynamicFormControlLayout,
|
||||
DynamicFormLayoutService,
|
||||
DynamicFormsCoreModule,
|
||||
DynamicFormValidationService
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap';
|
||||
import { AuthorityService } from '../../../../../../core/integration/authority.service';
|
||||
import { AuthorityServiceStub } from '../../../../../testing/authority-service.stub';
|
||||
|
||||
import { DsDynamicListComponent } from './dynamic-list.component';
|
||||
import { DynamicListCheckboxGroupModel } from './dynamic-list-checkbox-group.model';
|
||||
import { VocabularyOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||
import { FormBuilderService } from '../../../form-builder.service';
|
||||
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
|
||||
import { VocabularyServiceStub } from '../../../../../testing/vocabulary-service.stub';
|
||||
import { DynamicListRadioGroupModel } from './dynamic-list-radio-group.model';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { AuthorityValue } from '../../../../../../core/integration/models/authority.value';
|
||||
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
|
||||
import { createTestComponent } from '../../../../../testing/utils.test';
|
||||
|
||||
export const LAYOUT_TEST = {
|
||||
@@ -35,12 +35,10 @@ export const LIST_TEST_GROUP = new FormGroup({
|
||||
});
|
||||
|
||||
export const LIST_CHECKBOX_TEST_MODEL_CONFIG = {
|
||||
authorityOptions: {
|
||||
closed: false,
|
||||
metadata: 'listCheckbox',
|
||||
vocabularyOptions: {
|
||||
name: 'type_programme',
|
||||
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
|
||||
} as AuthorityOptions,
|
||||
closed: false
|
||||
} as VocabularyOptions,
|
||||
disabled: false,
|
||||
id: 'listCheckbox',
|
||||
label: 'Programme',
|
||||
@@ -52,12 +50,10 @@ export const LIST_CHECKBOX_TEST_MODEL_CONFIG = {
|
||||
};
|
||||
|
||||
export const LIST_RADIO_TEST_MODEL_CONFIG = {
|
||||
authorityOptions: {
|
||||
closed: false,
|
||||
metadata: 'listRadio',
|
||||
vocabularyOptions: {
|
||||
name: 'type_programme',
|
||||
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
|
||||
} as AuthorityOptions,
|
||||
closed: false
|
||||
} as VocabularyOptions,
|
||||
disabled: false,
|
||||
id: 'listRadio',
|
||||
label: 'Programme',
|
||||
@@ -77,7 +73,7 @@ describe('DsDynamicListComponent test suite', () => {
|
||||
let html;
|
||||
let modelValue;
|
||||
|
||||
const authorityServiceStub = new AuthorityServiceStub();
|
||||
const vocabularyServiceStub = new VocabularyServiceStub();
|
||||
|
||||
// async beforeEach
|
||||
beforeEach(async(() => {
|
||||
@@ -99,9 +95,9 @@ describe('DsDynamicListComponent test suite', () => {
|
||||
DsDynamicListComponent,
|
||||
DynamicFormValidationService,
|
||||
FormBuilderService,
|
||||
{provide: AuthorityService, useValue: authorityServiceStub},
|
||||
{provide: DynamicFormLayoutService, useValue: {}},
|
||||
{provide: DynamicFormValidationService, useValue: {}}
|
||||
{ provide: VocabularyService, useValue: vocabularyServiceStub },
|
||||
{ provide: DynamicFormLayoutService, useValue: {} },
|
||||
{ provide: DynamicFormValidationService, useValue: {} }
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
});
|
||||
@@ -147,20 +143,16 @@ describe('DsDynamicListComponent test suite', () => {
|
||||
});
|
||||
|
||||
it('should init component properly', () => {
|
||||
const results$ = authorityServiceStub.getEntriesByName({} as any);
|
||||
|
||||
results$.subscribe((results) => {
|
||||
expect((listComp as any).optionsList).toEqual(results.payload);
|
||||
expect(listComp.items.length).toBe(1);
|
||||
expect(listComp.items[0].length).toBe(2);
|
||||
})
|
||||
expect((listComp as any).optionsList).toEqual(vocabularyServiceStub.getList());
|
||||
expect(listComp.items.length).toBe(1);
|
||||
expect(listComp.items[0].length).toBe(2);
|
||||
});
|
||||
|
||||
it('should set model value properly when a checkbox option is selected', () => {
|
||||
const de = listFixture.debugElement.queryAll(By.css('div.custom-checkbox'));
|
||||
const items = de[0].queryAll(By.css('input.custom-control-input'));
|
||||
const item = items[0];
|
||||
modelValue = [Object.assign(new AuthorityValue(), {id: 1, display: 'one', value: 1})];
|
||||
modelValue = [Object.assign(new VocabularyEntry(), { authority: 1, display: 'one', value: 1 })];
|
||||
|
||||
item.nativeElement.click();
|
||||
|
||||
@@ -187,7 +179,7 @@ describe('DsDynamicListComponent test suite', () => {
|
||||
listComp = listFixture.componentInstance; // FormComponent test instance
|
||||
listComp.group = LIST_TEST_GROUP;
|
||||
listComp.model = new DynamicListCheckboxGroupModel(LIST_CHECKBOX_TEST_MODEL_CONFIG, LAYOUT_TEST);
|
||||
modelValue = [Object.assign(new AuthorityValue(), {id: 1, display: 'one', value: 1})];
|
||||
modelValue = [Object.assign(new VocabularyEntry(), { authority: 1, display: 'one', value: 1 })];
|
||||
listComp.model.value = modelValue;
|
||||
listFixture.detectChanges();
|
||||
});
|
||||
@@ -198,13 +190,9 @@ describe('DsDynamicListComponent test suite', () => {
|
||||
});
|
||||
|
||||
it('should init component properly', () => {
|
||||
const results$ = authorityServiceStub.getEntriesByName({} as any);
|
||||
|
||||
results$.subscribe((results) => {
|
||||
expect((listComp as any).optionsList).toEqual(results.payload);
|
||||
expect(listComp.model.value).toEqual(modelValue);
|
||||
expect((listComp.model as DynamicListCheckboxGroupModel).group[0].value).toBeTruthy();
|
||||
})
|
||||
expect((listComp as any).optionsList).toEqual(vocabularyServiceStub.getList());
|
||||
expect(listComp.model.value).toEqual(modelValue);
|
||||
expect((listComp.model as DynamicListCheckboxGroupModel).group[0].value).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should set model value properly when a checkbox option is deselected', () => {
|
||||
@@ -237,20 +225,16 @@ describe('DsDynamicListComponent test suite', () => {
|
||||
});
|
||||
|
||||
it('should init component properly', () => {
|
||||
const results$ = authorityServiceStub.getEntriesByName({} as any);
|
||||
|
||||
results$.subscribe((results) => {
|
||||
expect((listComp as any).optionsList).toEqual(results.payload);
|
||||
expect(listComp.items.length).toBe(1);
|
||||
expect(listComp.items[0].length).toBe(2);
|
||||
})
|
||||
expect((listComp as any).optionsList).toEqual(vocabularyServiceStub.getList());
|
||||
expect(listComp.items.length).toBe(1);
|
||||
expect(listComp.items[0].length).toBe(2);
|
||||
});
|
||||
|
||||
it('should set model value when a radio option is selected', () => {
|
||||
const de = listFixture.debugElement.queryAll(By.css('div.custom-radio'));
|
||||
const items = de[0].queryAll(By.css('input.custom-control-input'));
|
||||
const item = items[0];
|
||||
modelValue = Object.assign(new AuthorityValue(), {id: 1, display: 'one', value: 1});
|
||||
modelValue = Object.assign(new VocabularyEntry(), { authority: 1, display: 'one', value: 1 });
|
||||
|
||||
item.nativeElement.click();
|
||||
|
||||
@@ -265,7 +249,7 @@ describe('DsDynamicListComponent test suite', () => {
|
||||
listComp = listFixture.componentInstance; // FormComponent test instance
|
||||
listComp.group = LIST_TEST_GROUP;
|
||||
listComp.model = new DynamicListRadioGroupModel(LIST_RADIO_TEST_MODEL_CONFIG, LAYOUT_TEST);
|
||||
modelValue = Object.assign(new AuthorityValue(), {id: 1, display: 'one', value: 1});
|
||||
modelValue = Object.assign(new VocabularyEntry(), { authority: 1, display: 'one', value: 1 });
|
||||
listComp.model.value = modelValue;
|
||||
listFixture.detectChanges();
|
||||
});
|
||||
@@ -276,13 +260,9 @@ describe('DsDynamicListComponent test suite', () => {
|
||||
});
|
||||
|
||||
it('should init component properly', () => {
|
||||
const results$ = authorityServiceStub.getEntriesByName({} as any);
|
||||
|
||||
results$.subscribe((results) => {
|
||||
expect((listComp as any).optionsList).toEqual(results.payload);
|
||||
expect(listComp.model.value).toEqual(modelValue);
|
||||
expect((listComp.model as DynamicListRadioGroupModel).options[0].value).toBeTruthy();
|
||||
})
|
||||
expect((listComp as any).optionsList).toEqual(vocabularyServiceStub.getList());
|
||||
expect(listComp.model.value).toEqual(modelValue);
|
||||
expect((listComp.model as DynamicListRadioGroupModel).options[0].value).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,20 +1,23 @@
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
import {
|
||||
DynamicCheckboxModel,
|
||||
DynamicFormControlComponent,
|
||||
DynamicFormLayoutService,
|
||||
DynamicFormValidationService
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { findKey } from 'lodash';
|
||||
|
||||
import { AuthorityService } from '../../../../../../core/integration/authority.service';
|
||||
import { IntegrationSearchOptions } from '../../../../../../core/integration/models/integration-options.model';
|
||||
import { hasValue, isNotEmpty } from '../../../../../empty.util';
|
||||
import { DynamicListCheckboxGroupModel } from './dynamic-list-checkbox-group.model';
|
||||
import { FormBuilderService } from '../../../form-builder.service';
|
||||
import {
|
||||
DynamicCheckboxModel,
|
||||
DynamicFormControlComponent, DynamicFormLayoutService,
|
||||
DynamicFormValidationService
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { AuthorityValue } from '../../../../../../core/integration/models/authority.value';
|
||||
import { DynamicListRadioGroupModel } from './dynamic-list-radio-group.model';
|
||||
import { IntegrationData } from '../../../../../../core/integration/integration-data';
|
||||
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
|
||||
import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators';
|
||||
import { PaginatedList } from '../../../../../../core/data/paginated-list';
|
||||
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
|
||||
import { PageInfo } from '../../../../../../core/shared/page-info.model';
|
||||
|
||||
export interface ListItem {
|
||||
id: string,
|
||||
@@ -23,12 +26,14 @@ export interface ListItem {
|
||||
index: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Component representing a list input field
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-dynamic-list',
|
||||
styleUrls: ['./dynamic-list.component.scss'],
|
||||
templateUrl: './dynamic-list.component.html'
|
||||
})
|
||||
|
||||
export class DsDynamicListComponent extends DynamicFormControlComponent implements OnInit {
|
||||
@Input() bindId = true;
|
||||
@Input() group: FormGroup;
|
||||
@@ -39,10 +44,9 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
|
||||
@Output() focus: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
public items: ListItem[][] = [];
|
||||
protected optionsList: AuthorityValue[];
|
||||
protected searchOptions: IntegrationSearchOptions;
|
||||
protected optionsList: VocabularyEntry[];
|
||||
|
||||
constructor(private authorityService: AuthorityService,
|
||||
constructor(private vocabularyService: VocabularyService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private formBuilderService: FormBuilderService,
|
||||
protected layoutService: DynamicFormLayoutService,
|
||||
@@ -51,39 +55,46 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
|
||||
super(layoutService, validationService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the component, setting up the field options
|
||||
*/
|
||||
ngOnInit() {
|
||||
if (this.hasAuthorityOptions()) {
|
||||
// TODO Replace max elements 1000 with a paginated request when pagination bug is resolved
|
||||
this.searchOptions = new IntegrationSearchOptions(
|
||||
this.model.authorityOptions.scope,
|
||||
this.model.authorityOptions.name,
|
||||
this.model.authorityOptions.metadata,
|
||||
'',
|
||||
1000, // Max elements
|
||||
1);// Current Page
|
||||
this.setOptionsFromAuthority();
|
||||
if (this.model.vocabularyOptions && hasValue(this.model.vocabularyOptions.name)) {
|
||||
this.setOptionsFromVocabulary();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a blur event containing a given value.
|
||||
* @param event The value to emit.
|
||||
*/
|
||||
onBlur(event: Event) {
|
||||
this.blur.emit(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a focus event containing a given value.
|
||||
* @param event The value to emit.
|
||||
*/
|
||||
onFocus(event: Event) {
|
||||
this.focus.emit(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates model value with the current value
|
||||
* @param event The change event.
|
||||
*/
|
||||
onChange(event: Event) {
|
||||
const target = event.target as any;
|
||||
if (this.model.repeatable) {
|
||||
// Target tabindex coincide with the array index of the value into the authority list
|
||||
const authorityValue: AuthorityValue = this.optionsList[target.tabIndex];
|
||||
const entry: VocabularyEntry = this.optionsList[target.tabIndex];
|
||||
if (target.checked) {
|
||||
this.model.valueUpdates.next(authorityValue);
|
||||
this.model.valueUpdates.next(entry);
|
||||
} else {
|
||||
const newValue = [];
|
||||
this.model.value
|
||||
.filter((item) => item.value !== authorityValue.value)
|
||||
.filter((item) => item.value !== entry.value)
|
||||
.forEach((item) => newValue.push(item));
|
||||
this.model.valueUpdates.next(newValue);
|
||||
}
|
||||
@@ -93,17 +104,25 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
|
||||
this.change.emit(event);
|
||||
}
|
||||
|
||||
protected setOptionsFromAuthority() {
|
||||
if (this.model.authorityOptions.name && this.model.authorityOptions.name.length > 0) {
|
||||
/**
|
||||
* Setting up the field options from vocabulary
|
||||
*/
|
||||
protected setOptionsFromVocabulary() {
|
||||
if (this.model.vocabularyOptions.name && this.model.vocabularyOptions.name.length > 0) {
|
||||
const listGroup = this.group.controls[this.model.id] as FormGroup;
|
||||
this.authorityService.getEntriesByName(this.searchOptions).subscribe((authorities: IntegrationData) => {
|
||||
const pageInfo: PageInfo = new PageInfo({
|
||||
elementsPerPage: Number.MAX_VALUE, currentPage: 1
|
||||
} as PageInfo);
|
||||
this.vocabularyService.getVocabularyEntries(this.model.vocabularyOptions, pageInfo).pipe(
|
||||
getFirstSucceededRemoteDataPayload()
|
||||
).subscribe((entries: PaginatedList<VocabularyEntry>) => {
|
||||
let groupCounter = 0;
|
||||
let itemsPerGroup = 0;
|
||||
let tempList: ListItem[] = [];
|
||||
this.optionsList = authorities.payload as AuthorityValue[];
|
||||
this.optionsList = entries.page;
|
||||
// Make a list of available options (checkbox/radio) and split in groups of 'model.groupLength'
|
||||
(authorities.payload as AuthorityValue[]).forEach((option, key) => {
|
||||
const value = option.id || option.value;
|
||||
entries.page.forEach((option, key) => {
|
||||
const value = option.authority || option.value;
|
||||
const checked: boolean = isNotEmpty(findKey(
|
||||
this.model.value,
|
||||
(v) => v.value === option.value));
|
||||
@@ -137,9 +156,4 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
|
||||
}
|
||||
}
|
||||
|
||||
protected hasAuthorityOptions() {
|
||||
return (hasValue(this.model.authorityOptions.scope)
|
||||
&& hasValue(this.model.authorityOptions.name)
|
||||
&& hasValue(this.model.authorityOptions.metadata));
|
||||
}
|
||||
}
|
||||
|
@@ -21,8 +21,8 @@
|
||||
[placeholder]="model.placeholder | translate"
|
||||
[readonly]="model.readOnly"
|
||||
(change)="onChange($event)"
|
||||
(blur)="onBlurEvent($event); $event.stopPropagation(); sdRef.close();"
|
||||
(focus)="onFocusEvent($event); $event.stopPropagation(); sdRef.close();"
|
||||
(blur)="onBlur($event); $event.stopPropagation(); sdRef.close();"
|
||||
(focus)="onFocus($event); $event.stopPropagation(); sdRef.close();"
|
||||
(click)="$event.stopPropagation(); $event.stopPropagation(); sdRef.close();">
|
||||
</div>
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
[placeholder]="model.secondPlaceholder | translate"
|
||||
[readonly]="model.readOnly"
|
||||
(change)="onChange($event)"
|
||||
(blur)="onBlurEvent($event); $event.stopPropagation(); sdRef.close();"
|
||||
(focus)="onFocusEvent($event); $event.stopPropagation(); sdRef.close();"
|
||||
(blur)="onBlur($event); $event.stopPropagation(); sdRef.close();"
|
||||
(focus)="onFocus($event); $event.stopPropagation(); sdRef.close();"
|
||||
(click)="$event.stopPropagation(); sdRef.close();">
|
||||
</div>
|
||||
<div class="col-auto text-center">
|
||||
|
@@ -2,33 +2,32 @@
|
||||
import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { async, ComponentFixture, fakeAsync, inject, TestBed, tick, } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
import { AuthorityOptions } from '../../../../../../core/integration/models/authority-options.model';
|
||||
import { DynamicFormLayoutService, DynamicFormsCoreModule, DynamicFormValidationService } from '@ng-dynamic-forms/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||
import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap';
|
||||
import { AuthorityService } from '../../../../../../core/integration/authority.service';
|
||||
import { AuthorityServiceStub } from '../../../../../testing/authority-service.stub';
|
||||
import { DynamicFormLayoutService, DynamicFormsCoreModule, DynamicFormValidationService } from '@ng-dynamic-forms/core';
|
||||
|
||||
import { VocabularyOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
|
||||
import { VocabularyServiceStub } from '../../../../../testing/vocabulary-service.stub';
|
||||
import { DsDynamicLookupComponent } from './dynamic-lookup.component';
|
||||
import { DynamicLookupModel, DynamicLookupModelConfig } from './dynamic-lookup.model';
|
||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { AuthorityValue } from '../../../../../../core/integration/models/authority.value';
|
||||
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
|
||||
import { createTestComponent } from '../../../../../testing/utils.test';
|
||||
import { DynamicLookupNameModel } from './dynamic-lookup-name.model';
|
||||
import { AuthorityConfidenceStateDirective } from '../../../../../authority-confidence/authority-confidence-state.directive';
|
||||
import { ObjNgFor } from '../../../../../utils/object-ngfor.pipe';
|
||||
|
||||
let LOOKUP_TEST_MODEL_CONFIG: DynamicLookupModelConfig = {
|
||||
authorityOptions: {
|
||||
closed: false,
|
||||
metadata: 'lookup',
|
||||
vocabularyOptions: {
|
||||
name: 'RPAuthority',
|
||||
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
|
||||
} as AuthorityOptions,
|
||||
closed: false
|
||||
} as VocabularyOptions,
|
||||
disabled: false,
|
||||
errorMessages: { required: 'Required field.' },
|
||||
id: 'lookup',
|
||||
@@ -47,12 +46,10 @@ let LOOKUP_TEST_MODEL_CONFIG: DynamicLookupModelConfig = {
|
||||
};
|
||||
|
||||
let LOOKUP_NAME_TEST_MODEL_CONFIG = {
|
||||
authorityOptions: {
|
||||
closed: false,
|
||||
metadata: 'lookup-name',
|
||||
vocabularyOptions: {
|
||||
name: 'RPAuthority',
|
||||
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
|
||||
} as AuthorityOptions,
|
||||
closed: false
|
||||
} as VocabularyOptions,
|
||||
disabled: false,
|
||||
errorMessages: { required: 'Required field.' },
|
||||
id: 'lookupName',
|
||||
@@ -78,12 +75,10 @@ let LOOKUP_TEST_GROUP = new FormGroup({
|
||||
describe('Dynamic Lookup component', () => {
|
||||
function init() {
|
||||
LOOKUP_TEST_MODEL_CONFIG = {
|
||||
authorityOptions: {
|
||||
closed: false,
|
||||
metadata: 'lookup',
|
||||
vocabularyOptions: {
|
||||
name: 'RPAuthority',
|
||||
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
|
||||
} as AuthorityOptions,
|
||||
closed: false
|
||||
} as VocabularyOptions,
|
||||
disabled: false,
|
||||
errorMessages: { required: 'Required field.' },
|
||||
id: 'lookup',
|
||||
@@ -102,12 +97,10 @@ describe('Dynamic Lookup component', () => {
|
||||
};
|
||||
|
||||
LOOKUP_NAME_TEST_MODEL_CONFIG = {
|
||||
authorityOptions: {
|
||||
closed: false,
|
||||
metadata: 'lookup-name',
|
||||
vocabularyOptions: {
|
||||
name: 'RPAuthority',
|
||||
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
|
||||
} as AuthorityOptions,
|
||||
closed: false
|
||||
} as VocabularyOptions,
|
||||
disabled: false,
|
||||
errorMessages: { required: 'Required field.' },
|
||||
id: 'lookupName',
|
||||
@@ -137,12 +130,11 @@ describe('Dynamic Lookup component', () => {
|
||||
let testFixture: ComponentFixture<TestComponent>;
|
||||
let lookupFixture: ComponentFixture<DsDynamicLookupComponent>;
|
||||
let html;
|
||||
let vocabularyServiceStub: VocabularyServiceStub;
|
||||
|
||||
let authorityServiceStub;
|
||||
// async beforeEach
|
||||
beforeEach(async(() => {
|
||||
const authorityService = new AuthorityServiceStub();
|
||||
authorityServiceStub = authorityService;
|
||||
vocabularyServiceStub = new VocabularyServiceStub();
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
DynamicFormsCoreModule,
|
||||
@@ -162,7 +154,7 @@ describe('Dynamic Lookup component', () => {
|
||||
providers: [
|
||||
ChangeDetectorRef,
|
||||
DsDynamicLookupComponent,
|
||||
{ provide: AuthorityService, useValue: authorityService },
|
||||
{ provide: VocabularyService, useValue: vocabularyServiceStub },
|
||||
{ provide: DynamicFormLayoutService, useValue: {} },
|
||||
{ provide: DynamicFormValidationService, useValue: {} }
|
||||
],
|
||||
@@ -247,7 +239,7 @@ describe('Dynamic Lookup component', () => {
|
||||
it('should return search results', fakeAsync(() => {
|
||||
const de = lookupFixture.debugElement.queryAll(By.css('button'));
|
||||
const btnEl = de[0].nativeElement;
|
||||
const results$ = authorityServiceStub.getEntriesByName({} as any);
|
||||
const results = vocabularyServiceStub.getList();
|
||||
|
||||
lookupComp.firstInputValue = 'test';
|
||||
lookupFixture.detectChanges();
|
||||
@@ -255,17 +247,15 @@ describe('Dynamic Lookup component', () => {
|
||||
btnEl.click();
|
||||
tick();
|
||||
lookupFixture.detectChanges();
|
||||
results$.subscribe((results) => {
|
||||
expect(lookupComp.optionsList).toEqual(results.payload);
|
||||
});
|
||||
expect(lookupComp.optionsList).toEqual(results);
|
||||
|
||||
}));
|
||||
|
||||
it('should select a results entry properly', fakeAsync(() => {
|
||||
let de = lookupFixture.debugElement.queryAll(By.css('button'));
|
||||
const btnEl = de[0].nativeElement;
|
||||
const selectedValue = Object.assign(new AuthorityValue(), {
|
||||
id: 1,
|
||||
const selectedValue = Object.assign(new VocabularyEntry(), {
|
||||
authority: 1,
|
||||
display: 'one',
|
||||
value: 1
|
||||
});
|
||||
@@ -284,7 +274,7 @@ describe('Dynamic Lookup component', () => {
|
||||
expect(lookupComp.change.emit).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should set model.value on input type when AuthorityOptions.closed is false', fakeAsync(() => {
|
||||
it('should set model.value on input type when VocabularyOptions.closed is false', fakeAsync(() => {
|
||||
lookupComp.firstInputValue = 'test';
|
||||
lookupFixture.detectChanges();
|
||||
|
||||
@@ -293,8 +283,8 @@ describe('Dynamic Lookup component', () => {
|
||||
|
||||
}));
|
||||
|
||||
it('should not set model.value on input type when AuthorityOptions.closed is true', () => {
|
||||
lookupComp.model.authorityOptions.closed = true;
|
||||
it('should not set model.value on input type when VocabularyOptions.closed is true', () => {
|
||||
lookupComp.model.vocabularyOptions.closed = true;
|
||||
lookupComp.firstInputValue = 'test';
|
||||
lookupFixture.detectChanges();
|
||||
|
||||
@@ -312,7 +302,13 @@ describe('Dynamic Lookup component', () => {
|
||||
lookupComp = lookupFixture.componentInstance; // FormComponent test instance
|
||||
lookupComp.group = LOOKUP_TEST_GROUP;
|
||||
lookupComp.model = new DynamicLookupModel(LOOKUP_TEST_MODEL_CONFIG);
|
||||
lookupComp.model.value = new FormFieldMetadataValueObject('test', null, 'test001');
|
||||
const entry = observableOf(Object.assign(new VocabularyEntry(), {
|
||||
authority: null,
|
||||
value: 'test',
|
||||
display: 'testDisplay'
|
||||
}));
|
||||
spyOn((lookupComp as any).vocabularyService, 'getVocabularyEntryByValue').and.returnValue(entry);
|
||||
(lookupComp.model as any).value = new FormFieldMetadataValueObject('test', null, null, 'testDisplay');
|
||||
lookupFixture.detectChanges();
|
||||
|
||||
// spyOn(store, 'dispatch');
|
||||
@@ -321,9 +317,52 @@ describe('Dynamic Lookup component', () => {
|
||||
lookupFixture.destroy();
|
||||
lookupComp = null;
|
||||
});
|
||||
it('should init component properly', () => {
|
||||
expect(lookupComp.firstInputValue).toBe('test');
|
||||
it('should init component properly', fakeAsync(() => {
|
||||
tick();
|
||||
expect(lookupComp.firstInputValue).toBe('testDisplay');
|
||||
expect((lookupComp as any).vocabularyService.getVocabularyEntryByValue).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should have search button disabled on edit mode', () => {
|
||||
lookupComp.editMode = true;
|
||||
lookupFixture.detectChanges();
|
||||
|
||||
const de = lookupFixture.debugElement.queryAll(By.css('button'));
|
||||
const searchBtnEl = de[0].nativeElement;
|
||||
const saveBtnEl = de[1].nativeElement;
|
||||
expect(searchBtnEl.disabled).toBe(true);
|
||||
expect(saveBtnEl.disabled).toBe(false);
|
||||
expect(saveBtnEl.textContent.trim()).toBe('form.save');
|
||||
|
||||
});
|
||||
});
|
||||
describe('and init model value is not empty with authority', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
lookupFixture = TestBed.createComponent(DsDynamicLookupComponent);
|
||||
lookupComp = lookupFixture.componentInstance; // FormComponent test instance
|
||||
lookupComp.group = LOOKUP_TEST_GROUP;
|
||||
lookupComp.model = new DynamicLookupModel(LOOKUP_TEST_MODEL_CONFIG);
|
||||
const entry = observableOf(Object.assign(new VocabularyEntry(), {
|
||||
authority: 'test001',
|
||||
value: 'test',
|
||||
display: 'testDisplay'
|
||||
}));
|
||||
spyOn((lookupComp as any).vocabularyService, 'getVocabularyEntryByID').and.returnValue(entry);
|
||||
lookupComp.model.value = new FormFieldMetadataValueObject('test', null, 'test001', 'testDisplay');
|
||||
lookupFixture.detectChanges();
|
||||
|
||||
// spyOn(store, 'dispatch');
|
||||
});
|
||||
afterEach(() => {
|
||||
lookupFixture.destroy();
|
||||
lookupComp = null;
|
||||
});
|
||||
it('should init component properly', fakeAsync(() => {
|
||||
tick();
|
||||
expect(lookupComp.firstInputValue).toBe('testDisplay');
|
||||
expect((lookupComp as any).vocabularyService.getVocabularyEntryByID).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should have search button disabled on edit mode', () => {
|
||||
lookupComp.editMode = true;
|
||||
@@ -389,26 +428,26 @@ describe('Dynamic Lookup component', () => {
|
||||
|
||||
it('should select a results entry properly', fakeAsync(() => {
|
||||
const payload = [
|
||||
Object.assign(new AuthorityValue(), {
|
||||
id: 1,
|
||||
Object.assign(new VocabularyEntry(), {
|
||||
authority: 1,
|
||||
display: 'Name, Lastname',
|
||||
value: 1
|
||||
}),
|
||||
Object.assign(new AuthorityValue(), {
|
||||
id: 2,
|
||||
Object.assign(new VocabularyEntry(), {
|
||||
authority: 2,
|
||||
display: 'NameTwo, LastnameTwo',
|
||||
value: 2
|
||||
}),
|
||||
];
|
||||
let de = lookupFixture.debugElement.queryAll(By.css('button'));
|
||||
const btnEl = de[0].nativeElement;
|
||||
const selectedValue = Object.assign(new AuthorityValue(), {
|
||||
id: 1,
|
||||
const selectedValue = Object.assign(new VocabularyEntry(), {
|
||||
authority: 1,
|
||||
display: 'Name, Lastname',
|
||||
value: 1
|
||||
});
|
||||
spyOn(lookupComp.change, 'emit');
|
||||
authorityServiceStub.setNewPayload(payload);
|
||||
vocabularyServiceStub.setNewPayload(payload);
|
||||
lookupComp.firstInputValue = 'test';
|
||||
lookupFixture.detectChanges();
|
||||
btnEl.click();
|
||||
@@ -433,6 +472,13 @@ describe('Dynamic Lookup component', () => {
|
||||
lookupComp.group = LOOKUP_TEST_GROUP;
|
||||
lookupComp.model = new DynamicLookupNameModel(LOOKUP_NAME_TEST_MODEL_CONFIG);
|
||||
lookupComp.model.value = new FormFieldMetadataValueObject('Name, Lastname', null, 'test001');
|
||||
const entry = observableOf(Object.assign(new VocabularyEntry(), {
|
||||
authority: null,
|
||||
value: 'Name, Lastname',
|
||||
display: 'Name, Lastname'
|
||||
}));
|
||||
spyOn((lookupComp as any).vocabularyService, 'getVocabularyEntryByValue').and.returnValue(entry);
|
||||
(lookupComp.model as any).value = new FormFieldMetadataValueObject('Name, Lastname', null, null, 'Name, Lastname');
|
||||
lookupFixture.detectChanges();
|
||||
|
||||
});
|
||||
@@ -440,10 +486,55 @@ describe('Dynamic Lookup component', () => {
|
||||
lookupFixture.destroy();
|
||||
lookupComp = null;
|
||||
});
|
||||
it('should init component properly', () => {
|
||||
it('should init component properly', fakeAsync(() => {
|
||||
tick();
|
||||
expect(lookupComp.firstInputValue).toBe('Name');
|
||||
expect(lookupComp.secondInputValue).toBe('Lastname');
|
||||
expect((lookupComp as any).vocabularyService.getVocabularyEntryByValue).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should have search button disabled on edit mode', () => {
|
||||
lookupComp.editMode = true;
|
||||
lookupFixture.detectChanges();
|
||||
|
||||
const de = lookupFixture.debugElement.queryAll(By.css('button'));
|
||||
const searchBtnEl = de[0].nativeElement;
|
||||
const saveBtnEl = de[1].nativeElement;
|
||||
expect(searchBtnEl.disabled).toBe(true);
|
||||
expect(saveBtnEl.disabled).toBe(false);
|
||||
expect(saveBtnEl.textContent.trim()).toBe('form.save');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('and init model value is not empty with authority', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
lookupFixture = TestBed.createComponent(DsDynamicLookupComponent);
|
||||
lookupComp = lookupFixture.componentInstance; // FormComponent test instance
|
||||
lookupComp.group = LOOKUP_TEST_GROUP;
|
||||
lookupComp.model = new DynamicLookupNameModel(LOOKUP_NAME_TEST_MODEL_CONFIG);
|
||||
lookupComp.model.value = new FormFieldMetadataValueObject('Name, Lastname', null, 'test001');
|
||||
const entry = observableOf(Object.assign(new VocabularyEntry(), {
|
||||
authority: 'test001',
|
||||
value: 'Name, Lastname',
|
||||
display: 'Name, Lastname'
|
||||
}));
|
||||
spyOn((lookupComp as any).vocabularyService, 'getVocabularyEntryByID').and.returnValue(entry);
|
||||
lookupComp.model.value = new FormFieldMetadataValueObject('Name, Lastname', null, 'test001', 'Name, Lastname');
|
||||
lookupFixture.detectChanges();
|
||||
|
||||
});
|
||||
afterEach(() => {
|
||||
lookupFixture.destroy();
|
||||
lookupComp = null;
|
||||
});
|
||||
it('should init component properly', fakeAsync(() => {
|
||||
tick();
|
||||
expect(lookupComp.firstInputValue).toBe('Name');
|
||||
expect(lookupComp.secondInputValue).toBe('Lastname');
|
||||
expect((lookupComp as any).vocabularyService.getVocabularyEntryByID).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should have search button disabled on edit mode', () => {
|
||||
lookupComp.editMode = true;
|
||||
|
@@ -1,33 +1,31 @@
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
import { Subscription } from 'rxjs';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { of as observableOf, Subscription } from 'rxjs';
|
||||
import { catchError, distinctUntilChanged } from 'rxjs/operators';
|
||||
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap';
|
||||
import {
|
||||
DynamicFormControlComponent,
|
||||
DynamicFormLayoutService,
|
||||
DynamicFormValidationService
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
|
||||
|
||||
import { AuthorityService } from '../../../../../../core/integration/authority.service';
|
||||
import { DynamicLookupModel } from './dynamic-lookup.model';
|
||||
import { IntegrationSearchOptions } from '../../../../../../core/integration/models/integration-options.model';
|
||||
import { hasValue, isEmpty, isNotEmpty, isNull, isUndefined } from '../../../../../empty.util';
|
||||
import { IntegrationData } from '../../../../../../core/integration/integration-data';
|
||||
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
|
||||
import { hasValue, isEmpty, isNotEmpty } from '../../../../../empty.util';
|
||||
import { PageInfo } from '../../../../../../core/shared/page-info.model';
|
||||
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
|
||||
import { AuthorityValue } from '../../../../../../core/integration/models/authority.value';
|
||||
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
|
||||
import { DynamicLookupNameModel } from './dynamic-lookup-name.model';
|
||||
import { ConfidenceType } from '../../../../../../core/integration/models/confidence-type';
|
||||
import { ConfidenceType } from '../../../../../../core/shared/confidence-type';
|
||||
import { PaginatedList } from '../../../../../../core/data/paginated-list';
|
||||
import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators';
|
||||
import { DsDynamicVocabularyComponent } from '../dynamic-vocabulary.component';
|
||||
|
||||
/**
|
||||
* Component representing a lookup or lookup-name input field
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-dynamic-lookup',
|
||||
styleUrls: ['./dynamic-lookup.component.scss'],
|
||||
templateUrl: './dynamic-lookup.component.html'
|
||||
})
|
||||
export class DsDynamicLookupComponent extends DynamicFormControlComponent implements OnDestroy, OnInit {
|
||||
export class DsDynamicLookupComponent extends DsDynamicVocabularyComponent implements OnDestroy, OnInit {
|
||||
@Input() bindId = true;
|
||||
@Input() group: FormGroup;
|
||||
@Input() model: any;
|
||||
@@ -43,42 +41,262 @@ export class DsDynamicLookupComponent extends DynamicFormControlComponent implem
|
||||
public pageInfo: PageInfo;
|
||||
public optionsList: any;
|
||||
|
||||
protected searchOptions: IntegrationSearchOptions;
|
||||
protected subs: Subscription[] = [];
|
||||
|
||||
constructor(private authorityService: AuthorityService,
|
||||
constructor(protected vocabularyService: VocabularyService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
protected layoutService: DynamicFormLayoutService,
|
||||
protected validationService: DynamicFormValidationService
|
||||
) {
|
||||
super(layoutService, validationService);
|
||||
super(vocabularyService, layoutService, validationService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an item from the result list to a `string` to display in the `<input>` field.
|
||||
*/
|
||||
inputFormatter = (x: { display: string }, y: number) => {
|
||||
return y === 1 ? this.firstInputValue : this.secondInputValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the component, setting up the init form value
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.searchOptions = new IntegrationSearchOptions(
|
||||
this.model.authorityOptions.scope,
|
||||
this.model.authorityOptions.name,
|
||||
this.model.authorityOptions.metadata,
|
||||
'',
|
||||
this.model.maxOptions,
|
||||
1);
|
||||
|
||||
this.setInputsValue(this.model.value);
|
||||
if (isNotEmpty(this.model.value)) {
|
||||
this.setCurrentValue(this.model.value, true);
|
||||
}
|
||||
|
||||
this.subs.push(this.model.valueUpdates
|
||||
.subscribe((value) => {
|
||||
if (isEmpty(value)) {
|
||||
this.resetFields();
|
||||
} else if (!this.editMode) {
|
||||
this.setInputsValue(this.model.value);
|
||||
this.setCurrentValue(this.model.value);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if model value has an authority
|
||||
*/
|
||||
public hasAuthorityValue() {
|
||||
return hasValue(this.model.value)
|
||||
&& typeof this.model.value === 'object'
|
||||
&& this.model.value.hasAuthority();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current value has an authority
|
||||
*/
|
||||
public hasEmptyValue() {
|
||||
return isNotEmpty(this.getCurrentValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear inputs whether there is no results and authority is closed
|
||||
*/
|
||||
public clearFields() {
|
||||
if (this.model.vocabularyOptions.closed) {
|
||||
this.resetFields();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if edit button is disabled
|
||||
*/
|
||||
public isEditDisabled() {
|
||||
return !this.hasAuthorityValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if input is disabled
|
||||
*/
|
||||
public isInputDisabled() {
|
||||
return (this.model.vocabularyOptions.closed && this.hasAuthorityValue() && !this.editMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if model is instanceof DynamicLookupNameModel
|
||||
*/
|
||||
public isLookupName() {
|
||||
return (this.model instanceof DynamicLookupNameModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if search button is disabled
|
||||
*/
|
||||
public isSearchDisabled() {
|
||||
return isEmpty(this.firstInputValue) || this.editMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update model value with the typed text if vocabulary is not closed
|
||||
* @param event the typed text
|
||||
*/
|
||||
public onChange(event) {
|
||||
event.preventDefault();
|
||||
if (!this.model.vocabularyOptions.closed) {
|
||||
if (isNotEmpty(this.getCurrentValue())) {
|
||||
const currentValue = new FormFieldMetadataValueObject(this.getCurrentValue());
|
||||
if (!this.editMode) {
|
||||
this.updateModel(currentValue);
|
||||
}
|
||||
} else {
|
||||
this.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load more result entries
|
||||
*/
|
||||
public onScroll() {
|
||||
if (!this.loading && this.pageInfo.currentPage <= this.pageInfo.totalPages) {
|
||||
this.updatePageInfo(
|
||||
this.pageInfo.elementsPerPage,
|
||||
this.pageInfo.currentPage + 1,
|
||||
this.pageInfo.totalElements,
|
||||
this.pageInfo.totalPages
|
||||
);
|
||||
this.search();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update model value with selected entry
|
||||
* @param event the selected entry
|
||||
*/
|
||||
public onSelect(event) {
|
||||
this.updateModel(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the current value when dropdown toggle
|
||||
*/
|
||||
public openChange(isOpened: boolean) {
|
||||
if (!isOpened) {
|
||||
if (this.model.vocabularyOptions.closed && !this.hasAuthorityValue()) {
|
||||
this.setCurrentValue('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the model value
|
||||
*/
|
||||
public remove() {
|
||||
this.group.markAsPristine();
|
||||
this.dispatchUpdate(null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all changes
|
||||
*/
|
||||
public saveChanges() {
|
||||
if (isNotEmpty(this.getCurrentValue())) {
|
||||
const newValue = Object.assign(new VocabularyEntry(), this.model.value, {
|
||||
display: this.getCurrentValue(),
|
||||
value: this.getCurrentValue()
|
||||
});
|
||||
this.updateModel(newValue);
|
||||
} else {
|
||||
this.remove();
|
||||
}
|
||||
this.switchEditMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a stream of text values from the `<input>` element to the stream of the array of items
|
||||
* to display in the result list.
|
||||
*/
|
||||
public search() {
|
||||
this.optionsList = null;
|
||||
this.updatePageInfo(this.model.maxOptions, 1);
|
||||
this.loading = true;
|
||||
|
||||
this.subs.push(this.vocabularyService.getVocabularyEntriesByValue(
|
||||
this.getCurrentValue(),
|
||||
false,
|
||||
this.model.vocabularyOptions,
|
||||
this.pageInfo
|
||||
).pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
catchError(() =>
|
||||
observableOf(new PaginatedList(
|
||||
new PageInfo(),
|
||||
[]
|
||||
))
|
||||
),
|
||||
distinctUntilChanged())
|
||||
.subscribe((list: PaginatedList<VocabularyEntry>) => {
|
||||
this.optionsList = list.page;
|
||||
this.updatePageInfo(
|
||||
list.pageInfo.elementsPerPage,
|
||||
list.pageInfo.currentPage,
|
||||
list.pageInfo.totalElements,
|
||||
list.pageInfo.totalPages
|
||||
);
|
||||
this.loading = false;
|
||||
this.cdr.detectChanges();
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the edit mode flag
|
||||
*/
|
||||
public switchEditMode() {
|
||||
this.editMode = !this.editMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback functions for whenClickOnConfidenceNotAccepted event
|
||||
*/
|
||||
public whenClickOnConfidenceNotAccepted(sdRef: NgbDropdown, confidence: ConfidenceType) {
|
||||
if (!this.model.readOnly) {
|
||||
sdRef.open();
|
||||
this.search();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subs
|
||||
.filter((sub) => hasValue(sub))
|
||||
.forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current value with the given value.
|
||||
* @param value The value to set.
|
||||
* @param init Representing if is init value or not.
|
||||
*/
|
||||
public setCurrentValue(value: any, init = false) {
|
||||
if (init) {
|
||||
this.getInitValueFromModel()
|
||||
.subscribe((formValue: FormFieldMetadataValueObject) => this.setDisplayInputValue(formValue.display));
|
||||
} else if (hasValue(value)) {
|
||||
if (value instanceof FormFieldMetadataValueObject || value instanceof VocabularyEntry) {
|
||||
this.setDisplayInputValue(value.display);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected setDisplayInputValue(displayValue: string) {
|
||||
if (hasValue(displayValue)) {
|
||||
if (this.isLookupName()) {
|
||||
const values = displayValue.split((this.model as DynamicLookupNameModel).separator);
|
||||
|
||||
this.firstInputValue = (values[0] || '').trim();
|
||||
this.secondInputValue = (values[1] || '').trim();
|
||||
} else {
|
||||
this.firstInputValue = displayValue || '';
|
||||
}
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current text present in the input field(s)
|
||||
*/
|
||||
protected getCurrentValue(): string {
|
||||
let result = '';
|
||||
if (!this.isLookupName()) {
|
||||
@@ -96,6 +314,9 @@ export class DsDynamicLookupComponent extends DynamicFormControlComponent implem
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear text present in the input field(s)
|
||||
*/
|
||||
protected resetFields() {
|
||||
this.firstInputValue = '';
|
||||
if (this.isLookupName()) {
|
||||
@@ -103,173 +324,12 @@ export class DsDynamicLookupComponent extends DynamicFormControlComponent implem
|
||||
}
|
||||
}
|
||||
|
||||
protected setInputsValue(value) {
|
||||
if (hasValue(value)) {
|
||||
let displayValue = value;
|
||||
if (value instanceof FormFieldMetadataValueObject || value instanceof AuthorityValue) {
|
||||
displayValue = value.display;
|
||||
}
|
||||
|
||||
if (hasValue(displayValue)) {
|
||||
if (this.isLookupName()) {
|
||||
const values = displayValue.split((this.model as DynamicLookupNameModel).separator);
|
||||
|
||||
this.firstInputValue = (values[0] || '').trim();
|
||||
this.secondInputValue = (values[1] || '').trim();
|
||||
} else {
|
||||
this.firstInputValue = displayValue || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected updateModel(value) {
|
||||
this.group.markAsDirty();
|
||||
this.model.valueUpdates.next(value);
|
||||
this.setInputsValue(value);
|
||||
this.change.emit(value);
|
||||
this.dispatchUpdate(value);
|
||||
this.setCurrentValue(value);
|
||||
this.optionsList = null;
|
||||
this.pageInfo = null;
|
||||
}
|
||||
|
||||
public formatItemForInput(item: any, field: number): string {
|
||||
if (isUndefined(item) || isNull(item)) {
|
||||
return '';
|
||||
}
|
||||
return (typeof item === 'string') ? item : this.inputFormatter(item, field);
|
||||
}
|
||||
|
||||
public hasAuthorityValue() {
|
||||
return hasValue(this.model.value)
|
||||
&& this.model.value.hasAuthority();
|
||||
}
|
||||
|
||||
public hasEmptyValue() {
|
||||
return isNotEmpty(this.getCurrentValue());
|
||||
}
|
||||
|
||||
public clearFields() {
|
||||
// Clear inputs whether there is no results and authority is closed
|
||||
if (this.model.authorityOptions.closed) {
|
||||
this.resetFields();
|
||||
}
|
||||
}
|
||||
|
||||
public isEditDisabled() {
|
||||
return !this.hasAuthorityValue();
|
||||
}
|
||||
|
||||
public isInputDisabled() {
|
||||
return (this.model.authorityOptions.closed && this.hasAuthorityValue() && !this.editMode);
|
||||
}
|
||||
|
||||
public isLookupName() {
|
||||
return (this.model instanceof DynamicLookupNameModel);
|
||||
}
|
||||
|
||||
public isSearchDisabled() {
|
||||
return isEmpty(this.firstInputValue) || this.editMode;
|
||||
}
|
||||
|
||||
public onBlurEvent(event: Event) {
|
||||
this.blur.emit(event);
|
||||
}
|
||||
|
||||
public onFocusEvent(event) {
|
||||
this.focus.emit(event);
|
||||
}
|
||||
|
||||
public onChange(event) {
|
||||
event.preventDefault();
|
||||
if (!this.model.authorityOptions.closed) {
|
||||
if (isNotEmpty(this.getCurrentValue())) {
|
||||
const currentValue = new FormFieldMetadataValueObject(this.getCurrentValue());
|
||||
if (!this.editMode) {
|
||||
this.updateModel(currentValue);
|
||||
}
|
||||
} else {
|
||||
this.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public onScroll() {
|
||||
if (!this.loading && this.pageInfo.currentPage <= this.pageInfo.totalPages) {
|
||||
this.searchOptions.currentPage++;
|
||||
this.search();
|
||||
}
|
||||
}
|
||||
|
||||
public onSelect(event) {
|
||||
this.updateModel(event);
|
||||
}
|
||||
|
||||
public openChange(isOpened: boolean) {
|
||||
if (!isOpened) {
|
||||
if (this.model.authorityOptions.closed && !this.hasAuthorityValue()) {
|
||||
this.setInputsValue('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public remove() {
|
||||
this.group.markAsPristine();
|
||||
this.model.valueUpdates.next(null);
|
||||
this.change.emit(null);
|
||||
}
|
||||
|
||||
public saveChanges() {
|
||||
if (isNotEmpty(this.getCurrentValue())) {
|
||||
const newValue = Object.assign(new AuthorityValue(), this.model.value, {
|
||||
display: this.getCurrentValue(),
|
||||
value: this.getCurrentValue()
|
||||
});
|
||||
this.updateModel(newValue);
|
||||
} else {
|
||||
this.remove();
|
||||
}
|
||||
this.switchEditMode();
|
||||
}
|
||||
|
||||
public search() {
|
||||
this.optionsList = null;
|
||||
this.pageInfo = null;
|
||||
|
||||
// Query
|
||||
this.searchOptions.query = this.getCurrentValue();
|
||||
|
||||
this.loading = true;
|
||||
this.subs.push(this.authorityService.getEntriesByName(this.searchOptions).pipe(
|
||||
catchError(() => {
|
||||
const emptyResult = new IntegrationData(
|
||||
new PageInfo(),
|
||||
[]
|
||||
);
|
||||
return observableOf(emptyResult);
|
||||
}),
|
||||
distinctUntilChanged())
|
||||
.subscribe((object: IntegrationData) => {
|
||||
this.optionsList = object.payload;
|
||||
this.pageInfo = object.pageInfo;
|
||||
this.loading = false;
|
||||
this.cdr.detectChanges();
|
||||
}));
|
||||
}
|
||||
|
||||
public switchEditMode() {
|
||||
this.editMode = !this.editMode;
|
||||
}
|
||||
|
||||
public whenClickOnConfidenceNotAccepted(sdRef: NgbDropdown, confidence: ConfidenceType) {
|
||||
if (!this.model.readOnly) {
|
||||
sdRef.open();
|
||||
this.search();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subs
|
||||
.filter((sub) => hasValue(sub))
|
||||
.forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@
|
||||
</ul>
|
||||
</ng-template>
|
||||
|
||||
<div class="position-relative right-addon">
|
||||
<div *ngIf="!(isHierarchicalVocabulary() | async)" class="position-relative right-addon">
|
||||
<i *ngIf="searching" class="fas fa-circle-notch fa-spin fa-2x fa-fw text-primary position-absolute mt-1 p-0" aria-hidden="true"></i>
|
||||
<i *ngIf="!searching"
|
||||
dsAuthorityConfidenceState
|
||||
@@ -49,3 +49,20 @@
|
||||
|
||||
<div class="invalid-feedback" *ngIf="searchFailed">Sorry, suggestions could not be loaded.</div>
|
||||
</div>
|
||||
|
||||
<input *ngIf="(isHierarchicalVocabulary() | async)"
|
||||
class="form-control custom-select"
|
||||
[attr.autoComplete]="model.autoComplete"
|
||||
[class.is-invalid]="showErrorMessages"
|
||||
[dynamicId]="bindId && model.id"
|
||||
[name]="model.name"
|
||||
[placeholder]="model.placeholder"
|
||||
[readonly]="model.readOnly"
|
||||
[type]="model.inputType"
|
||||
[value]="currentValue?.display"
|
||||
(focus)="onFocus($event)"
|
||||
(change)="onChange($event)"
|
||||
(click)="openTree($event)"
|
||||
(keydown)="$event.preventDefault()"
|
||||
(keypress)="$event.preventDefault()"
|
||||
(keyup)="$event.preventDefault()">
|
@@ -16,3 +16,8 @@
|
||||
color: $dropdown-link-hover-color !important;
|
||||
background-color: $dropdown-link-hover-bg !important;
|
||||
}
|
||||
|
||||
.treeview .modal-body {
|
||||
max-height: 85vh !important;
|
||||
overflow-y: auto;
|
||||
}
|
@@ -0,0 +1,462 @@
|
||||
// Load the implementations that should be tested
|
||||
import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { ComponentFixture, fakeAsync, inject, TestBed, tick, } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { CdkTreeModule } from '@angular/cdk/tree';
|
||||
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
import { getTestScheduler } from 'jasmine-marbles';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { NgbModal, NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { DynamicFormLayoutService, DynamicFormsCoreModule, DynamicFormValidationService } from '@ng-dynamic-forms/core';
|
||||
import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { VocabularyOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
|
||||
import { VocabularyServiceStub } from '../../../../../testing/vocabulary-service.stub';
|
||||
import { DsDynamicOneboxComponent } from './dynamic-onebox.component';
|
||||
import { DynamicOneboxModel } from './dynamic-onebox.model';
|
||||
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
|
||||
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';
|
||||
import { VocabularyTreeviewComponent } from '../../../../../vocabulary-treeview/vocabulary-treeview.component';
|
||||
|
||||
export let ONEBOX_TEST_GROUP;
|
||||
|
||||
export let ONEBOX_TEST_MODEL_CONFIG;
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
|
||||
// Mock class for NgbModalRef
|
||||
export class MockNgbModalRef {
|
||||
componentInstance = {
|
||||
vocabularyOptions: undefined,
|
||||
preloadLevel: undefined,
|
||||
selectedItem: undefined
|
||||
};
|
||||
result: Promise<any> = new Promise((resolve, reject) => resolve(true));
|
||||
}
|
||||
|
||||
function init() {
|
||||
ONEBOX_TEST_GROUP = new FormGroup({
|
||||
onebox: new FormControl(),
|
||||
});
|
||||
|
||||
ONEBOX_TEST_MODEL_CONFIG = {
|
||||
vocabularyOptions: {
|
||||
closed: false,
|
||||
name: 'vocabulary'
|
||||
} as VocabularyOptions,
|
||||
disabled: false,
|
||||
id: 'onebox',
|
||||
label: 'Conference',
|
||||
minChars: 3,
|
||||
name: 'onebox',
|
||||
placeholder: 'Conference',
|
||||
readOnly: false,
|
||||
required: false,
|
||||
repeatable: false,
|
||||
value: undefined
|
||||
};
|
||||
}
|
||||
|
||||
describe('DsDynamicOneboxComponent test suite', () => {
|
||||
|
||||
let scheduler: TestScheduler;
|
||||
let testComp: TestComponent;
|
||||
let oneboxComponent: DsDynamicOneboxComponent;
|
||||
let testFixture: ComponentFixture<TestComponent>;
|
||||
let oneboxCompFixture: ComponentFixture<DsDynamicOneboxComponent>;
|
||||
let vocabularyServiceStub: any;
|
||||
let modalService: any;
|
||||
let html;
|
||||
let modal;
|
||||
const vocabulary = {
|
||||
id: 'vocabulary',
|
||||
name: 'vocabulary',
|
||||
scrollable: true,
|
||||
hierarchical: false,
|
||||
preloadLevel: 0,
|
||||
type: 'vocabulary',
|
||||
_links: {
|
||||
self: {
|
||||
url: 'self'
|
||||
},
|
||||
entries: {
|
||||
url: 'entries'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hierarchicalVocabulary = {
|
||||
id: 'hierarchicalVocabulary',
|
||||
name: 'hierarchicalVocabulary',
|
||||
scrollable: true,
|
||||
hierarchical: true,
|
||||
preloadLevel: 2,
|
||||
type: 'vocabulary',
|
||||
_links: {
|
||||
self: {
|
||||
url: 'self'
|
||||
},
|
||||
entries: {
|
||||
url: 'entries'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// async beforeEach
|
||||
beforeEach(() => {
|
||||
vocabularyServiceStub = new VocabularyServiceStub();
|
||||
|
||||
modal = jasmine.createSpyObj('modal',
|
||||
{
|
||||
open: jasmine.createSpy('open'),
|
||||
close: jasmine.createSpy('close'),
|
||||
dismiss: jasmine.createSpy('dismiss'),
|
||||
}
|
||||
);
|
||||
init();
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
DynamicFormsCoreModule,
|
||||
DynamicFormsNGBootstrapUIModule,
|
||||
FormsModule,
|
||||
NgbModule,
|
||||
ReactiveFormsModule,
|
||||
TranslateModule.forRoot(),
|
||||
CdkTreeModule
|
||||
],
|
||||
declarations: [
|
||||
DsDynamicOneboxComponent,
|
||||
TestComponent,
|
||||
AuthorityConfidenceStateDirective,
|
||||
ObjNgFor,
|
||||
VocabularyTreeviewComponent
|
||||
], // declare the test component
|
||||
providers: [
|
||||
ChangeDetectorRef,
|
||||
DsDynamicOneboxComponent,
|
||||
{ provide: VocabularyService, useValue: vocabularyServiceStub },
|
||||
{ provide: DynamicFormLayoutService, useValue: {} },
|
||||
{ provide: DynamicFormValidationService, useValue: {} },
|
||||
{ provide: NgbModal, useValue: modal }
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
}).compileComponents();
|
||||
|
||||
});
|
||||
|
||||
describe('', () => {
|
||||
// synchronous beforeEach
|
||||
beforeEach(() => {
|
||||
html = `
|
||||
<ds-dynamic-onebox [bindId]="bindId"
|
||||
[group]="group"
|
||||
[model]="model"
|
||||
(blur)="onBlur($event)"
|
||||
(change)="onValueChange($event)"
|
||||
(focus)="onFocus($event)"></ds-dynamic-onebox>`;
|
||||
|
||||
spyOn(vocabularyServiceStub, 'findVocabularyById').and.returnValue(createSuccessfulRemoteDataObject$(vocabulary));
|
||||
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||
testComp = testFixture.componentInstance;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
testFixture.destroy();
|
||||
});
|
||||
it('should create DsDynamicOneboxComponent', inject([DsDynamicOneboxComponent], (app: DsDynamicOneboxComponent) => {
|
||||
expect(app).toBeDefined();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Has not hierarchical vocabulary', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(vocabularyServiceStub, 'findVocabularyById').and.returnValue(createSuccessfulRemoteDataObject$(vocabulary));
|
||||
});
|
||||
|
||||
describe('when init model value is empty', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
oneboxCompFixture = TestBed.createComponent(DsDynamicOneboxComponent);
|
||||
oneboxComponent = oneboxCompFixture.componentInstance; // FormComponent test instance
|
||||
oneboxComponent.group = ONEBOX_TEST_GROUP;
|
||||
oneboxComponent.model = new DynamicOneboxModel(ONEBOX_TEST_MODEL_CONFIG);
|
||||
oneboxCompFixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
oneboxCompFixture.destroy();
|
||||
oneboxComponent = null;
|
||||
});
|
||||
|
||||
it('should init component properly', () => {
|
||||
expect(oneboxComponent.currentValue).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should search when 3+ characters typed', fakeAsync(() => {
|
||||
|
||||
spyOn((oneboxComponent as any).vocabularyService, 'getVocabularyEntriesByValue').and.callThrough();
|
||||
|
||||
oneboxComponent.search(observableOf('test')).subscribe();
|
||||
|
||||
tick(300);
|
||||
oneboxCompFixture.detectChanges();
|
||||
|
||||
expect((oneboxComponent as any).vocabularyService.getVocabularyEntriesByValue).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should set model.value on input type when VocabularyOptions.closed is false', () => {
|
||||
const inputDe = oneboxCompFixture.debugElement.query(By.css('input.form-control'));
|
||||
const inputElement = inputDe.nativeElement;
|
||||
|
||||
inputElement.value = 'test value';
|
||||
inputElement.dispatchEvent(new Event('input'));
|
||||
|
||||
expect(oneboxComponent.inputValue).toEqual(new FormFieldMetadataValueObject('test value'))
|
||||
|
||||
});
|
||||
|
||||
it('should not set model.value on input type when VocabularyOptions.closed is true', () => {
|
||||
oneboxComponent.model.vocabularyOptions.closed = true;
|
||||
oneboxCompFixture.detectChanges();
|
||||
const inputDe = oneboxCompFixture.debugElement.query(By.css('input.form-control'));
|
||||
const inputElement = inputDe.nativeElement;
|
||||
|
||||
inputElement.value = 'test value';
|
||||
inputElement.dispatchEvent(new Event('input'));
|
||||
|
||||
expect(oneboxComponent.model.value).not.toBeDefined();
|
||||
|
||||
});
|
||||
|
||||
it('should emit blur Event onBlur when popup is closed', () => {
|
||||
spyOn(oneboxComponent.blur, 'emit');
|
||||
spyOn(oneboxComponent.instance, 'isPopupOpen').and.returnValue(false);
|
||||
oneboxComponent.onBlur(new Event('blur'));
|
||||
expect(oneboxComponent.blur.emit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not emit blur Event onBlur when popup is opened', () => {
|
||||
spyOn(oneboxComponent.blur, 'emit');
|
||||
spyOn(oneboxComponent.instance, 'isPopupOpen').and.returnValue(true);
|
||||
const input = oneboxCompFixture.debugElement.query(By.css('input'));
|
||||
|
||||
input.nativeElement.blur();
|
||||
expect(oneboxComponent.blur.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit change Event onBlur when VocabularyOptions.closed is false and inputValue is changed', () => {
|
||||
oneboxComponent.inputValue = 'test value';
|
||||
oneboxCompFixture.detectChanges();
|
||||
spyOn(oneboxComponent.blur, 'emit');
|
||||
spyOn(oneboxComponent.change, 'emit');
|
||||
spyOn(oneboxComponent.instance, 'isPopupOpen').and.returnValue(false);
|
||||
oneboxComponent.onBlur(new Event('blur',));
|
||||
expect(oneboxComponent.change.emit).toHaveBeenCalled();
|
||||
expect(oneboxComponent.blur.emit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not emit change Event onBlur when VocabularyOptions.closed is false and inputValue is not changed', () => {
|
||||
oneboxComponent.inputValue = 'test value';
|
||||
oneboxComponent.model = new DynamicOneboxModel(ONEBOX_TEST_MODEL_CONFIG);
|
||||
(oneboxComponent.model as any).value = 'test value';
|
||||
oneboxCompFixture.detectChanges();
|
||||
spyOn(oneboxComponent.blur, 'emit');
|
||||
spyOn(oneboxComponent.change, 'emit');
|
||||
spyOn(oneboxComponent.instance, 'isPopupOpen').and.returnValue(false);
|
||||
oneboxComponent.onBlur(new Event('blur',));
|
||||
expect(oneboxComponent.change.emit).not.toHaveBeenCalled();
|
||||
expect(oneboxComponent.blur.emit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not emit change Event onBlur when VocabularyOptions.closed is false and inputValue is null', () => {
|
||||
oneboxComponent.inputValue = null;
|
||||
oneboxComponent.model = new DynamicOneboxModel(ONEBOX_TEST_MODEL_CONFIG);
|
||||
(oneboxComponent.model as any).value = 'test value';
|
||||
oneboxCompFixture.detectChanges();
|
||||
spyOn(oneboxComponent.blur, 'emit');
|
||||
spyOn(oneboxComponent.change, 'emit');
|
||||
spyOn(oneboxComponent.instance, 'isPopupOpen').and.returnValue(false);
|
||||
oneboxComponent.onBlur(new Event('blur',));
|
||||
expect(oneboxComponent.change.emit).not.toHaveBeenCalled();
|
||||
expect(oneboxComponent.blur.emit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit focus Event onFocus', () => {
|
||||
spyOn(oneboxComponent.focus, 'emit');
|
||||
oneboxComponent.onFocus(new Event('focus'));
|
||||
expect(oneboxComponent.focus.emit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when init model value is not empty', () => {
|
||||
beforeEach(() => {
|
||||
oneboxCompFixture = TestBed.createComponent(DsDynamicOneboxComponent);
|
||||
oneboxComponent = oneboxCompFixture.componentInstance; // FormComponent test instance
|
||||
oneboxComponent.group = ONEBOX_TEST_GROUP;
|
||||
oneboxComponent.model = new DynamicOneboxModel(ONEBOX_TEST_MODEL_CONFIG);
|
||||
const entry = observableOf(Object.assign(new VocabularyEntry(), {
|
||||
authority: null,
|
||||
value: 'test',
|
||||
display: 'testDisplay'
|
||||
}));
|
||||
spyOn((oneboxComponent as any).vocabularyService, 'getVocabularyEntryByValue').and.returnValue(entry);
|
||||
spyOn((oneboxComponent as any).vocabularyService, 'getVocabularyEntryByID').and.returnValue(entry);
|
||||
(oneboxComponent.model as any).value = new FormFieldMetadataValueObject('test', null, null, 'testDisplay');
|
||||
oneboxCompFixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
oneboxCompFixture.destroy();
|
||||
oneboxComponent = null;
|
||||
});
|
||||
|
||||
it('should init component properly', fakeAsync(() => {
|
||||
tick();
|
||||
expect(oneboxComponent.currentValue).toEqual(new FormFieldMetadataValueObject('test', null, null, 'testDisplay'));
|
||||
expect((oneboxComponent as any).vocabularyService.getVocabularyEntryByValue).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should emit change Event onChange and currentValue is empty', () => {
|
||||
oneboxComponent.currentValue = null;
|
||||
spyOn(oneboxComponent.change, 'emit');
|
||||
oneboxComponent.onChange(new Event('change'));
|
||||
expect(oneboxComponent.change.emit).toHaveBeenCalled();
|
||||
expect(oneboxComponent.model.value).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when init model value is not empty and has authority', () => {
|
||||
beforeEach(() => {
|
||||
oneboxCompFixture = TestBed.createComponent(DsDynamicOneboxComponent);
|
||||
oneboxComponent = oneboxCompFixture.componentInstance; // FormComponent test instance
|
||||
oneboxComponent.group = ONEBOX_TEST_GROUP;
|
||||
oneboxComponent.model = new DynamicOneboxModel(ONEBOX_TEST_MODEL_CONFIG);
|
||||
const entry = observableOf(Object.assign(new VocabularyEntry(), {
|
||||
authority: 'test001',
|
||||
value: 'test001',
|
||||
display: 'test'
|
||||
}));
|
||||
spyOn((oneboxComponent as any).vocabularyService, 'getVocabularyEntryByValue').and.returnValue(entry);
|
||||
spyOn((oneboxComponent as any).vocabularyService, 'getVocabularyEntryByID').and.returnValue(entry);
|
||||
(oneboxComponent.model as any).value = new FormFieldMetadataValueObject('test', null, 'test001');
|
||||
oneboxCompFixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
oneboxCompFixture.destroy();
|
||||
oneboxComponent = null;
|
||||
});
|
||||
|
||||
it('should init component properly', fakeAsync(() => {
|
||||
tick();
|
||||
expect(oneboxComponent.currentValue).toEqual(new FormFieldMetadataValueObject('test001', null, 'test001', 'test'));
|
||||
expect((oneboxComponent as any).vocabularyService.getVocabularyEntryByID).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should emit change Event onChange and currentValue is empty', () => {
|
||||
oneboxComponent.currentValue = null;
|
||||
spyOn(oneboxComponent.change, 'emit');
|
||||
oneboxComponent.onChange(new Event('change'));
|
||||
expect(oneboxComponent.change.emit).toHaveBeenCalled();
|
||||
expect(oneboxComponent.model.value).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Has hierarchical vocabulary', () => {
|
||||
beforeEach(() => {
|
||||
scheduler = getTestScheduler();
|
||||
spyOn(vocabularyServiceStub, 'findVocabularyById').and.returnValue(createSuccessfulRemoteDataObject$(hierarchicalVocabulary));
|
||||
oneboxCompFixture = TestBed.createComponent(DsDynamicOneboxComponent);
|
||||
oneboxComponent = oneboxCompFixture.componentInstance; // FormComponent test instance
|
||||
modalService = TestBed.get(NgbModal);
|
||||
modalService.open.and.returnValue(new MockNgbModalRef());
|
||||
});
|
||||
|
||||
describe('when init model value is empty', () => {
|
||||
beforeEach(() => {
|
||||
oneboxComponent.group = ONEBOX_TEST_GROUP;
|
||||
oneboxComponent.model = new DynamicOneboxModel(ONEBOX_TEST_MODEL_CONFIG);
|
||||
oneboxCompFixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
oneboxCompFixture.destroy();
|
||||
oneboxComponent = null;
|
||||
});
|
||||
|
||||
it('should init component properly', fakeAsync(() => {
|
||||
tick();
|
||||
expect(oneboxComponent.currentValue).not.toBeDefined();
|
||||
}));
|
||||
|
||||
it('should open tree properly', (done) => {
|
||||
scheduler.schedule(() => oneboxComponent.openTree(new Event('click')));
|
||||
scheduler.flush();
|
||||
|
||||
expect((oneboxComponent as any).modalService.open).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when init model value is not empty', () => {
|
||||
beforeEach(() => {
|
||||
oneboxComponent.group = ONEBOX_TEST_GROUP;
|
||||
oneboxComponent.model = new DynamicOneboxModel(ONEBOX_TEST_MODEL_CONFIG);
|
||||
const entry = observableOf(Object.assign(new VocabularyEntry(), {
|
||||
authority: null,
|
||||
value: 'test',
|
||||
display: 'testDisplay'
|
||||
}));
|
||||
spyOn((oneboxComponent as any).vocabularyService, 'getVocabularyEntryByValue').and.returnValue(entry);
|
||||
spyOn((oneboxComponent as any).vocabularyService, 'getVocabularyEntryByID').and.returnValue(entry);
|
||||
(oneboxComponent.model as any).value = new FormFieldMetadataValueObject('test', null, null, 'testDisplay');
|
||||
oneboxCompFixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
oneboxCompFixture.destroy();
|
||||
oneboxComponent = null;
|
||||
});
|
||||
|
||||
it('should init component properly', fakeAsync(() => {
|
||||
tick();
|
||||
expect(oneboxComponent.currentValue).toEqual(new FormFieldMetadataValueObject('test', null, null, 'testDisplay'));
|
||||
expect((oneboxComponent as any).vocabularyService.getVocabularyEntryByValue).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should open tree properly', (done) => {
|
||||
scheduler.schedule(() => oneboxComponent.openTree(new Event('click')));
|
||||
scheduler.flush();
|
||||
|
||||
expect((oneboxComponent as any).modalService.open).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// declare a test component
|
||||
@Component({
|
||||
selector: 'ds-test-cmp',
|
||||
template: ``
|
||||
})
|
||||
class TestComponent {
|
||||
|
||||
group: FormGroup = ONEBOX_TEST_GROUP;
|
||||
|
||||
model = new DynamicOneboxModel(ONEBOX_TEST_MODEL_CONFIG);
|
||||
|
||||
}
|
||||
|
||||
/* tslint:enable:max-classes-per-file */
|
@@ -0,0 +1,278 @@
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
|
||||
import {
|
||||
catchError,
|
||||
debounceTime,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
map,
|
||||
merge,
|
||||
switchMap,
|
||||
take,
|
||||
tap
|
||||
} from 'rxjs/operators';
|
||||
import { Observable, of as observableOf, Subject, Subscription } from 'rxjs';
|
||||
import { NgbModal, NgbModalRef, NgbTypeahead, NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
|
||||
import { DynamicOneboxModel } from './dynamic-onebox.model';
|
||||
import { hasValue, isEmpty, isNotEmpty, isNotNull } from '../../../../../empty.util';
|
||||
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
|
||||
import { ConfidenceType } from '../../../../../../core/shared/confidence-type';
|
||||
import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators';
|
||||
import { PaginatedList } from '../../../../../../core/data/paginated-list';
|
||||
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
|
||||
import { PageInfo } from '../../../../../../core/shared/page-info.model';
|
||||
import { DsDynamicVocabularyComponent } from '../dynamic-vocabulary.component';
|
||||
import { Vocabulary } from '../../../../../../core/submission/vocabularies/models/vocabulary.model';
|
||||
import { VocabularyTreeviewComponent } from '../../../../../vocabulary-treeview/vocabulary-treeview.component';
|
||||
import { VocabularyEntryDetail } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
||||
|
||||
/**
|
||||
* Component representing a onebox input field.
|
||||
* If field has a Hierarchical Vocabulary configured, it's rendered with vocabulary tree
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-dynamic-onebox',
|
||||
styleUrls: ['./dynamic-onebox.component.scss'],
|
||||
templateUrl: './dynamic-onebox.component.html'
|
||||
})
|
||||
export class DsDynamicOneboxComponent extends DsDynamicVocabularyComponent implements OnInit {
|
||||
@Input() bindId = true;
|
||||
@Input() group: FormGroup;
|
||||
@Input() model: DynamicOneboxModel;
|
||||
|
||||
@Output() blur: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output() change: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output() focus: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@ViewChild('instance', { static: false }) instance: NgbTypeahead;
|
||||
|
||||
pageInfo: PageInfo = new PageInfo();
|
||||
searching = false;
|
||||
searchFailed = false;
|
||||
hideSearchingWhenUnsubscribed$ = new Observable(() => () => this.changeSearchingStatus(false));
|
||||
click$ = new Subject<string>();
|
||||
currentValue: any;
|
||||
inputValue: any;
|
||||
preloadLevel: number;
|
||||
|
||||
private vocabulary$: Observable<Vocabulary>;
|
||||
private isHierarchicalVocabulary$: Observable<boolean>;
|
||||
private subs: Subscription[] = [];
|
||||
|
||||
constructor(protected vocabularyService: VocabularyService,
|
||||
protected cdr: ChangeDetectorRef,
|
||||
protected layoutService: DynamicFormLayoutService,
|
||||
protected modalService: NgbModal,
|
||||
protected validationService: DynamicFormValidationService
|
||||
) {
|
||||
super(vocabularyService, layoutService, validationService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an item from the result list to a `string` to display in the `<input>` field.
|
||||
*/
|
||||
formatter = (x: { display: string }) => {
|
||||
return (typeof x === 'object') ? x.display : x
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a stream of text values from the `<input>` element to the stream of the array of items
|
||||
* to display in the onebox popup.
|
||||
*/
|
||||
search = (text$: Observable<string>) => {
|
||||
return text$.pipe(
|
||||
merge(this.click$),
|
||||
debounceTime(300),
|
||||
distinctUntilChanged(),
|
||||
tap(() => this.changeSearchingStatus(true)),
|
||||
switchMap((term) => {
|
||||
if (term === '' || term.length < this.model.minChars) {
|
||||
return observableOf({ list: [] });
|
||||
} else {
|
||||
return this.vocabularyService.getVocabularyEntriesByValue(
|
||||
term,
|
||||
false,
|
||||
this.model.vocabularyOptions,
|
||||
this.pageInfo).pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
tap(() => this.searchFailed = false),
|
||||
catchError(() => {
|
||||
this.searchFailed = true;
|
||||
return observableOf(new PaginatedList(
|
||||
new PageInfo(),
|
||||
[]
|
||||
));
|
||||
}));
|
||||
}
|
||||
}),
|
||||
map((list: PaginatedList<VocabularyEntry>) => list.page),
|
||||
tap(() => this.changeSearchingStatus(false)),
|
||||
merge(this.hideSearchingWhenUnsubscribed$)
|
||||
)
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the component, setting up the init form value
|
||||
*/
|
||||
ngOnInit() {
|
||||
if (this.model.value) {
|
||||
this.setCurrentValue(this.model.value, true);
|
||||
}
|
||||
|
||||
this.vocabulary$ = this.vocabularyService.findVocabularyById(this.model.vocabularyOptions.name).pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
|
||||
this.isHierarchicalVocabulary$ = this.vocabulary$.pipe(
|
||||
map((result: Vocabulary) => result.hierarchical)
|
||||
);
|
||||
|
||||
this.subs.push(this.group.get(this.model.id).valueChanges.pipe(
|
||||
filter((value) => this.currentValue !== value))
|
||||
.subscribe((value) => {
|
||||
this.setCurrentValue(this.model.value);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the searching status
|
||||
* @param status
|
||||
*/
|
||||
changeSearchingStatus(status: boolean) {
|
||||
this.searching = status;
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if configured vocabulary is Hierarchical or not
|
||||
*/
|
||||
isHierarchicalVocabulary(): Observable<boolean> {
|
||||
return this.isHierarchicalVocabulary$;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the input value with a FormFieldMetadataValueObject
|
||||
* @param event
|
||||
*/
|
||||
onInput(event) {
|
||||
if (!this.model.vocabularyOptions.closed && isNotEmpty(event.target.value)) {
|
||||
this.inputValue = new FormFieldMetadataValueObject(event.target.value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a blur event containing a given value.
|
||||
* @param event The value to emit.
|
||||
*/
|
||||
onBlur(event: Event) {
|
||||
if (!this.instance.isPopupOpen()) {
|
||||
if (!this.model.vocabularyOptions.closed && isNotEmpty(this.inputValue)) {
|
||||
if (isNotNull(this.inputValue) && this.model.value !== this.inputValue) {
|
||||
this.dispatchUpdate(this.inputValue);
|
||||
}
|
||||
this.inputValue = null;
|
||||
}
|
||||
this.blur.emit(event);
|
||||
} else {
|
||||
// prevent on blur propagation if typeahed suggestions are showed
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
// set focus on input again, this is to avoid to lose changes when no suggestion is selected
|
||||
(event.target as HTMLInputElement).focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates model value with the current value
|
||||
* @param event The change event.
|
||||
*/
|
||||
onChange(event: Event) {
|
||||
event.stopPropagation();
|
||||
if (isEmpty(this.currentValue)) {
|
||||
this.dispatchUpdate(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates current value and model value with the selected value.
|
||||
* @param event The value to set.
|
||||
*/
|
||||
onSelectItem(event: NgbTypeaheadSelectItemEvent) {
|
||||
this.inputValue = null;
|
||||
this.setCurrentValue(event.item);
|
||||
this.dispatchUpdate(event.item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open modal to show tree for hierarchical vocabulary
|
||||
* @param event The click event fired
|
||||
*/
|
||||
openTree(event) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
this.subs.push(this.vocabulary$.pipe(
|
||||
map((vocabulary: Vocabulary) => vocabulary.preloadLevel),
|
||||
take(1)
|
||||
).subscribe((preloadLevel) => {
|
||||
const modalRef: NgbModalRef = this.modalService.open(VocabularyTreeviewComponent, { size: 'lg', windowClass: 'treeview' });
|
||||
modalRef.componentInstance.vocabularyOptions = this.model.vocabularyOptions;
|
||||
modalRef.componentInstance.preloadLevel = preloadLevel;
|
||||
modalRef.componentInstance.selectedItem = this.currentValue ? this.currentValue : '';
|
||||
modalRef.result.then((result: VocabularyEntryDetail) => {
|
||||
if (result) {
|
||||
this.currentValue = result;
|
||||
this.dispatchUpdate(result);
|
||||
}
|
||||
}, () => {
|
||||
return;
|
||||
});
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback functions for whenClickOnConfidenceNotAccepted event
|
||||
*/
|
||||
public whenClickOnConfidenceNotAccepted(confidence: ConfidenceType) {
|
||||
if (!this.model.readOnly) {
|
||||
this.click$.next(this.formatter(this.currentValue));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current value with the given value.
|
||||
* @param value The value to set.
|
||||
* @param init Representing if is init value or not.
|
||||
*/
|
||||
setCurrentValue(value: any, init = false): void {
|
||||
let result: string;
|
||||
if (init) {
|
||||
this.getInitValueFromModel()
|
||||
.subscribe((formValue: FormFieldMetadataValueObject) => {
|
||||
this.currentValue = formValue;
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
} else {
|
||||
if (isEmpty(value)) {
|
||||
result = '';
|
||||
} else {
|
||||
result = value.value;
|
||||
}
|
||||
|
||||
this.currentValue = result;
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs
|
||||
.filter((sub) => hasValue(sub))
|
||||
.forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
}
|
@@ -1,19 +1,19 @@
|
||||
import { AUTOCOMPLETE_OFF, DynamicFormControlLayout, serializable } from '@ng-dynamic-forms/core';
|
||||
import { DsDynamicInputModel, DsDynamicInputModelConfig } from '../ds-dynamic-input.model';
|
||||
|
||||
export const DYNAMIC_FORM_CONTROL_TYPE_TYPEAHEAD = 'TYPEAHEAD';
|
||||
export const DYNAMIC_FORM_CONTROL_TYPE_ONEBOX = 'ONEBOX';
|
||||
|
||||
export interface DsDynamicTypeaheadModelConfig extends DsDynamicInputModelConfig {
|
||||
export interface DsDynamicOneboxModelConfig extends DsDynamicInputModelConfig {
|
||||
minChars?: number;
|
||||
value?: any;
|
||||
}
|
||||
|
||||
export class DynamicTypeaheadModel extends DsDynamicInputModel {
|
||||
export class DynamicOneboxModel extends DsDynamicInputModel {
|
||||
|
||||
@serializable() minChars: number;
|
||||
@serializable() readonly type: string = DYNAMIC_FORM_CONTROL_TYPE_TYPEAHEAD;
|
||||
@serializable() readonly type: string = DYNAMIC_FORM_CONTROL_TYPE_ONEBOX;
|
||||
|
||||
constructor(config: DsDynamicTypeaheadModelConfig, layout?: DynamicFormControlLayout) {
|
||||
constructor(config: DsDynamicOneboxModelConfig, layout?: DynamicFormControlLayout) {
|
||||
|
||||
super(config, layout);
|
||||
|
@@ -2,9 +2,12 @@
|
||||
import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { async, ComponentFixture, inject, TestBed, } from '@angular/core/testing';
|
||||
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { Store, StoreModule } from '@ngrx/store';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
|
||||
|
||||
import { DsDynamicRelationGroupComponent } from './dynamic-relation-group.components';
|
||||
import { DynamicRelationGroupModel, DynamicRelationGroupModelConfig } from './dynamic-relation-group.model';
|
||||
@@ -13,18 +16,14 @@ import { FormFieldModel } from '../../../models/form-field.model';
|
||||
import { FormBuilderService } from '../../../form-builder.service';
|
||||
import { FormService } from '../../../../form.service';
|
||||
import { FormComponent } from '../../../../form.component';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { Chips } from '../../../../../chips/models/chips.model';
|
||||
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
|
||||
import { DsDynamicInputModel } from '../ds-dynamic-input.model';
|
||||
import { createTestComponent } from '../../../../../testing/utils.test';
|
||||
import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
|
||||
import { AuthorityService } from '../../../../../../core/integration/authority.service';
|
||||
import { AuthorityServiceStub } from '../../../../../testing/authority-service.stub';
|
||||
import { Store, StoreModule } from '@ngrx/store';
|
||||
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
|
||||
import { VocabularyServiceStub } from '../../../../../testing/vocabulary-service.stub';
|
||||
import { StoreMock } from '../../../../../testing/store.mock';
|
||||
import { FormRowModel } from '../../../../../../core/config/models/config-submission-form.model';
|
||||
import { GlobalConfig } from '../../../../../../../config/global-config.interface';
|
||||
import { storeModuleConfig } from '../../../../../../app.reducer';
|
||||
|
||||
export let FORM_GROUP_TEST_MODEL_CONFIG;
|
||||
@@ -47,7 +46,7 @@ function init() {
|
||||
mandatoryMessage: 'Required field!',
|
||||
repeatable: false,
|
||||
selectableMetadata: [{
|
||||
authority: 'RPAuthority',
|
||||
controlledVocabulary: 'RPAuthority',
|
||||
closed: false,
|
||||
metadata: 'dc.contributor.author'
|
||||
}],
|
||||
@@ -61,7 +60,7 @@ function init() {
|
||||
mandatory: 'false',
|
||||
repeatable: false,
|
||||
selectableMetadata: [{
|
||||
authority: 'OUAuthority',
|
||||
controlledVocabulary: 'OUAuthority',
|
||||
closed: false,
|
||||
metadata: 'local.contributor.affiliation'
|
||||
}]
|
||||
@@ -129,7 +128,7 @@ describe('DsDynamicRelationGroupComponent test suite', () => {
|
||||
FormBuilderService,
|
||||
FormComponent,
|
||||
FormService,
|
||||
{ provide: AuthorityService, useValue: new AuthorityServiceStub() },
|
||||
{ provide: VocabularyService, useValue: new VocabularyServiceStub() },
|
||||
{ provide: Store, useClass: StoreMock }
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
|
@@ -1,14 +1,4 @@
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Inject,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
import { combineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
|
||||
@@ -33,14 +23,16 @@ import { hasValue, isEmpty, isNotEmpty, isNotNull } from '../../../../../empty.u
|
||||
import { shrinkInOut } from '../../../../../animations/shrink';
|
||||
import { ChipsItem } from '../../../../../chips/models/chips-item.model';
|
||||
import { hasOnlyEmptyProperties } from '../../../../../object.util';
|
||||
import { IntegrationSearchOptions } from '../../../../../../core/integration/models/integration-options.model';
|
||||
import { AuthorityService } from '../../../../../../core/integration/authority.service';
|
||||
import { IntegrationData } from '../../../../../../core/integration/integration-data';
|
||||
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
|
||||
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
|
||||
import { AuthorityValue } from '../../../../../../core/integration/models/authority.value';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
import { PLACEHOLDER_PARENT_METADATA } from '../../ds-dynamic-form-constants';
|
||||
import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators';
|
||||
import { VocabularyEntryDetail } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
||||
|
||||
/**
|
||||
* Component representing a group input field
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-dynamic-relation-group',
|
||||
styleUrls: ['./dynamic-relation-group.component.scss'],
|
||||
@@ -65,9 +57,9 @@ export class DsDynamicRelationGroupComponent extends DynamicFormControlComponent
|
||||
private selectedChipItem: ChipsItem;
|
||||
private subs: Subscription[] = [];
|
||||
|
||||
@ViewChild('formRef', {static: false}) private formRef: FormComponent;
|
||||
@ViewChild('formRef', { static: false }) private formRef: FormComponent;
|
||||
|
||||
constructor(private authorityService: AuthorityService,
|
||||
constructor(private vocabularyService: VocabularyService,
|
||||
private formBuilderService: FormBuilderService,
|
||||
private formService: FormService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
@@ -178,6 +170,12 @@ export class DsDynamicRelationGroupComponent extends DynamicFormControlComponent
|
||||
this.clear();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs
|
||||
.filter((sub) => hasValue(sub))
|
||||
.forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
private addToChips() {
|
||||
if (!this.formRef.formGroup.valid) {
|
||||
this.formService.validateAllFormFields(this.formRef.formGroup);
|
||||
@@ -236,20 +234,16 @@ export class DsDynamicRelationGroupComponent extends DynamicFormControlComponent
|
||||
if (isObject(valueObj[fieldName]) && valueObj[fieldName].hasAuthority() && isNotEmpty(valueObj[fieldName].authority)) {
|
||||
const fieldId = fieldName.replace(/\./g, '_');
|
||||
const model = this.formBuilderService.findById(fieldId, this.formModel);
|
||||
const searchOptions: IntegrationSearchOptions = new IntegrationSearchOptions(
|
||||
(model as any).authorityOptions.scope,
|
||||
(model as any).authorityOptions.name,
|
||||
(model as any).authorityOptions.metadata,
|
||||
return$ = this.vocabularyService.findEntryDetailById(
|
||||
valueObj[fieldName].authority,
|
||||
(model as any).maxOptions,
|
||||
1);
|
||||
|
||||
return$ = this.authorityService.getEntryByValue(searchOptions).pipe(
|
||||
map((result: IntegrationData) => Object.assign(
|
||||
(model as any).vocabularyOptions.name
|
||||
).pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
map((entryDetail: VocabularyEntryDetail) => Object.assign(
|
||||
new FormFieldMetadataValueObject(),
|
||||
valueObj[fieldName],
|
||||
{
|
||||
otherInformation: (result.payload[0] as AuthorityValue).otherInformation
|
||||
otherInformation: entryDetail.otherInformation
|
||||
})
|
||||
));
|
||||
} else {
|
||||
@@ -316,10 +310,4 @@ export class DsDynamicRelationGroupComponent extends DynamicFormControlComponent
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs
|
||||
.filter((sub) => hasValue(sub))
|
||||
.forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,8 @@
|
||||
<div #sdRef="ngbDropdown" ngbDropdown class="input-group w-100">
|
||||
<input #inputElement class="form-control"
|
||||
<div #sdRef="ngbDropdown" ngbDropdown class="w-100">
|
||||
<input ngbDropdownToggle class="form-control custom-select"
|
||||
[attr.autoComplete]="model.autoComplete"
|
||||
[class.is-invalid]="showErrorMessages"
|
||||
[dynamicId]="bindId && model.id"
|
||||
[name]="model.name"
|
||||
[readonly]="model.readOnly"
|
||||
[type]="model.inputType"
|
||||
@@ -10,11 +11,6 @@
|
||||
(click)="$event.stopPropagation(); openDropdown(sdRef);"
|
||||
(focus)="onFocus($event)"
|
||||
(keypress)="$event.preventDefault()">
|
||||
<button #buttonElement aria-describedby="collectionControlsMenuLabel"
|
||||
class="ds-form-input-btn btn btn-outline-primary"
|
||||
ngbDropdownToggle
|
||||
[disabled]="model.readOnly"
|
||||
(click)="onToggle(sdRef); $event.stopPropagation();"></button>
|
||||
|
||||
<div ngbDropdownMenu
|
||||
class="dropdown-menu scrollable-dropdown-menu w-100"
|
||||
@@ -30,7 +26,7 @@
|
||||
[scrollWindow]="false">
|
||||
|
||||
<button class="dropdown-item disabled" *ngIf="optionsList && optionsList.length == 0">{{'form.no-results' | translate}}</button>
|
||||
<button class="dropdown-item collection-item text-truncate" *ngFor="let listEntry of optionsList" (click)="onSelect(listEntry)" title="{{ listEntry.display }}">
|
||||
<button class="dropdown-item collection-item text-truncate" *ngFor="let listEntry of optionsList" (click)="onSelect(listEntry); sdRef.close()" title="{{ listEntry.display }}">
|
||||
{{inputFormatter(listEntry)}}
|
||||
</button>
|
||||
<div class="scrollable-dropdown-loading text-center" *ngIf="loading"><p>{{'form.loading' | translate}}</p></div>
|
||||
|
@@ -9,12 +9,12 @@ import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||
import { DynamicFormLayoutService, DynamicFormsCoreModule, DynamicFormValidationService } from '@ng-dynamic-forms/core';
|
||||
import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap';
|
||||
|
||||
import { AuthorityOptions } from '../../../../../../core/integration/models/authority-options.model';
|
||||
import { AuthorityService } from '../../../../../../core/integration/authority.service';
|
||||
import { AuthorityServiceStub } from '../../../../../testing/authority-service.stub';
|
||||
import { VocabularyOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
|
||||
import { VocabularyServiceStub } from '../../../../../testing/vocabulary-service.stub';
|
||||
import { DsDynamicScrollableDropdownComponent } from './dynamic-scrollable-dropdown.component';
|
||||
import { DynamicScrollableDropdownModel } from './dynamic-scrollable-dropdown.model';
|
||||
import { AuthorityValue } from '../../../../../../core/integration/models/authority.value';
|
||||
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
|
||||
import { createTestComponent, hasClass } from '../../../../../testing/utils.test';
|
||||
|
||||
export const SD_TEST_GROUP = new FormGroup({
|
||||
@@ -22,14 +22,12 @@ export const SD_TEST_GROUP = new FormGroup({
|
||||
});
|
||||
|
||||
export const SD_TEST_MODEL_CONFIG = {
|
||||
authorityOptions: {
|
||||
vocabularyOptions: {
|
||||
closed: false,
|
||||
metadata: 'dropdown',
|
||||
name: 'common_iso_languages',
|
||||
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
|
||||
} as AuthorityOptions,
|
||||
name: 'common_iso_languages'
|
||||
} as VocabularyOptions,
|
||||
disabled: false,
|
||||
errorMessages: {required: 'Required field.'},
|
||||
errorMessages: { required: 'Required field.' },
|
||||
id: 'dropdown',
|
||||
label: 'Language',
|
||||
maxOptions: 10,
|
||||
@@ -53,7 +51,7 @@ describe('Dynamic Dynamic Scrollable Dropdown component', () => {
|
||||
let html;
|
||||
let modelValue;
|
||||
|
||||
const authorityServiceStub = new AuthorityServiceStub();
|
||||
const vocabularyServiceStub = new VocabularyServiceStub();
|
||||
|
||||
// async beforeEach
|
||||
beforeEach(async(() => {
|
||||
@@ -75,9 +73,9 @@ describe('Dynamic Dynamic Scrollable Dropdown component', () => {
|
||||
providers: [
|
||||
ChangeDetectorRef,
|
||||
DsDynamicScrollableDropdownComponent,
|
||||
{provide: AuthorityService, useValue: authorityServiceStub},
|
||||
{provide: DynamicFormLayoutService, useValue: {}},
|
||||
{provide: DynamicFormValidationService, useValue: {}}
|
||||
{ provide: VocabularyService, useValue: vocabularyServiceStub },
|
||||
{ provide: DynamicFormLayoutService, useValue: {} },
|
||||
{ provide: DynamicFormValidationService, useValue: {} }
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
});
|
||||
@@ -122,15 +120,12 @@ describe('Dynamic Dynamic Scrollable Dropdown component', () => {
|
||||
});
|
||||
|
||||
it('should init component properly', () => {
|
||||
const results$ = authorityServiceStub.getEntriesByName({} as any);
|
||||
expect(scrollableDropdownComp.optionsList).toBeDefined();
|
||||
results$.subscribe((results) => {
|
||||
expect(scrollableDropdownComp.optionsList).toEqual(results.payload);
|
||||
})
|
||||
expect(scrollableDropdownComp.optionsList).toEqual(vocabularyServiceStub.getList());
|
||||
});
|
||||
|
||||
it('should display dropdown menu entries', () => {
|
||||
const de = scrollableDropdownFixture.debugElement.query(By.css('button.ds-form-input-btn'));
|
||||
const de = scrollableDropdownFixture.debugElement.query(By.css('input.custom-select'));
|
||||
const btnEl = de.nativeElement;
|
||||
|
||||
const deMenu = scrollableDropdownFixture.debugElement.query(By.css('div.scrollable-dropdown-menu'));
|
||||
@@ -155,9 +150,9 @@ describe('Dynamic Dynamic Scrollable Dropdown component', () => {
|
||||
}));
|
||||
|
||||
it('should select a results entry properly', fakeAsync(() => {
|
||||
const selectedValue = Object.assign(new AuthorityValue(), {id: 1, display: 'one', value: 1});
|
||||
const selectedValue = Object.assign(new VocabularyEntry(), { authority: 1, display: 'one', value: 1 });
|
||||
|
||||
let de: any = scrollableDropdownFixture.debugElement.query(By.css('button.ds-form-input-btn'));
|
||||
let de: any = scrollableDropdownFixture.debugElement.query(By.css('input.custom-select'));
|
||||
let btnEl = de.nativeElement;
|
||||
|
||||
btnEl.click();
|
||||
@@ -193,7 +188,7 @@ describe('Dynamic Dynamic Scrollable Dropdown component', () => {
|
||||
scrollableDropdownFixture = TestBed.createComponent(DsDynamicScrollableDropdownComponent);
|
||||
scrollableDropdownComp = scrollableDropdownFixture.componentInstance; // FormComponent test instance
|
||||
scrollableDropdownComp.group = SD_TEST_GROUP;
|
||||
modelValue = Object.assign(new AuthorityValue(), {id: 1, display: 'one', value: 1});
|
||||
modelValue = Object.assign(new VocabularyEntry(), { authority: 1, display: 'one', value: 1 });
|
||||
scrollableDropdownComp.model = new DynamicScrollableDropdownModel(SD_TEST_MODEL_CONFIG);
|
||||
scrollableDropdownComp.model.value = modelValue;
|
||||
scrollableDropdownFixture.detectChanges();
|
||||
@@ -205,12 +200,9 @@ describe('Dynamic Dynamic Scrollable Dropdown component', () => {
|
||||
});
|
||||
|
||||
it('should init component properly', () => {
|
||||
const results$ = authorityServiceStub.getEntriesByName({} as any);
|
||||
expect(scrollableDropdownComp.optionsList).toBeDefined();
|
||||
results$.subscribe((results) => {
|
||||
expect(scrollableDropdownComp.optionsList).toEqual(results.payload);
|
||||
expect(scrollableDropdownComp.model.value).toEqual(modelValue);
|
||||
})
|
||||
expect(scrollableDropdownComp.optionsList).toEqual(vocabularyServiceStub.getList());
|
||||
expect(scrollableDropdownComp.model.value).toEqual(modelValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -2,28 +2,29 @@ import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } fro
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { catchError, distinctUntilChanged, first, tap } from 'rxjs/operators';
|
||||
import { catchError, distinctUntilChanged, map, tap } from 'rxjs/operators';
|
||||
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap';
|
||||
import {
|
||||
DynamicFormControlComponent,
|
||||
DynamicFormLayoutService,
|
||||
DynamicFormValidationService
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
|
||||
|
||||
import { AuthorityValue } from '../../../../../../core/integration/models/authority.value';
|
||||
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
|
||||
import { DynamicScrollableDropdownModel } from './dynamic-scrollable-dropdown.model';
|
||||
import { PageInfo } from '../../../../../../core/shared/page-info.model';
|
||||
import { isNull, isUndefined } from '../../../../../empty.util';
|
||||
import { AuthorityService } from '../../../../../../core/integration/authority.service';
|
||||
import { IntegrationSearchOptions } from '../../../../../../core/integration/models/integration-options.model';
|
||||
import { IntegrationData } from '../../../../../../core/integration/integration-data';
|
||||
import { isEmpty } from '../../../../../empty.util';
|
||||
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
|
||||
import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators';
|
||||
import { PaginatedList } from '../../../../../../core/data/paginated-list';
|
||||
import { DsDynamicVocabularyComponent } from '../dynamic-vocabulary.component';
|
||||
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
|
||||
|
||||
/**
|
||||
* Component representing a dropdown input field
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-dynamic-scrollable-dropdown',
|
||||
styleUrls: ['./dynamic-scrollable-dropdown.component.scss'],
|
||||
templateUrl: './dynamic-scrollable-dropdown.component.html'
|
||||
})
|
||||
export class DsDynamicScrollableDropdownComponent extends DynamicFormControlComponent implements OnInit {
|
||||
export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyComponent implements OnInit {
|
||||
@Input() bindId = true;
|
||||
@Input() group: FormGroup;
|
||||
@Input() model: DynamicScrollableDropdownModel;
|
||||
@@ -37,39 +38,38 @@ export class DsDynamicScrollableDropdownComponent extends DynamicFormControlComp
|
||||
public pageInfo: PageInfo;
|
||||
public optionsList: any;
|
||||
|
||||
protected searchOptions: IntegrationSearchOptions;
|
||||
|
||||
constructor(private authorityService: AuthorityService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
constructor(protected vocabularyService: VocabularyService,
|
||||
protected cdr: ChangeDetectorRef,
|
||||
protected layoutService: DynamicFormLayoutService,
|
||||
protected validationService: DynamicFormValidationService
|
||||
) {
|
||||
super(layoutService, validationService);
|
||||
super(vocabularyService, layoutService, validationService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the component, setting up the init form value
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.searchOptions = new IntegrationSearchOptions(
|
||||
this.model.authorityOptions.scope,
|
||||
this.model.authorityOptions.name,
|
||||
this.model.authorityOptions.metadata,
|
||||
'',
|
||||
this.model.maxOptions,
|
||||
1);
|
||||
this.authorityService.getEntriesByName(this.searchOptions).pipe(
|
||||
catchError(() => {
|
||||
const emptyResult = new IntegrationData(
|
||||
new PageInfo(),
|
||||
[]
|
||||
);
|
||||
return observableOf(emptyResult);
|
||||
}),
|
||||
first())
|
||||
.subscribe((object: IntegrationData) => {
|
||||
this.optionsList = object.payload;
|
||||
this.updatePageInfo(this.model.maxOptions, 1)
|
||||
this.vocabularyService.getVocabularyEntries(this.model.vocabularyOptions, this.pageInfo).pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
catchError(() => observableOf(new PaginatedList(
|
||||
new PageInfo(),
|
||||
[]
|
||||
))
|
||||
))
|
||||
.subscribe((list: PaginatedList<VocabularyEntry>) => {
|
||||
this.optionsList = list.page;
|
||||
if (this.model.value) {
|
||||
this.setCurrentValue(this.model.value);
|
||||
this.setCurrentValue(this.model.value, true);
|
||||
}
|
||||
this.pageInfo = object.pageInfo;
|
||||
|
||||
this.updatePageInfo(
|
||||
list.pageInfo.elementsPerPage,
|
||||
list.pageInfo.currentPage,
|
||||
list.pageInfo.totalElements,
|
||||
list.pageInfo.totalPages
|
||||
);
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
|
||||
@@ -77,75 +77,90 @@ export class DsDynamicScrollableDropdownComponent extends DynamicFormControlComp
|
||||
.subscribe((value) => {
|
||||
this.setCurrentValue(value);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
inputFormatter = (x: AuthorityValue): string => x.display || x.value;
|
||||
/**
|
||||
* Converts an item from the result list to a `string` to display in the `<input>` field.
|
||||
*/
|
||||
inputFormatter = (x: VocabularyEntry): string => x.display || x.value;
|
||||
|
||||
/**
|
||||
* Opens dropdown menu
|
||||
* @param sdRef The reference of the NgbDropdown.
|
||||
*/
|
||||
openDropdown(sdRef: NgbDropdown) {
|
||||
if (!this.model.readOnly) {
|
||||
this.group.markAsUntouched();
|
||||
sdRef.open();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads any new entries
|
||||
*/
|
||||
onScroll() {
|
||||
if (!this.loading && this.pageInfo.currentPage <= this.pageInfo.totalPages) {
|
||||
this.loading = true;
|
||||
this.searchOptions.currentPage++;
|
||||
this.authorityService.getEntriesByName(this.searchOptions).pipe(
|
||||
catchError(() => {
|
||||
const emptyResult = new IntegrationData(
|
||||
new PageInfo(),
|
||||
[]
|
||||
);
|
||||
return observableOf(emptyResult);
|
||||
}),
|
||||
this.updatePageInfo(
|
||||
this.pageInfo.elementsPerPage,
|
||||
this.pageInfo.currentPage + 1,
|
||||
this.pageInfo.totalElements,
|
||||
this.pageInfo.totalPages
|
||||
);
|
||||
this.vocabularyService.getVocabularyEntries(this.model.vocabularyOptions, this.pageInfo).pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
catchError(() => observableOf(new PaginatedList(
|
||||
new PageInfo(),
|
||||
[]
|
||||
))
|
||||
),
|
||||
tap(() => this.loading = false))
|
||||
.subscribe((object: IntegrationData) => {
|
||||
this.optionsList = this.optionsList.concat(object.payload);
|
||||
this.pageInfo = object.pageInfo;
|
||||
.subscribe((list: PaginatedList<VocabularyEntry>) => {
|
||||
this.optionsList = this.optionsList.concat(list.page);
|
||||
this.updatePageInfo(
|
||||
list.pageInfo.elementsPerPage,
|
||||
list.pageInfo.currentPage,
|
||||
list.pageInfo.totalElements,
|
||||
list.pageInfo.totalPages
|
||||
);
|
||||
this.cdr.detectChanges();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onBlur(event: Event) {
|
||||
this.blur.emit(event);
|
||||
}
|
||||
|
||||
onFocus(event) {
|
||||
this.focus.emit(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a change event and set the current value with the given value.
|
||||
* @param event The value to emit.
|
||||
*/
|
||||
onSelect(event) {
|
||||
this.group.markAsDirty();
|
||||
this.model.valueUpdates.next(event);
|
||||
this.change.emit(event);
|
||||
this.dispatchUpdate(event);
|
||||
this.setCurrentValue(event);
|
||||
}
|
||||
|
||||
onToggle(sdRef: NgbDropdown) {
|
||||
if (sdRef.isOpen()) {
|
||||
this.focus.emit(event);
|
||||
} else {
|
||||
this.blur.emit(event);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sets the current value with the given value.
|
||||
* @param value The value to set.
|
||||
* @param init Representing if is init value or not.
|
||||
*/
|
||||
setCurrentValue(value: any, init = false): void {
|
||||
let result: Observable<string>;
|
||||
|
||||
setCurrentValue(value): void {
|
||||
let result: string;
|
||||
if (isUndefined(value) || isNull(value)) {
|
||||
result = '';
|
||||
} else if (typeof value === 'string') {
|
||||
result = value;
|
||||
if (init) {
|
||||
result = this.getInitValueFromModel().pipe(
|
||||
map((formValue: FormFieldMetadataValueObject) => formValue.display)
|
||||
);
|
||||
} else {
|
||||
for (const item of this.optionsList) {
|
||||
if (value.value === (item as any).value) {
|
||||
result = this.inputFormatter(item);
|
||||
break;
|
||||
}
|
||||
if (isEmpty(value)) {
|
||||
result = observableOf('');
|
||||
} else if (typeof value === 'string') {
|
||||
result = observableOf(value);
|
||||
} else {
|
||||
result = observableOf(value.display)
|
||||
}
|
||||
}
|
||||
this.currentValue = observableOf(result);
|
||||
|
||||
this.currentValue = result;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { AUTOCOMPLETE_OFF, DynamicFormControlLayout, serializable } from '@ng-dynamic-forms/core';
|
||||
import { DsDynamicInputModel, DsDynamicInputModelConfig } from '../ds-dynamic-input.model';
|
||||
import { AuthorityOptions } from '../../../../../../core/integration/models/authority-options.model';
|
||||
import { VocabularyOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||
|
||||
export const DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN = 'SCROLLABLE_DROPDOWN';
|
||||
|
||||
export interface DynamicScrollableDropdownModelConfig extends DsDynamicInputModelConfig {
|
||||
authorityOptions: AuthorityOptions;
|
||||
vocabularyOptions: VocabularyOptions;
|
||||
maxOptions?: number;
|
||||
value?: any;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export class DynamicScrollableDropdownModel extends DsDynamicInputModel {
|
||||
super(config, layout);
|
||||
|
||||
this.autoComplete = AUTOCOMPLETE_OFF;
|
||||
this.authorityOptions = config.authorityOptions;
|
||||
this.vocabularyOptions = config.vocabularyOptions;
|
||||
this.maxOptions = config.maxOptions || 10;
|
||||
}
|
||||
|
||||
|
@@ -12,15 +12,14 @@ import {
|
||||
import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap';
|
||||
import { NgbModule, NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
import { AuthorityOptions } from '../../../../../../core/integration/models/authority-options.model';
|
||||
import { AuthorityService } from '../../../../../../core/integration/authority.service';
|
||||
import { AuthorityServiceStub } from '../../../../../testing/authority-service.stub';
|
||||
import { VocabularyOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
|
||||
import { VocabularyServiceStub } from '../../../../../testing/vocabulary-service.stub';
|
||||
import { DsDynamicTagComponent } from './dynamic-tag.component';
|
||||
import { DynamicTagModel } from './dynamic-tag.model';
|
||||
import { GlobalConfig } from '../../../../../../../config/global-config.interface';
|
||||
import { Chips } from '../../../../../chips/models/chips.model';
|
||||
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
|
||||
import { AuthorityValue } from '../../../../../../core/integration/models/authority.value';
|
||||
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
|
||||
import { createTestComponent } from '../../../../../testing/utils.test';
|
||||
|
||||
function createKeyUpEvent(key: number) {
|
||||
@@ -45,12 +44,10 @@ function init() {
|
||||
});
|
||||
|
||||
TAG_TEST_MODEL_CONFIG = {
|
||||
authorityOptions: {
|
||||
vocabularyOptions: {
|
||||
closed: false,
|
||||
metadata: 'tag',
|
||||
name: 'common_iso_languages',
|
||||
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
|
||||
} as AuthorityOptions,
|
||||
name: 'common_iso_languages'
|
||||
} as VocabularyOptions,
|
||||
disabled: false,
|
||||
id: 'tag',
|
||||
label: 'Keywords',
|
||||
@@ -75,7 +72,7 @@ describe('DsDynamicTagComponent test suite', () => {
|
||||
|
||||
// async beforeEach
|
||||
beforeEach(async(() => {
|
||||
const authorityServiceStub = new AuthorityServiceStub();
|
||||
const vocabularyServiceStub = new VocabularyServiceStub();
|
||||
init();
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
@@ -92,7 +89,7 @@ describe('DsDynamicTagComponent test suite', () => {
|
||||
providers: [
|
||||
ChangeDetectorRef,
|
||||
DsDynamicTagComponent,
|
||||
{ provide: AuthorityService, useValue: authorityServiceStub },
|
||||
{ provide: VocabularyService, useValue: vocabularyServiceStub },
|
||||
{ provide: DynamicFormLayoutService, useValue: {} },
|
||||
{ provide: DynamicFormValidationService, useValue: {} }
|
||||
],
|
||||
@@ -124,7 +121,7 @@ describe('DsDynamicTagComponent test suite', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
describe('when authorityOptions are set', () => {
|
||||
describe('when vocabularyOptions are set', () => {
|
||||
describe('and init model value is empty', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -143,25 +140,23 @@ describe('DsDynamicTagComponent test suite', () => {
|
||||
it('should init component properly', () => {
|
||||
chips = new Chips([], 'display');
|
||||
expect(tagComp.chips.getChipsItems()).toEqual(chips.getChipsItems());
|
||||
|
||||
expect(tagComp.searchOptions).toBeDefined();
|
||||
});
|
||||
|
||||
it('should search when 3+ characters typed', fakeAsync(() => {
|
||||
spyOn((tagComp as any).authorityService, 'getEntriesByName').and.callThrough();
|
||||
spyOn((tagComp as any).vocabularyService, 'getVocabularyEntriesByValue').and.callThrough();
|
||||
|
||||
tagComp.search(observableOf('test')).subscribe(() => {
|
||||
expect((tagComp as any).authorityService.getEntriesByName).toHaveBeenCalled();
|
||||
expect((tagComp as any).vocabularyService.getVocabularyEntriesByValue).toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should select a results entry properly', fakeAsync(() => {
|
||||
modelValue = [
|
||||
Object.assign(new AuthorityValue(), { id: 1, display: 'Name, Lastname', value: 1 })
|
||||
Object.assign(new VocabularyEntry(), { authority: 1, display: 'Name, Lastname', value: 1 })
|
||||
];
|
||||
const event: NgbTypeaheadSelectItemEvent = {
|
||||
item: Object.assign(new AuthorityValue(), {
|
||||
id: 1,
|
||||
item: Object.assign(new VocabularyEntry(), {
|
||||
authority: 1,
|
||||
display: 'Name, Lastname',
|
||||
value: 1
|
||||
}),
|
||||
@@ -233,13 +228,12 @@ describe('DsDynamicTagComponent test suite', () => {
|
||||
it('should init component properly', () => {
|
||||
chips = new Chips(modelValue, 'display');
|
||||
expect(tagComp.chips.getChipsItems()).toEqual(chips.getChipsItems());
|
||||
expect(tagComp.searchOptions).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when authorityOptions are not set', () => {
|
||||
describe('when vocabularyOptions are not set', () => {
|
||||
describe('and init model value is empty', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -247,7 +241,7 @@ describe('DsDynamicTagComponent test suite', () => {
|
||||
tagComp = tagFixture.componentInstance; // FormComponent test instance
|
||||
tagComp.group = TAG_TEST_GROUP;
|
||||
const config = TAG_TEST_MODEL_CONFIG;
|
||||
config.authorityOptions = null;
|
||||
config.vocabularyOptions = null;
|
||||
tagComp.model = new DynamicTagModel(config);
|
||||
tagFixture.detectChanges();
|
||||
});
|
||||
@@ -260,7 +254,6 @@ describe('DsDynamicTagComponent test suite', () => {
|
||||
it('should init component properly', () => {
|
||||
chips = new Chips([], 'display');
|
||||
expect(tagComp.chips.getChipsItems()).toEqual(chips.getChipsItems());
|
||||
expect(tagComp.searchOptions).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should add an item on ENTER or key press is \',\' or \';\'', fakeAsync(() => {
|
||||
|
@@ -1,29 +1,33 @@
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Inject, Input, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
import {
|
||||
DynamicFormControlComponent,
|
||||
DynamicFormLayoutService,
|
||||
DynamicFormValidationService
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { of as observableOf, Observable } from 'rxjs';
|
||||
import { catchError, debounceTime, distinctUntilChanged, tap, switchMap, map, merge } from 'rxjs/operators';
|
||||
import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { catchError, debounceTime, distinctUntilChanged, map, merge, switchMap, tap } from 'rxjs/operators';
|
||||
import { NgbTypeahead, NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { AuthorityService } from '../../../../../../core/integration/authority.service';
|
||||
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
|
||||
import { DynamicTagModel } from './dynamic-tag.model';
|
||||
import { IntegrationSearchOptions } from '../../../../../../core/integration/models/integration-options.model';
|
||||
import { Chips } from '../../../../../chips/models/chips.model';
|
||||
import { hasValue, isNotEmpty } from '../../../../../empty.util';
|
||||
import { environment } from '../../../../../../../environments/environment';
|
||||
import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators';
|
||||
import { PaginatedList } from '../../../../../../core/data/paginated-list';
|
||||
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
|
||||
import { PageInfo } from '../../../../../../core/shared/page-info.model';
|
||||
import { DsDynamicVocabularyComponent } from '../dynamic-vocabulary.component';
|
||||
|
||||
/**
|
||||
* Component representing a tag input field
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-dynamic-tag',
|
||||
styleUrls: ['./dynamic-tag.component.scss'],
|
||||
templateUrl: './dynamic-tag.component.html'
|
||||
})
|
||||
export class DsDynamicTagComponent extends DynamicFormControlComponent implements OnInit {
|
||||
export class DsDynamicTagComponent extends DsDynamicVocabularyComponent implements OnInit {
|
||||
|
||||
@Input() bindId = true;
|
||||
@Input() group: FormGroup;
|
||||
@Input() model: DynamicTagModel;
|
||||
@@ -32,19 +36,34 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
|
||||
@Output() change: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output() focus: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@ViewChild('instance', {static: false}) instance: NgbTypeahead;
|
||||
@ViewChild('instance', { static: false }) instance: NgbTypeahead;
|
||||
|
||||
chips: Chips;
|
||||
hasAuthority: boolean;
|
||||
|
||||
searching = false;
|
||||
searchOptions: IntegrationSearchOptions;
|
||||
searchFailed = false;
|
||||
hideSearchingWhenUnsubscribed = new Observable(() => () => this.changeSearchingStatus(false));
|
||||
currentValue: any;
|
||||
public pageInfo: PageInfo;
|
||||
|
||||
constructor(protected vocabularyService: VocabularyService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
protected layoutService: DynamicFormLayoutService,
|
||||
protected validationService: DynamicFormValidationService
|
||||
) {
|
||||
super(vocabularyService, layoutService, validationService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an item from the result list to a `string` to display in the `<input>` field.
|
||||
*/
|
||||
formatter = (x: { display: string }) => x.display;
|
||||
|
||||
/**
|
||||
* Converts a stream of text values from the `<input>` element to the stream of the array of items
|
||||
* to display in the typeahead popup.
|
||||
*/
|
||||
search = (text$: Observable<string>) =>
|
||||
text$.pipe(
|
||||
debounceTime(300),
|
||||
@@ -52,45 +71,29 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
|
||||
tap(() => this.changeSearchingStatus(true)),
|
||||
switchMap((term) => {
|
||||
if (term === '' || term.length < this.model.minChars) {
|
||||
return observableOf({list: []});
|
||||
return observableOf({ list: [] });
|
||||
} else {
|
||||
this.searchOptions.query = term;
|
||||
return this.authorityService.getEntriesByName(this.searchOptions).pipe(
|
||||
map((authorities) => {
|
||||
// @TODO Pagination for authority is not working, to refactor when it will be fixed
|
||||
return {
|
||||
list: authorities.payload,
|
||||
pageInfo: authorities.pageInfo
|
||||
};
|
||||
}),
|
||||
return this.vocabularyService.getVocabularyEntriesByValue(term, false, this.model.vocabularyOptions, new PageInfo()).pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
tap(() => this.searchFailed = false),
|
||||
catchError(() => {
|
||||
this.searchFailed = true;
|
||||
return observableOf({list: []});
|
||||
return observableOf(new PaginatedList(
|
||||
new PageInfo(),
|
||||
[]
|
||||
));
|
||||
}));
|
||||
}
|
||||
}),
|
||||
map((results) => results.list),
|
||||
map((list: PaginatedList<VocabularyEntry>) => list.page),
|
||||
tap(() => this.changeSearchingStatus(false)),
|
||||
merge(this.hideSearchingWhenUnsubscribed));
|
||||
|
||||
constructor(private authorityService: AuthorityService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
protected layoutService: DynamicFormLayoutService,
|
||||
protected validationService: DynamicFormValidationService
|
||||
) {
|
||||
super(layoutService, validationService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the component, setting up the init form value
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.hasAuthority = this.model.authorityOptions && hasValue(this.model.authorityOptions.name);
|
||||
|
||||
if (this.hasAuthority) {
|
||||
this.searchOptions = new IntegrationSearchOptions(
|
||||
this.model.authorityOptions.scope,
|
||||
this.model.authorityOptions.name,
|
||||
this.model.authorityOptions.metadata);
|
||||
}
|
||||
this.hasAuthority = this.model.vocabularyOptions && hasValue(this.model.vocabularyOptions.name);
|
||||
|
||||
this.chips = new Chips(
|
||||
this.model.value,
|
||||
@@ -103,17 +106,24 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
|
||||
const items = this.chips.getChipsItems();
|
||||
// Does not emit change if model value is equal to the current value
|
||||
if (!isEqual(items, this.model.value)) {
|
||||
this.model.valueUpdates.next(items);
|
||||
this.change.emit(event);
|
||||
this.dispatchUpdate(items);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the searching status
|
||||
* @param status
|
||||
*/
|
||||
changeSearchingStatus(status: boolean) {
|
||||
this.searching = status;
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark form group as dirty on input
|
||||
* @param event
|
||||
*/
|
||||
onInput(event) {
|
||||
if (event.data) {
|
||||
this.group.markAsDirty();
|
||||
@@ -121,6 +131,10 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a blur event containing a given value and add all tags to chips.
|
||||
* @param event The value to emit.
|
||||
*/
|
||||
onBlur(event: Event) {
|
||||
if (isNotEmpty(this.currentValue) && !this.instance.isPopupOpen()) {
|
||||
this.addTagsToChips();
|
||||
@@ -128,10 +142,10 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
|
||||
this.blur.emit(event);
|
||||
}
|
||||
|
||||
onFocus(event) {
|
||||
this.focus.emit(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates model value with the selected value and add a new tag to chips.
|
||||
* @param event The value to set.
|
||||
*/
|
||||
onSelectItem(event: NgbTypeaheadSelectItemEvent) {
|
||||
this.chips.add(event.item);
|
||||
// this.group.controls[this.model.id].setValue(this.model.value);
|
||||
@@ -139,25 +153,34 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
|
||||
|
||||
setTimeout(() => {
|
||||
// Reset the input text after x ms, mandatory or the formatter overwrite it
|
||||
this.currentValue = null;
|
||||
this.setCurrentValue(null);
|
||||
this.cdr.detectChanges();
|
||||
}, 50);
|
||||
}
|
||||
|
||||
updateModel(event) {
|
||||
this.model.valueUpdates.next(this.chips.getChipsItems());
|
||||
this.change.emit(event);
|
||||
/* this.model.valueUpdates.next(this.chips.getChipsItems());
|
||||
this.change.emit(event);*/
|
||||
this.dispatchUpdate(this.chips.getChipsItems());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new tag with typed text when typing 'Enter' or ',' or ';'
|
||||
* @param event the keyUp event
|
||||
*/
|
||||
onKeyUp(event) {
|
||||
if (event.keyCode === 13 || event.keyCode === 188) {
|
||||
event.preventDefault();
|
||||
// Key: Enter or ',' or ';'
|
||||
// Key: 'Enter' or ',' or ';'
|
||||
this.addTagsToChips();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent propagation of a key event in case of return key is pressed
|
||||
* @param event the key event
|
||||
*/
|
||||
preventEventsPropagation(event) {
|
||||
event.stopPropagation();
|
||||
if (event.keyCode === 13) {
|
||||
@@ -165,8 +188,17 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current value with the given value.
|
||||
* @param value The value to set.
|
||||
* @param init Representing if is init value or not.
|
||||
*/
|
||||
public setCurrentValue(value: any, init = false) {
|
||||
this.currentValue = value;
|
||||
}
|
||||
|
||||
private addTagsToChips() {
|
||||
if (hasValue(this.currentValue) && (!this.hasAuthority || !this.model.authorityOptions.closed)) {
|
||||
if (hasValue(this.currentValue) && (!this.hasAuthority || !this.model.vocabularyOptions.closed)) {
|
||||
let res: string[] = [];
|
||||
res = this.currentValue.split(',');
|
||||
|
||||
@@ -187,7 +219,7 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
|
||||
// this.currentValue = '';
|
||||
setTimeout(() => {
|
||||
// Reset the input text after x ms, mandatory or the formatter overwrite it
|
||||
this.currentValue = null;
|
||||
this.setCurrentValue(null);
|
||||
this.cdr.detectChanges();
|
||||
}, 50);
|
||||
this.updateModel(event);
|
||||
|
@@ -1,274 +0,0 @@
|
||||
// Load the implementations that should be tested
|
||||
import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { async, ComponentFixture, fakeAsync, inject, TestBed, tick, } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { DynamicFormLayoutService, DynamicFormsCoreModule, DynamicFormValidationService } from '@ng-dynamic-forms/core';
|
||||
import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { AuthorityOptions } from '../../../../../../core/integration/models/authority-options.model';
|
||||
import { AuthorityService } from '../../../../../../core/integration/authority.service';
|
||||
import { AuthorityServiceStub } from '../../../../../testing/authority-service.stub';
|
||||
import { GlobalConfig } from '../../../../../../../config/global-config.interface';
|
||||
import { DsDynamicTypeaheadComponent } from './dynamic-typeahead.component';
|
||||
import { DynamicTypeaheadModel } from './dynamic-typeahead.model';
|
||||
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
|
||||
import { createTestComponent } from '../../../../../testing/utils.test';
|
||||
import { AuthorityConfidenceStateDirective } from '../../../../../authority-confidence/authority-confidence-state.directive';
|
||||
import { ObjNgFor } from '../../../../../utils/object-ngfor.pipe';
|
||||
|
||||
export let TYPEAHEAD_TEST_GROUP;
|
||||
|
||||
export let TYPEAHEAD_TEST_MODEL_CONFIG;
|
||||
|
||||
function init() {
|
||||
TYPEAHEAD_TEST_GROUP = new FormGroup({
|
||||
typeahead: new FormControl(),
|
||||
});
|
||||
|
||||
TYPEAHEAD_TEST_MODEL_CONFIG = {
|
||||
authorityOptions: {
|
||||
closed: false,
|
||||
metadata: 'typeahead',
|
||||
name: 'EVENTAuthority',
|
||||
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
|
||||
} as AuthorityOptions,
|
||||
disabled: false,
|
||||
id: 'typeahead',
|
||||
label: 'Conference',
|
||||
minChars: 3,
|
||||
name: 'typeahead',
|
||||
placeholder: 'Conference',
|
||||
readOnly: false,
|
||||
required: false,
|
||||
repeatable: false,
|
||||
value: undefined
|
||||
};
|
||||
}
|
||||
describe('DsDynamicTypeaheadComponent test suite', () => {
|
||||
|
||||
let testComp: TestComponent;
|
||||
let typeaheadComp: DsDynamicTypeaheadComponent;
|
||||
let testFixture: ComponentFixture<TestComponent>;
|
||||
let typeaheadFixture: ComponentFixture<DsDynamicTypeaheadComponent>;
|
||||
let html;
|
||||
|
||||
// async beforeEach
|
||||
beforeEach(async(() => {
|
||||
const authorityServiceStub = new AuthorityServiceStub();
|
||||
init();
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
DynamicFormsCoreModule,
|
||||
DynamicFormsNGBootstrapUIModule,
|
||||
FormsModule,
|
||||
NgbModule,
|
||||
ReactiveFormsModule,
|
||||
TranslateModule.forRoot()
|
||||
],
|
||||
declarations: [
|
||||
DsDynamicTypeaheadComponent,
|
||||
TestComponent,
|
||||
AuthorityConfidenceStateDirective,
|
||||
ObjNgFor
|
||||
], // declare the test component
|
||||
providers: [
|
||||
ChangeDetectorRef,
|
||||
DsDynamicTypeaheadComponent,
|
||||
{ provide: AuthorityService, useValue: authorityServiceStub },
|
||||
{ provide: DynamicFormLayoutService, useValue: {} },
|
||||
{ provide: DynamicFormValidationService, useValue: {} }
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
});
|
||||
|
||||
}));
|
||||
|
||||
describe('', () => {
|
||||
// synchronous beforeEach
|
||||
beforeEach(() => {
|
||||
html = `
|
||||
<ds-dynamic-typeahead [bindId]="bindId"
|
||||
[group]="group"
|
||||
[model]="model"
|
||||
(blur)="onBlur($event)"
|
||||
(change)="onValueChange($event)"
|
||||
(focus)="onFocus($event)"></ds-dynamic-typeahead>`;
|
||||
|
||||
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||
testComp = testFixture.componentInstance;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
testFixture.destroy();
|
||||
});
|
||||
it('should create DsDynamicTypeaheadComponent', inject([DsDynamicTypeaheadComponent], (app: DsDynamicTypeaheadComponent) => {
|
||||
|
||||
expect(app).toBeDefined();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('', () => {
|
||||
describe('when init model value is empty', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
typeaheadFixture = TestBed.createComponent(DsDynamicTypeaheadComponent);
|
||||
typeaheadComp = typeaheadFixture.componentInstance; // FormComponent test instance
|
||||
typeaheadComp.group = TYPEAHEAD_TEST_GROUP;
|
||||
typeaheadComp.model = new DynamicTypeaheadModel(TYPEAHEAD_TEST_MODEL_CONFIG);
|
||||
typeaheadFixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
typeaheadFixture.destroy();
|
||||
typeaheadComp = null;
|
||||
});
|
||||
|
||||
it('should init component properly', () => {
|
||||
expect(typeaheadComp.currentValue).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should search when 3+ characters typed', fakeAsync(() => {
|
||||
|
||||
spyOn((typeaheadComp as any).authorityService, 'getEntriesByName').and.callThrough();
|
||||
|
||||
typeaheadComp.search(observableOf('test')).subscribe();
|
||||
|
||||
tick(300);
|
||||
typeaheadFixture.detectChanges();
|
||||
|
||||
expect((typeaheadComp as any).authorityService.getEntriesByName).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should set model.value on input type when AuthorityOptions.closed is false', () => {
|
||||
const inputDe = typeaheadFixture.debugElement.query(By.css('input.form-control'));
|
||||
const inputElement = inputDe.nativeElement;
|
||||
|
||||
inputElement.value = 'test value';
|
||||
inputElement.dispatchEvent(new Event('input'));
|
||||
|
||||
expect(typeaheadComp.inputValue).toEqual(new FormFieldMetadataValueObject('test value'))
|
||||
|
||||
});
|
||||
|
||||
it('should not set model.value on input type when AuthorityOptions.closed is true', () => {
|
||||
typeaheadComp.model.authorityOptions.closed = true;
|
||||
typeaheadFixture.detectChanges();
|
||||
const inputDe = typeaheadFixture.debugElement.query(By.css('input.form-control'));
|
||||
const inputElement = inputDe.nativeElement;
|
||||
|
||||
inputElement.value = 'test value';
|
||||
inputElement.dispatchEvent(new Event('input'));
|
||||
|
||||
expect(typeaheadComp.model.value).not.toBeDefined();
|
||||
|
||||
});
|
||||
|
||||
it('should emit blur Event onBlur when popup is closed', () => {
|
||||
spyOn(typeaheadComp.blur, 'emit');
|
||||
spyOn(typeaheadComp.instance, 'isPopupOpen').and.returnValue(false);
|
||||
typeaheadComp.onBlur(new Event('blur'));
|
||||
expect(typeaheadComp.blur.emit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not emit blur Event onBlur when popup is opened', () => {
|
||||
spyOn(typeaheadComp.blur, 'emit');
|
||||
spyOn(typeaheadComp.instance, 'isPopupOpen').and.returnValue(true);
|
||||
const input = typeaheadFixture.debugElement.query(By.css('input'));
|
||||
|
||||
input.nativeElement.blur();
|
||||
expect(typeaheadComp.blur.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit change Event onBlur when AuthorityOptions.closed is false and inputValue is changed', () => {
|
||||
typeaheadComp.inputValue = 'test value';
|
||||
typeaheadFixture.detectChanges();
|
||||
spyOn(typeaheadComp.blur, 'emit');
|
||||
spyOn(typeaheadComp.change, 'emit');
|
||||
spyOn(typeaheadComp.instance, 'isPopupOpen').and.returnValue(false);
|
||||
typeaheadComp.onBlur(new Event('blur', ));
|
||||
expect(typeaheadComp.change.emit).toHaveBeenCalled();
|
||||
expect(typeaheadComp.blur.emit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not emit change Event onBlur when AuthorityOptions.closed is false and inputValue is not changed', () => {
|
||||
typeaheadComp.inputValue = 'test value';
|
||||
typeaheadComp.model = new DynamicTypeaheadModel(TYPEAHEAD_TEST_MODEL_CONFIG);
|
||||
(typeaheadComp.model as any).value = 'test value';
|
||||
typeaheadFixture.detectChanges();
|
||||
spyOn(typeaheadComp.blur, 'emit');
|
||||
spyOn(typeaheadComp.change, 'emit');
|
||||
spyOn(typeaheadComp.instance, 'isPopupOpen').and.returnValue(false);
|
||||
typeaheadComp.onBlur(new Event('blur', ));
|
||||
expect(typeaheadComp.change.emit).not.toHaveBeenCalled();
|
||||
expect(typeaheadComp.blur.emit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not emit change Event onBlur when AuthorityOptions.closed is false and inputValue is null', () => {
|
||||
typeaheadComp.inputValue = null;
|
||||
typeaheadComp.model = new DynamicTypeaheadModel(TYPEAHEAD_TEST_MODEL_CONFIG);
|
||||
(typeaheadComp.model as any).value = 'test value';
|
||||
typeaheadFixture.detectChanges();
|
||||
spyOn(typeaheadComp.blur, 'emit');
|
||||
spyOn(typeaheadComp.change, 'emit');
|
||||
spyOn(typeaheadComp.instance, 'isPopupOpen').and.returnValue(false);
|
||||
typeaheadComp.onBlur(new Event('blur', ));
|
||||
expect(typeaheadComp.change.emit).not.toHaveBeenCalled();
|
||||
expect(typeaheadComp.blur.emit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit focus Event onFocus', () => {
|
||||
spyOn(typeaheadComp.focus, 'emit');
|
||||
typeaheadComp.onFocus(new Event('focus'));
|
||||
expect(typeaheadComp.focus.emit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('and init model value is not empty', () => {
|
||||
beforeEach(() => {
|
||||
typeaheadFixture = TestBed.createComponent(DsDynamicTypeaheadComponent);
|
||||
typeaheadComp = typeaheadFixture.componentInstance; // FormComponent test instance
|
||||
typeaheadComp.group = TYPEAHEAD_TEST_GROUP;
|
||||
typeaheadComp.model = new DynamicTypeaheadModel(TYPEAHEAD_TEST_MODEL_CONFIG);
|
||||
(typeaheadComp.model as any).value = new FormFieldMetadataValueObject('test', null, 'test001');
|
||||
typeaheadFixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
typeaheadFixture.destroy();
|
||||
typeaheadComp = null;
|
||||
});
|
||||
|
||||
it('should init component properly', () => {
|
||||
expect(typeaheadComp.currentValue).toEqual(new FormFieldMetadataValueObject('test', null, 'test001'));
|
||||
});
|
||||
|
||||
it('should emit change Event onChange and currentValue is empty', () => {
|
||||
typeaheadComp.currentValue = null;
|
||||
spyOn(typeaheadComp.change, 'emit');
|
||||
typeaheadComp.onChange(new Event('change'));
|
||||
expect(typeaheadComp.change.emit).toHaveBeenCalled();
|
||||
expect(typeaheadComp.model.value).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// declare a test component
|
||||
@Component({
|
||||
selector: 'ds-test-cmp',
|
||||
template: ``
|
||||
})
|
||||
class TestComponent {
|
||||
|
||||
group: FormGroup = TYPEAHEAD_TEST_GROUP;
|
||||
|
||||
model = new DynamicTypeaheadModel(TYPEAHEAD_TEST_MODEL_CONFIG);
|
||||
|
||||
}
|
@@ -1,156 +0,0 @@
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
import {
|
||||
DynamicFormControlComponent,
|
||||
DynamicFormLayoutService,
|
||||
DynamicFormValidationService
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { catchError, debounceTime, distinctUntilChanged, filter, map, merge, switchMap, tap } from 'rxjs/operators';
|
||||
import { Observable, of as observableOf, Subject } from 'rxjs';
|
||||
import { NgbTypeahead, NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
import { AuthorityService } from '../../../../../../core/integration/authority.service';
|
||||
import { DynamicTypeaheadModel } from './dynamic-typeahead.model';
|
||||
import { IntegrationSearchOptions } from '../../../../../../core/integration/models/integration-options.model';
|
||||
import { isEmpty, isNotEmpty, isNotNull } from '../../../../../empty.util';
|
||||
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
|
||||
import { ConfidenceType } from '../../../../../../core/integration/models/confidence-type';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-dynamic-typeahead',
|
||||
styleUrls: ['./dynamic-typeahead.component.scss'],
|
||||
templateUrl: './dynamic-typeahead.component.html'
|
||||
})
|
||||
export class DsDynamicTypeaheadComponent extends DynamicFormControlComponent implements OnInit {
|
||||
@Input() bindId = true;
|
||||
@Input() group: FormGroup;
|
||||
@Input() model: DynamicTypeaheadModel;
|
||||
|
||||
@Output() blur: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output() change: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output() focus: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@ViewChild('instance', {static: false}) instance: NgbTypeahead;
|
||||
|
||||
searching = false;
|
||||
searchOptions: IntegrationSearchOptions;
|
||||
searchFailed = false;
|
||||
hideSearchingWhenUnsubscribed$ = new Observable(() => () => this.changeSearchingStatus(false));
|
||||
click$ = new Subject<string>();
|
||||
currentValue: any;
|
||||
inputValue: any;
|
||||
|
||||
formatter = (x: { display: string }) => {
|
||||
return (typeof x === 'object') ? x.display : x
|
||||
};
|
||||
|
||||
search = (text$: Observable<string>) => {
|
||||
return text$.pipe(
|
||||
merge(this.click$),
|
||||
debounceTime(300),
|
||||
distinctUntilChanged(),
|
||||
tap(() => this.changeSearchingStatus(true)),
|
||||
switchMap((term) => {
|
||||
if (term === '' || term.length < this.model.minChars) {
|
||||
return observableOf({list: []});
|
||||
} else {
|
||||
this.searchOptions.query = term;
|
||||
return this.authorityService.getEntriesByName(this.searchOptions).pipe(
|
||||
map((authorities) => {
|
||||
// @TODO Pagination for authority is not working, to refactor when it will be fixed
|
||||
return {
|
||||
list: authorities.payload,
|
||||
pageInfo: authorities.pageInfo
|
||||
};
|
||||
}),
|
||||
tap(() => this.searchFailed = false),
|
||||
catchError(() => {
|
||||
this.searchFailed = true;
|
||||
return observableOf({list: []});
|
||||
}));
|
||||
}
|
||||
}),
|
||||
map((results) => results.list),
|
||||
tap(() => this.changeSearchingStatus(false)),
|
||||
merge(this.hideSearchingWhenUnsubscribed$)
|
||||
)
|
||||
};
|
||||
|
||||
constructor(private authorityService: AuthorityService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
protected layoutService: DynamicFormLayoutService,
|
||||
protected validationService: DynamicFormValidationService
|
||||
) {
|
||||
super(layoutService, validationService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.currentValue = this.model.value;
|
||||
this.searchOptions = new IntegrationSearchOptions(
|
||||
this.model.authorityOptions.scope,
|
||||
this.model.authorityOptions.name,
|
||||
this.model.authorityOptions.metadata);
|
||||
this.group.get(this.model.id).valueChanges.pipe(
|
||||
filter((value) => this.currentValue !== value))
|
||||
.subscribe((value) => {
|
||||
this.currentValue = value;
|
||||
});
|
||||
}
|
||||
|
||||
changeSearchingStatus(status: boolean) {
|
||||
this.searching = status;
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
onInput(event) {
|
||||
if (!this.model.authorityOptions.closed && isNotEmpty(event.target.value)) {
|
||||
this.inputValue = new FormFieldMetadataValueObject(event.target.value);
|
||||
}
|
||||
}
|
||||
|
||||
onBlur(event: Event) {
|
||||
if (!this.instance.isPopupOpen()) {
|
||||
if (!this.model.authorityOptions.closed && isNotEmpty(this.inputValue)) {
|
||||
if (isNotNull(this.inputValue) && this.model.value !== this.inputValue) {
|
||||
this.model.valueUpdates.next(this.inputValue);
|
||||
this.change.emit(this.inputValue);
|
||||
}
|
||||
this.inputValue = null;
|
||||
}
|
||||
this.blur.emit(event);
|
||||
} else {
|
||||
// prevent on blur propagation if typeahed suggestions are showed
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
// set focus on input again, this is to avoid to lose changes when no suggestion is selected
|
||||
(event.target as HTMLInputElement).focus();
|
||||
}
|
||||
}
|
||||
|
||||
onChange(event: Event) {
|
||||
event.stopPropagation();
|
||||
if (isEmpty(this.currentValue)) {
|
||||
this.model.valueUpdates.next(null);
|
||||
this.change.emit(null);
|
||||
}
|
||||
}
|
||||
|
||||
onFocus(event) {
|
||||
this.focus.emit(event);
|
||||
}
|
||||
|
||||
onSelectItem(event: NgbTypeaheadSelectItemEvent) {
|
||||
this.inputValue = null;
|
||||
this.currentValue = event.item;
|
||||
this.model.valueUpdates.next(event.item);
|
||||
this.change.emit(event.item);
|
||||
}
|
||||
|
||||
public whenClickOnConfidenceNotAccepted(confidence: ConfidenceType) {
|
||||
if (!this.model.readOnly) {
|
||||
this.click$.next(this.formatter(this.currentValue));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
import { BehaviorSubject, never, Observable, of as observableOf } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
|
||||
import { RelationshipEffects } from './relationship.effects';
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import { provideMockActions } from '@ngrx/effects/testing';
|
||||
|
@@ -34,9 +34,9 @@ import { DynamicScrollableDropdownModel } from './ds-dynamic-form-ui/models/scro
|
||||
import { DynamicRelationGroupModel } from './ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model';
|
||||
import { DynamicLookupModel } from './ds-dynamic-form-ui/models/lookup/dynamic-lookup.model';
|
||||
import { DynamicDsDatePickerModel } from './ds-dynamic-form-ui/models/date-picker/date-picker.model';
|
||||
import { DynamicTypeaheadModel } from './ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.model';
|
||||
import { DynamicOneboxModel } from './ds-dynamic-form-ui/models/onebox/dynamic-onebox.model';
|
||||
import { DynamicListRadioGroupModel } from './ds-dynamic-form-ui/models/list/dynamic-list-radio-group.model';
|
||||
import { AuthorityOptions } from '../../../core/integration/models/authority-options.model';
|
||||
import { VocabularyOptions } from '../../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||
import { FormFieldModel } from './models/form-field.model';
|
||||
import {
|
||||
SubmissionFormsModel
|
||||
@@ -78,11 +78,9 @@ describe('FormBuilderService test suite', () => {
|
||||
]
|
||||
});
|
||||
|
||||
const authorityOptions: AuthorityOptions = {
|
||||
closed: false,
|
||||
metadata: 'list',
|
||||
const vocabularyOptions: VocabularyOptions = {
|
||||
name: 'type_programme',
|
||||
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
|
||||
closed: false
|
||||
};
|
||||
|
||||
testModel = [
|
||||
@@ -195,15 +193,15 @@ describe('FormBuilderService test suite', () => {
|
||||
|
||||
new DynamicColorPickerModel({ id: 'testColorPicker' }),
|
||||
|
||||
new DynamicTypeaheadModel({ id: 'testTypeahead', repeatable: false, metadataFields: [], submissionId: '1234', hasSelectableMetadata: false }),
|
||||
new DynamicOneboxModel({ id: 'testOnebox', repeatable: false, metadataFields: [], submissionId: '1234', hasSelectableMetadata: false }),
|
||||
|
||||
new DynamicScrollableDropdownModel({ id: 'testScrollableDropdown', authorityOptions: authorityOptions, repeatable: false, metadataFields: [], submissionId: '1234', hasSelectableMetadata: false }),
|
||||
new DynamicScrollableDropdownModel({ id: 'testScrollableDropdown', vocabularyOptions: vocabularyOptions, repeatable: false, metadataFields: [], submissionId: '1234', hasSelectableMetadata: false }),
|
||||
|
||||
new DynamicTagModel({ id: 'testTag', repeatable: false, metadataFields: [], submissionId: '1234', hasSelectableMetadata: false }),
|
||||
|
||||
new DynamicListCheckboxGroupModel({ id: 'testCheckboxList', authorityOptions: authorityOptions, repeatable: true }),
|
||||
new DynamicListCheckboxGroupModel({id: 'testCheckboxList', vocabularyOptions: vocabularyOptions, repeatable: true}),
|
||||
|
||||
new DynamicListRadioGroupModel({ id: 'testRadioList', authorityOptions: authorityOptions, repeatable: false }),
|
||||
new DynamicListRadioGroupModel({id: 'testRadioList', vocabularyOptions: vocabularyOptions, repeatable: false}),
|
||||
|
||||
new DynamicRelationGroupModel({
|
||||
submissionId,
|
||||
@@ -218,7 +216,7 @@ describe('FormBuilderService test suite', () => {
|
||||
mandatoryMessage: 'Required field!',
|
||||
repeatable: false,
|
||||
selectableMetadata: [{
|
||||
authority: 'RPAuthority',
|
||||
controlledVocabulary: 'RPAuthority',
|
||||
closed: false,
|
||||
metadata: 'dc.contributor.author'
|
||||
}]
|
||||
@@ -232,7 +230,7 @@ describe('FormBuilderService test suite', () => {
|
||||
mandatory: 'false',
|
||||
repeatable: false,
|
||||
selectableMetadata: [{
|
||||
authority: 'OUAuthority',
|
||||
controlledVocabulary: 'OUAuthority',
|
||||
closed: false,
|
||||
metadata: 'local.contributor.affiliation'
|
||||
}]
|
||||
@@ -290,7 +288,7 @@ describe('FormBuilderService test suite', () => {
|
||||
selectableMetadata: [
|
||||
{
|
||||
metadata: 'journal',
|
||||
authority: 'JOURNALAuthority',
|
||||
controlledVocabulary: 'JOURNALAuthority',
|
||||
closed: false
|
||||
}
|
||||
],
|
||||
@@ -370,7 +368,7 @@ describe('FormBuilderService test suite', () => {
|
||||
selectableMetadata: [
|
||||
{
|
||||
metadata: 'conference',
|
||||
authority: 'EVENTAuthority',
|
||||
controlledVocabulary: 'EVENTAuthority',
|
||||
closed: false
|
||||
}
|
||||
],
|
||||
@@ -439,7 +437,7 @@ describe('FormBuilderService test suite', () => {
|
||||
|
||||
expect(formModel[2] instanceof DynamicRowGroupModel).toBe(true);
|
||||
expect((formModel[2] as DynamicRowGroupModel).group.length).toBe(1);
|
||||
expect((formModel[2] as DynamicRowGroupModel).get(0) instanceof DynamicTypeaheadModel).toBe(true);
|
||||
expect((formModel[2] as DynamicRowGroupModel).get(0) instanceof DynamicOneboxModel).toBe(true);
|
||||
});
|
||||
|
||||
it('should return form\'s fields value from form model', () => {
|
||||
@@ -455,7 +453,7 @@ describe('FormBuilderService test suite', () => {
|
||||
};
|
||||
expect(service.getValueFromModel(formModel)).toEqual(value);
|
||||
|
||||
((formModel[2] as DynamicRowGroupModel).get(0) as DynamicTypeaheadModel).valueUpdates.next('test one');
|
||||
((formModel[2] as DynamicRowGroupModel).get(0) as DynamicOneboxModel).valueUpdates.next('test one');
|
||||
value = {
|
||||
issue: [new FormFieldMetadataValueObject('test')],
|
||||
conference: [new FormFieldMetadataValueObject('test one')]
|
||||
@@ -468,11 +466,11 @@ describe('FormBuilderService test suite', () => {
|
||||
const value = {} as any;
|
||||
|
||||
((formModel[0] as DynamicRowGroupModel).get(1) as DsDynamicInputModel).valueUpdates.next('test');
|
||||
((formModel[2] as DynamicRowGroupModel).get(0) as DynamicTypeaheadModel).valueUpdates.next('test one');
|
||||
((formModel[2] as DynamicRowGroupModel).get(0) as DynamicOneboxModel).valueUpdates.next('test one');
|
||||
|
||||
service.clearAllModelsValue(formModel);
|
||||
expect(((formModel[0] as DynamicRowGroupModel).get(1) as DynamicTypeaheadModel).value).toEqual(undefined)
|
||||
expect(((formModel[2] as DynamicRowGroupModel).get(0) as DynamicTypeaheadModel).value).toEqual(undefined)
|
||||
expect(((formModel[0] as DynamicRowGroupModel).get(1) as DynamicOneboxModel).value).toEqual(undefined)
|
||||
expect(((formModel[2] as DynamicRowGroupModel).get(0) as DynamicOneboxModel).value).toEqual(undefined)
|
||||
});
|
||||
|
||||
it('should return true when model has a custom group model as parent', () => {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { hasValue, isEmpty, isNotEmpty, isNotNull } from '../../../empty.util';
|
||||
import { ConfidenceType } from '../../../../core/integration/models/confidence-type';
|
||||
import { ConfidenceType } from '../../../../core/shared/confidence-type';
|
||||
import { MetadataValueInterface, VIRTUAL_METADATA_PREFIX } from '../../../../core/shared/metadata.models';
|
||||
import { PLACEHOLDER_PARENT_METADATA } from '../ds-dynamic-form-ui/ds-dynamic-form-constants';
|
||||
|
||||
@@ -7,6 +7,9 @@ export interface OtherInformation {
|
||||
[name: string]: string
|
||||
}
|
||||
|
||||
/**
|
||||
* A class representing a specific input-form field's value
|
||||
*/
|
||||
export class FormFieldMetadataValueObject implements MetadataValueInterface {
|
||||
metadata?: string;
|
||||
value: any;
|
||||
@@ -15,7 +18,6 @@ export class FormFieldMetadataValueObject implements MetadataValueInterface {
|
||||
authority: string;
|
||||
confidence: ConfidenceType;
|
||||
place: number;
|
||||
closed: boolean;
|
||||
label: string;
|
||||
otherInformation: OtherInformation;
|
||||
|
||||
@@ -33,7 +35,7 @@ export class FormFieldMetadataValueObject implements MetadataValueInterface {
|
||||
this.display = display || value;
|
||||
|
||||
this.confidence = confidence;
|
||||
if (authority != null && isEmpty(confidence)) {
|
||||
if (authority != null && (isEmpty(confidence) || confidence === -1)) {
|
||||
this.confidence = ConfidenceType.CF_ACCEPTED;
|
||||
} else if (isNotEmpty(confidence)) {
|
||||
this.confidence = confidence;
|
||||
@@ -49,26 +51,53 @@ export class FormFieldMetadataValueObject implements MetadataValueInterface {
|
||||
this.otherInformation = otherInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this this object has an authority value
|
||||
*/
|
||||
hasAuthority(): boolean {
|
||||
return isNotEmpty(this.authority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this this object has a value
|
||||
*/
|
||||
hasValue(): boolean {
|
||||
return isNotEmpty(this.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this this object has otherInformation property with value
|
||||
*/
|
||||
hasOtherInformation(): boolean {
|
||||
return isNotEmpty(this.otherInformation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this object value contains a placeholder
|
||||
*/
|
||||
hasPlaceholder() {
|
||||
return this.hasValue() && this.value === PLACEHOLDER_PARENT_METADATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this Metadatum's authority key starts with 'virtual::'
|
||||
*/
|
||||
get isVirtual(): boolean {
|
||||
return hasValue(this.authority) && this.authority.startsWith(VIRTUAL_METADATA_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is a virtual Metadatum, it returns everything in the authority key after 'virtual::'.
|
||||
* Returns undefined otherwise.
|
||||
*/
|
||||
get virtualValue(): string {
|
||||
if (this.isVirtual) {
|
||||
return this.authority.substring(this.authority.indexOf(VIRTUAL_METADATA_PREFIX) + VIRTUAL_METADATA_PREFIX.length);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.display || this.value;
|
||||
}
|
||||
|
@@ -1,50 +1,121 @@
|
||||
import { autoserialize } from 'cerialize';
|
||||
|
||||
import { LanguageCode } from './form-field-language-value.model';
|
||||
import { FormFieldMetadataValueObject } from './form-field-metadata-value.model';
|
||||
import { RelationshipOptions } from './relationship-options.model';
|
||||
import { FormRowModel } from '../../../../core/config/models/config-submission-form.model';
|
||||
|
||||
/**
|
||||
* Representing SelectableMetadata properties
|
||||
*/
|
||||
export interface SelectableMetadata {
|
||||
/**
|
||||
* The key of the metadata field to use to store the input
|
||||
*/
|
||||
metadata: string;
|
||||
|
||||
/**
|
||||
* The label of the metadata field to use to store the input
|
||||
*/
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* The name of the controlled vocabulary used to retrieve value for the input see controlled vocabularies
|
||||
*/
|
||||
controlledVocabulary: string;
|
||||
|
||||
/**
|
||||
* A boolean representing if value is closely related to the controlled vocabulary entry or not
|
||||
*/
|
||||
closed: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A class representing a specific input-form field
|
||||
*/
|
||||
export class FormFieldModel {
|
||||
|
||||
/**
|
||||
* The hints for this metadata field to display on form
|
||||
*/
|
||||
@autoserialize
|
||||
hints: string;
|
||||
|
||||
/**
|
||||
* The label for this metadata field to display on form
|
||||
*/
|
||||
@autoserialize
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* The languages available for this metadata field to display on form
|
||||
*/
|
||||
@autoserialize
|
||||
languageCodes: LanguageCode[];
|
||||
|
||||
/**
|
||||
* The error message for this metadata field to display on form in case of field is required
|
||||
*/
|
||||
@autoserialize
|
||||
mandatoryMessage: string;
|
||||
|
||||
/**
|
||||
* Representing if this metadata field is mandatory or not
|
||||
*/
|
||||
@autoserialize
|
||||
mandatory: string;
|
||||
|
||||
/**
|
||||
* Representing if this metadata field is repeatable or not
|
||||
*/
|
||||
@autoserialize
|
||||
repeatable: boolean;
|
||||
|
||||
/**
|
||||
* Containing additional properties for this metadata field
|
||||
*/
|
||||
@autoserialize
|
||||
input: {
|
||||
/**
|
||||
* Representing the type for this metadata field
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* Containing regex to use for field validation
|
||||
*/
|
||||
regex?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Representing additional vocabulary configuration for this metadata field
|
||||
*/
|
||||
@autoserialize
|
||||
selectableMetadata: FormFieldMetadataValueObject[];
|
||||
selectableMetadata: SelectableMetadata[];
|
||||
|
||||
/**
|
||||
* Representing additional relationship configuration for this metadata field
|
||||
*/
|
||||
@autoserialize
|
||||
selectableRelationship: RelationshipOptions;
|
||||
|
||||
@autoserialize
|
||||
rows: FormRowModel[];
|
||||
|
||||
/**
|
||||
* Representing the scope for this metadata field
|
||||
*/
|
||||
@autoserialize
|
||||
scope: string;
|
||||
|
||||
/**
|
||||
* Containing additional css classes for this metadata field to use on form
|
||||
*/
|
||||
@autoserialize
|
||||
style: string;
|
||||
|
||||
/**
|
||||
* Containing the value for this metadata field
|
||||
*/
|
||||
@autoserialize
|
||||
value: any;
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ describe('DateFieldParser test suite', () => {
|
||||
const parserOptions: ParserOptions = {
|
||||
readOnly: false,
|
||||
submissionScope: null,
|
||||
authorityUuid: null
|
||||
collectionUUID: null
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@@ -12,7 +12,7 @@ describe('DisabledFieldParser test suite', () => {
|
||||
const parserOptions: ParserOptions = {
|
||||
readOnly: false,
|
||||
submissionScope: null,
|
||||
authorityUuid: null
|
||||
collectionUUID: null
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@@ -11,7 +11,7 @@ describe('DropdownFieldParser test suite', () => {
|
||||
const parserOptions: ParserOptions = {
|
||||
readOnly: false,
|
||||
submissionScope: 'testScopeUUID',
|
||||
authorityUuid: null
|
||||
collectionUUID: null
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -26,7 +26,7 @@ describe('DropdownFieldParser test suite', () => {
|
||||
selectableMetadata: [
|
||||
{
|
||||
metadata: 'type',
|
||||
authority: 'common_types_dataset',
|
||||
controlledVocabulary: 'common_types_dataset',
|
||||
closed: false
|
||||
}
|
||||
],
|
||||
@@ -50,7 +50,7 @@ describe('DropdownFieldParser test suite', () => {
|
||||
});
|
||||
|
||||
it('should throw when authority is not passed', () => {
|
||||
field.selectableMetadata[0].authority = null;
|
||||
field.selectableMetadata[0].controlledVocabulary = null;
|
||||
const parser = new DropdownFieldParser(submissionId, field, initFormValues, parserOptions);
|
||||
|
||||
expect(() => parser.parse())
|
||||
|
@@ -31,8 +31,8 @@ export class DropdownFieldParser extends FieldParser {
|
||||
const dropdownModelConfig: DynamicScrollableDropdownModelConfig = this.initModel(null, label);
|
||||
let layout: DynamicFormControlLayout;
|
||||
|
||||
if (isNotEmpty(this.configData.selectableMetadata[0].authority)) {
|
||||
this.setAuthorityOptions(dropdownModelConfig, this.parserOptions.authorityUuid);
|
||||
if (isNotEmpty(this.configData.selectableMetadata[0].controlledVocabulary)) {
|
||||
this.setVocabularyOptions(dropdownModelConfig);
|
||||
if (isNotEmpty(fieldValue)) {
|
||||
dropdownModelConfig.value = fieldValue;
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export class DropdownFieldParser extends FieldParser {
|
||||
const dropdownModel = new DynamicScrollableDropdownModel(dropdownModelConfig, layout);
|
||||
return dropdownModel;
|
||||
} else {
|
||||
throw Error(`Authority name is not available. Please check the form configuration file.`);
|
||||
throw Error(`Controlled Vocabulary name is not available. Please check the form configuration file.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,16 +1,20 @@
|
||||
import { Inject, InjectionToken } from '@angular/core';
|
||||
import { hasValue, isNotEmpty, isNotNull, isNotUndefined } from '../../../empty.util';
|
||||
import { FormFieldModel } from '../models/form-field.model';
|
||||
|
||||
import { uniqueId } from 'lodash';
|
||||
import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model';
|
||||
import { DynamicRowArrayModel, DynamicRowArrayModelConfig } from '../ds-dynamic-form-ui/models/ds-dynamic-row-array-model';
|
||||
import { DsDynamicInputModel, DsDynamicInputModelConfig } from '../ds-dynamic-form-ui/models/ds-dynamic-input.model';
|
||||
import { DynamicFormControlLayout } from '@ng-dynamic-forms/core';
|
||||
|
||||
import { hasValue, isNotEmpty, isNotNull, isNotUndefined } from '../../../empty.util';
|
||||
import { FormFieldModel } from '../models/form-field.model';
|
||||
import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model';
|
||||
import {
|
||||
DynamicRowArrayModel,
|
||||
DynamicRowArrayModelConfig
|
||||
} from '../ds-dynamic-form-ui/models/ds-dynamic-row-array-model';
|
||||
import { DsDynamicInputModel, DsDynamicInputModelConfig } from '../ds-dynamic-form-ui/models/ds-dynamic-input.model';
|
||||
import { setLayout } from './parser.utils';
|
||||
import { AuthorityOptions } from '../../../../core/integration/models/authority-options.model';
|
||||
import { ParserOptions } from './parser-options';
|
||||
import { RelationshipOptions } from '../models/relationship-options.model';
|
||||
import { VocabularyOptions } from '../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||
|
||||
export const SUBMISSION_ID: InjectionToken<string> = new InjectionToken<string>('submissionId');
|
||||
export const CONFIG_DATA: InjectionToken<FormFieldModel> = new InjectionToken<FormFieldModel>('configData');
|
||||
@@ -105,6 +109,52 @@ export abstract class FieldParser {
|
||||
}
|
||||
}
|
||||
|
||||
public setVocabularyOptions(controlModel) {
|
||||
if (isNotEmpty(this.configData.selectableMetadata) && isNotEmpty(this.configData.selectableMetadata[0].controlledVocabulary)) {
|
||||
controlModel.vocabularyOptions = new VocabularyOptions(
|
||||
this.configData.selectableMetadata[0].controlledVocabulary,
|
||||
this.configData.selectableMetadata[0].closed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public setValues(modelConfig: DsDynamicInputModelConfig, fieldValue: any, forceValueAsObj: boolean = false, groupModel?: boolean) {
|
||||
if (isNotEmpty(fieldValue)) {
|
||||
if (groupModel) {
|
||||
// Array, values is an array
|
||||
modelConfig.value = this.getInitGroupValues();
|
||||
if (Array.isArray(modelConfig.value) && modelConfig.value.length > 0 && modelConfig.value[0].language) {
|
||||
// Array Item has language, ex. AuthorityModel
|
||||
modelConfig.language = modelConfig.value[0].language;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof fieldValue === 'object') {
|
||||
modelConfig.metadataValue = fieldValue;
|
||||
modelConfig.language = fieldValue.language;
|
||||
modelConfig.place = fieldValue.place;
|
||||
if (forceValueAsObj) {
|
||||
modelConfig.value = fieldValue;
|
||||
} else {
|
||||
modelConfig.value = fieldValue.value;
|
||||
}
|
||||
} else {
|
||||
if (forceValueAsObj) {
|
||||
// If value isn't an instance of FormFieldMetadataValueObject instantiate it
|
||||
modelConfig.value = new FormFieldMetadataValueObject(fieldValue);
|
||||
} else {
|
||||
if (typeof fieldValue === 'string') {
|
||||
// Case only string
|
||||
modelConfig.value = fieldValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modelConfig;
|
||||
}
|
||||
|
||||
protected getInitValueCount(index = 0, fieldId?): number {
|
||||
const fieldIds = fieldId || this.getAllFieldIds();
|
||||
if (isNotEmpty(this.initFormValues) && isNotNull(fieldIds) && fieldIds.length === 1 && this.initFormValues.hasOwnProperty(fieldIds[0])) {
|
||||
@@ -148,8 +198,8 @@ export abstract class FieldParser {
|
||||
fieldIds.forEach((id) => {
|
||||
if (this.initFormValues.hasOwnProperty(id)) {
|
||||
const valueObj: FormFieldMetadataValueObject = Object.assign(new FormFieldMetadataValueObject(), this.initFormValues[id][innerIndex]);
|
||||
// Set metadata name, used for Qualdrop fields
|
||||
valueObj.metadata = id;
|
||||
// valueObj.value = this.initFormValues[id][innerIndex];
|
||||
values.push(valueObj);
|
||||
}
|
||||
});
|
||||
@@ -238,14 +288,6 @@ export abstract class FieldParser {
|
||||
if (this.configData.languageCodes && this.configData.languageCodes.length > 0) {
|
||||
(controlModel as DsDynamicInputModel).languageCodes = this.configData.languageCodes;
|
||||
}
|
||||
/* (controlModel as DsDynamicInputModel).languageCodes = [{
|
||||
display: 'English',
|
||||
code: 'en_US'
|
||||
},
|
||||
{
|
||||
display: 'Italian',
|
||||
code: 'it_IT'
|
||||
}];*/
|
||||
|
||||
return controlModel;
|
||||
}
|
||||
@@ -291,51 +333,4 @@ export abstract class FieldParser {
|
||||
}
|
||||
}
|
||||
|
||||
public setAuthorityOptions(controlModel, authorityUuid) {
|
||||
if (isNotEmpty(this.configData.selectableMetadata) && isNotEmpty(this.configData.selectableMetadata[0].authority)) {
|
||||
controlModel.authorityOptions = new AuthorityOptions(
|
||||
this.configData.selectableMetadata[0].authority,
|
||||
this.configData.selectableMetadata[0].metadata,
|
||||
authorityUuid,
|
||||
this.configData.selectableMetadata[0].closed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public setValues(modelConfig: DsDynamicInputModelConfig, fieldValue: any, forceValueAsObj: boolean = false, groupModel?: boolean) {
|
||||
if (isNotEmpty(fieldValue)) {
|
||||
if (groupModel) {
|
||||
// Array, values is an array
|
||||
modelConfig.value = this.getInitGroupValues();
|
||||
if (Array.isArray(modelConfig.value) && modelConfig.value.length > 0 && modelConfig.value[0].language) {
|
||||
// Array Item has language, ex. AuthorityModel
|
||||
modelConfig.language = modelConfig.value[0].language;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof fieldValue === 'object') {
|
||||
modelConfig.metadataValue = fieldValue;
|
||||
modelConfig.language = fieldValue.language;
|
||||
modelConfig.place = fieldValue.place;
|
||||
if (forceValueAsObj) {
|
||||
modelConfig.value = fieldValue;
|
||||
} else {
|
||||
modelConfig.value = fieldValue.value;
|
||||
}
|
||||
} else {
|
||||
if (forceValueAsObj) {
|
||||
// If value isn't an instance of FormFieldMetadataValueObject instantiate it
|
||||
modelConfig.value = new FormFieldMetadataValueObject(fieldValue);
|
||||
} else {
|
||||
if (typeof fieldValue === 'string') {
|
||||
// Case only string
|
||||
modelConfig.value = fieldValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return modelConfig;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ describe('ListFieldParser test suite', () => {
|
||||
const parserOptions: ParserOptions = {
|
||||
readOnly: false,
|
||||
submissionScope: 'testScopeUUID',
|
||||
authorityUuid: null
|
||||
collectionUUID: null
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -28,7 +28,7 @@ describe('ListFieldParser test suite', () => {
|
||||
selectableMetadata: [
|
||||
{
|
||||
metadata: 'type',
|
||||
authority: 'type_programme',
|
||||
controlledVocabulary: 'type_programme',
|
||||
closed: false
|
||||
}
|
||||
],
|
||||
|
@@ -1,19 +1,17 @@
|
||||
import { FieldParser } from './field-parser';
|
||||
import { isNotEmpty } from '../../../empty.util';
|
||||
import { IntegrationSearchOptions } from '../../../../core/integration/models/integration-options.model';
|
||||
import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model';
|
||||
import { DynamicListCheckboxGroupModel } from '../ds-dynamic-form-ui/models/list/dynamic-list-checkbox-group.model';
|
||||
import { DynamicListRadioGroupModel } from '../ds-dynamic-form-ui/models/list/dynamic-list-radio-group.model';
|
||||
|
||||
export class ListFieldParser extends FieldParser {
|
||||
searchOptions: IntegrationSearchOptions;
|
||||
|
||||
public modelFactory(fieldValue?: FormFieldMetadataValueObject | any, label?: boolean): any {
|
||||
const listModelConfig = this.initModel(null, label);
|
||||
listModelConfig.repeatable = this.configData.repeatable;
|
||||
|
||||
if (this.configData.selectableMetadata[0].authority
|
||||
&& this.configData.selectableMetadata[0].authority.length > 0) {
|
||||
if (this.configData.selectableMetadata[0].controlledVocabulary
|
||||
&& this.configData.selectableMetadata[0].controlledVocabulary.length > 0) {
|
||||
|
||||
if (isNotEmpty(this.getInitGroupValues())) {
|
||||
listModelConfig.value = [];
|
||||
@@ -26,7 +24,7 @@ export class ListFieldParser extends FieldParser {
|
||||
}
|
||||
});
|
||||
}
|
||||
this.setAuthorityOptions(listModelConfig, this.parserOptions.authorityUuid);
|
||||
this.setVocabularyOptions(listModelConfig);
|
||||
}
|
||||
|
||||
let listModel;
|
||||
|
@@ -12,7 +12,7 @@ describe('LookupFieldParser test suite', () => {
|
||||
const parserOptions: ParserOptions = {
|
||||
readOnly: false,
|
||||
submissionScope: 'testScopeUUID',
|
||||
authorityUuid: null
|
||||
collectionUUID: null
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -27,7 +27,7 @@ describe('LookupFieldParser test suite', () => {
|
||||
selectableMetadata: [
|
||||
{
|
||||
metadata: 'journal',
|
||||
authority: 'JOURNALAuthority',
|
||||
controlledVocabulary: 'JOURNALAuthority',
|
||||
closed: false
|
||||
}
|
||||
],
|
||||
|
@@ -5,10 +5,10 @@ import { FormFieldMetadataValueObject } from '../models/form-field-metadata-valu
|
||||
export class LookupFieldParser extends FieldParser {
|
||||
|
||||
public modelFactory(fieldValue?: FormFieldMetadataValueObject | any, label?: boolean): any {
|
||||
if (this.configData.selectableMetadata[0].authority) {
|
||||
if (this.configData.selectableMetadata[0].controlledVocabulary) {
|
||||
const lookupModelConfig: DynamicLookupModelConfig = this.initModel(null, label);
|
||||
|
||||
this.setAuthorityOptions(lookupModelConfig, this.parserOptions.authorityUuid);
|
||||
this.setVocabularyOptions(lookupModelConfig);
|
||||
|
||||
this.setValues(lookupModelConfig, fieldValue, true);
|
||||
|
||||
|
@@ -12,7 +12,7 @@ describe('LookupNameFieldParser test suite', () => {
|
||||
const parserOptions: ParserOptions = {
|
||||
readOnly: false,
|
||||
submissionScope: 'testScopeUUID',
|
||||
authorityUuid: null
|
||||
collectionUUID: null
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -27,7 +27,7 @@ describe('LookupNameFieldParser test suite', () => {
|
||||
selectableMetadata: [
|
||||
{
|
||||
metadata: 'author',
|
||||
authority: 'RPAuthority',
|
||||
controlledVocabulary: 'RPAuthority',
|
||||
closed: false
|
||||
}
|
||||
],
|
||||
|
@@ -8,10 +8,10 @@ import { FormFieldMetadataValueObject } from '../models/form-field-metadata-valu
|
||||
export class LookupNameFieldParser extends FieldParser {
|
||||
|
||||
public modelFactory(fieldValue?: FormFieldMetadataValueObject | any, label?: boolean): any {
|
||||
if (this.configData.selectableMetadata[0].authority) {
|
||||
if (this.configData.selectableMetadata[0].controlledVocabulary) {
|
||||
const lookupModelConfig: DynamicLookupNameModelConfig = this.initModel(null, label);
|
||||
|
||||
this.setAuthorityOptions(lookupModelConfig, this.parserOptions.authorityUuid);
|
||||
this.setVocabularyOptions(lookupModelConfig);
|
||||
|
||||
this.setValues(lookupModelConfig, fieldValue, true);
|
||||
|
||||
|
@@ -14,7 +14,7 @@ describe('NameFieldParser test suite', () => {
|
||||
const parserOptions: ParserOptions = {
|
||||
readOnly: false,
|
||||
submissionScope: 'testScopeUUID',
|
||||
authorityUuid: null
|
||||
collectionUUID: null
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { FormFieldModel } from '../models/form-field.model';
|
||||
import { OneboxFieldParser } from './onebox-field-parser';
|
||||
import { DynamicQualdropModel } from '../ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model';
|
||||
import { DynamicTypeaheadModel } from '../ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.model';
|
||||
import { DynamicOneboxModel } from '../ds-dynamic-form-ui/models/onebox/dynamic-onebox.model';
|
||||
import { DsDynamicInputModel } from '../ds-dynamic-form-ui/models/ds-dynamic-input.model';
|
||||
import { ParserOptions } from './parser-options';
|
||||
|
||||
@@ -15,7 +15,7 @@ describe('OneboxFieldParser test suite', () => {
|
||||
const parserOptions: ParserOptions = {
|
||||
readOnly: false,
|
||||
submissionScope: 'testScopeUUID',
|
||||
authorityUuid: null
|
||||
collectionUUID: null
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -28,7 +28,7 @@ describe('OneboxFieldParser test suite', () => {
|
||||
selectableMetadata: [
|
||||
{
|
||||
metadata: 'title',
|
||||
authority: 'EVENTAuthority',
|
||||
controlledVocabulary: 'EVENTAuthority',
|
||||
closed: false
|
||||
}
|
||||
],
|
||||
@@ -92,12 +92,12 @@ describe('OneboxFieldParser test suite', () => {
|
||||
expect(fieldModel instanceof DsDynamicInputModel).toBe(true);
|
||||
});
|
||||
|
||||
it('should return a DynamicTypeaheadModel object when selectableMetadata has authority', () => {
|
||||
it('should return a DynamicOneboxModel object when selectableMetadata has authority', () => {
|
||||
const parser = new OneboxFieldParser(submissionId, field1, initFormValues, parserOptions);
|
||||
|
||||
const fieldModel = parser.parse();
|
||||
|
||||
expect(fieldModel instanceof DynamicTypeaheadModel).toBe(true);
|
||||
expect(fieldModel instanceof DynamicOneboxModel).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -12,9 +12,9 @@ import { FormFieldMetadataValueObject } from '../models/form-field-metadata-valu
|
||||
import { isNotEmpty } from '../../../empty.util';
|
||||
import { DsDynamicInputModel, DsDynamicInputModelConfig } from '../ds-dynamic-form-ui/models/ds-dynamic-input.model';
|
||||
import {
|
||||
DsDynamicTypeaheadModelConfig,
|
||||
DynamicTypeaheadModel
|
||||
} from '../ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.model';
|
||||
DsDynamicOneboxModelConfig,
|
||||
DynamicOneboxModel
|
||||
} from '../ds-dynamic-form-ui/models/onebox/dynamic-onebox.model';
|
||||
|
||||
export class OneboxFieldParser extends FieldParser {
|
||||
|
||||
@@ -75,12 +75,12 @@ export class OneboxFieldParser extends FieldParser {
|
||||
inputSelectGroup.group.push(new DsDynamicInputModel(inputModelConfig, clsInput));
|
||||
|
||||
return new DynamicQualdropModel(inputSelectGroup, clsGroup);
|
||||
} else if (this.configData.selectableMetadata[0].authority) {
|
||||
const typeaheadModelConfig: DsDynamicTypeaheadModelConfig = this.initModel(null, label);
|
||||
this.setAuthorityOptions(typeaheadModelConfig, this.parserOptions.authorityUuid);
|
||||
this.setValues(typeaheadModelConfig, fieldValue, true);
|
||||
} else if (this.configData.selectableMetadata[0].controlledVocabulary) {
|
||||
const oneboxModelConfig: DsDynamicOneboxModelConfig = this.initModel(null, label);
|
||||
this.setVocabularyOptions(oneboxModelConfig);
|
||||
this.setValues(oneboxModelConfig, fieldValue, true);
|
||||
|
||||
return new DynamicTypeaheadModel(typeaheadModelConfig);
|
||||
return new DynamicOneboxModel(oneboxModelConfig);
|
||||
} else {
|
||||
const inputModelConfig: DsDynamicInputModelConfig = this.initModel(null, label);
|
||||
this.setValues(inputModelConfig, fieldValue);
|
||||
|
@@ -1,5 +1,5 @@
|
||||
export interface ParserOptions {
|
||||
readOnly: boolean;
|
||||
submissionScope: string;
|
||||
authorityUuid: string
|
||||
collectionUUID: string
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ describe('RelationGroupFieldParser test suite', () => {
|
||||
const parserOptions: ParserOptions = {
|
||||
readOnly: false,
|
||||
submissionScope: 'testScopeUUID',
|
||||
authorityUuid: 'WORKSPACE'
|
||||
collectionUUID: 'WORKSPACE'
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@@ -16,7 +16,7 @@ export class RelationGroupFieldParser extends FieldParser {
|
||||
const modelConfiguration: DynamicRelationGroupModelConfig = this.initModel(null, label);
|
||||
|
||||
modelConfiguration.submissionId = this.submissionId;
|
||||
modelConfiguration.scopeUUID = this.parserOptions.authorityUuid;
|
||||
modelConfiguration.scopeUUID = this.parserOptions.collectionUUID;
|
||||
modelConfiguration.submissionScope = this.parserOptions.submissionScope;
|
||||
if (this.configData && this.configData.rows && this.configData.rows.length > 0) {
|
||||
modelConfiguration.formConfiguration = this.configData.rows;
|
||||
|
@@ -35,7 +35,7 @@ describe('RowParser test suite', () => {
|
||||
selectableMetadata: [
|
||||
{
|
||||
metadata: 'journal',
|
||||
authority: 'JOURNALAuthority',
|
||||
controlledVocabulary: 'JOURNALAuthority',
|
||||
closed: false
|
||||
}
|
||||
],
|
||||
@@ -83,7 +83,7 @@ describe('RowParser test suite', () => {
|
||||
selectableMetadata: [
|
||||
{
|
||||
metadata: 'title',
|
||||
authority: 'EVENTAuthority',
|
||||
controlledVocabulary: 'EVENTAuthority',
|
||||
closed: false
|
||||
}
|
||||
],
|
||||
@@ -103,7 +103,7 @@ describe('RowParser test suite', () => {
|
||||
selectableMetadata: [
|
||||
{
|
||||
metadata: 'title',
|
||||
authority: 'EVENTAuthority',
|
||||
controlledVocabulary: 'EVENTAuthority',
|
||||
closed: false
|
||||
}
|
||||
],
|
||||
@@ -119,7 +119,7 @@ describe('RowParser test suite', () => {
|
||||
selectableMetadata: [
|
||||
{
|
||||
metadata: 'otherTitle',
|
||||
authority: 'EVENTAuthority',
|
||||
controlledVocabulary: 'EVENTAuthority',
|
||||
closed: false
|
||||
}
|
||||
],
|
||||
@@ -141,7 +141,7 @@ describe('RowParser test suite', () => {
|
||||
selectableMetadata: [
|
||||
{
|
||||
metadata: 'type',
|
||||
authority: 'common_types_dataset',
|
||||
controlledVocabulary: 'common_types_dataset',
|
||||
closed: false
|
||||
}
|
||||
],
|
||||
@@ -176,7 +176,7 @@ describe('RowParser test suite', () => {
|
||||
selectableMetadata: [
|
||||
{
|
||||
metadata: 'author',
|
||||
authority: 'RPAuthority',
|
||||
controlledVocabulary: 'RPAuthority',
|
||||
closed: false
|
||||
}
|
||||
],
|
||||
@@ -198,7 +198,7 @@ describe('RowParser test suite', () => {
|
||||
selectableMetadata: [
|
||||
{
|
||||
metadata: 'type',
|
||||
authority: 'type_programme',
|
||||
controlledVocabulary: 'type_programme',
|
||||
closed: false
|
||||
}
|
||||
],
|
||||
@@ -241,7 +241,7 @@ describe('RowParser test suite', () => {
|
||||
selectableMetadata: [
|
||||
{
|
||||
metadata: 'subject',
|
||||
authority: 'JOURNALAuthority',
|
||||
controlledVocabulary: 'JOURNALAuthority',
|
||||
closed: false
|
||||
}
|
||||
],
|
||||
|
@@ -1,21 +1,12 @@
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import {
|
||||
DYNAMIC_FORM_CONTROL_TYPE_ARRAY,
|
||||
DynamicFormGroupModelConfig
|
||||
} from '@ng-dynamic-forms/core';
|
||||
|
||||
import { DYNAMIC_FORM_CONTROL_TYPE_ARRAY, DynamicFormGroupModelConfig } from '@ng-dynamic-forms/core';
|
||||
import { uniqueId } from 'lodash';
|
||||
|
||||
import { IntegrationSearchOptions } from '../../../../core/integration/models/integration-options.model';
|
||||
import { isEmpty } from '../../../empty.util';
|
||||
import { DynamicRowGroupModel } from '../ds-dynamic-form-ui/models/ds-dynamic-row-group-model';
|
||||
import { FormFieldModel } from '../models/form-field.model';
|
||||
import {
|
||||
CONFIG_DATA,
|
||||
FieldParser,
|
||||
INIT_FORM_VALUES,
|
||||
PARSER_OPTIONS,
|
||||
SUBMISSION_ID
|
||||
} from './field-parser';
|
||||
import { CONFIG_DATA, FieldParser, INIT_FORM_VALUES, PARSER_OPTIONS, SUBMISSION_ID } from './field-parser';
|
||||
import { ParserFactory } from './parser-factory';
|
||||
import { ParserOptions } from './parser-options';
|
||||
import { ParserType } from './parser-type';
|
||||
@@ -48,8 +39,6 @@ export class RowParser {
|
||||
group: [],
|
||||
};
|
||||
|
||||
const authorityOptions = new IntegrationSearchOptions(scopeUUID);
|
||||
|
||||
const scopedFields: FormFieldModel[] = this.filterScopedFields(rowData.fields, submissionScope);
|
||||
|
||||
const layoutDefaultGridClass = ' col-sm-' + Math.trunc(12 / scopedFields.length);
|
||||
@@ -58,7 +47,7 @@ export class RowParser {
|
||||
const parserOptions: ParserOptions = {
|
||||
readOnly: readOnly,
|
||||
submissionScope: submissionScope,
|
||||
authorityUuid: authorityOptions.uuid
|
||||
collectionUUID: scopeUUID
|
||||
};
|
||||
|
||||
// Iterate over row's fields
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user