Merge branch 'master' into submission-miscellaneous-fixes

This commit is contained in:
Giuseppe Digilio
2019-08-22 15:40:56 +02:00
268 changed files with 3753 additions and 3791 deletions

View File

@@ -13,6 +13,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
import { HostWindowService } from '../../../shared/host-window.service';
import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub';
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
describe('BitstreamFormatsComponent', () => {
let comp: BitstreamFormatsComponent;
@@ -52,7 +53,7 @@ describe('BitstreamFormatsComponent', () => {
extensions: null
}
];
const mockFormats = observableOf(new RemoteData(false, false, true, undefined, new PaginatedList(null, mockFormatsList)));
const mockFormats = createSuccessfulRemoteDataObject$(new PaginatedList(null, mockFormatsList));
const registryServiceStub = {
getBitstreamFormats: () => mockFormats
};

View File

@@ -1,7 +1,7 @@
import { Action } from '@ngrx/store';
import { type } from '../../../shared/ngrx/type';
import { MetadataSchema } from '../../../core/metadata/metadataschema.model';
import { MetadataField } from '../../../core/metadata/metadatafield.model';
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
import { MetadataField } from '../../../core/metadata/metadata-field.model';
/**
* For each action type in an action group, make a simple

View File

@@ -17,6 +17,7 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
import { RestResponse } from '../../../core/cache/response.models';
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
describe('MetadataRegistryComponent', () => {
let comp: MetadataRegistryComponent;
@@ -36,7 +37,7 @@ describe('MetadataRegistryComponent', () => {
namespace: 'http://dspace.org/mockschema'
}
];
const mockSchemas = observableOf(new RemoteData(false, false, true, undefined, new PaginatedList(null, mockSchemasList)));
const mockSchemas = createSuccessfulRemoteDataObject$(new PaginatedList(null, mockSchemasList));
/* tslint:disable:no-empty */
const registryServiceStub = {
getMetadataSchemas: () => mockSchemas,

View File

@@ -3,7 +3,6 @@ import { RegistryService } from '../../../core/registry/registry.service';
import { Observable, combineLatest as observableCombineLatest } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list';
import { MetadataSchema } from '../../../core/metadata/metadataschema.model';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { map, take } from 'rxjs/operators';
import { hasValue } from '../../../shared/empty.util';
@@ -12,6 +11,7 @@ import { zip } from 'rxjs/internal/observable/zip';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { Route, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
@Component({
selector: 'ds-metadata-registry',

View File

@@ -7,8 +7,8 @@ import {
MetadataRegistrySelectSchemaAction
} from './metadata-registry.actions';
import { metadataRegistryReducer, MetadataRegistryState } from './metadata-registry.reducers';
import { MetadataSchema } from '../../../core/metadata/metadataschema.model';
import { MetadataField } from '../../../core/metadata/metadatafield.model';
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
import { MetadataField } from '../../../core/metadata/metadata-field.model';
class NullAction extends MetadataRegistryEditSchemaAction {
type = null;

View File

@@ -1,4 +1,3 @@
import { MetadataSchema } from '../../../core/metadata/metadataschema.model';
import {
MetadataRegistryAction,
MetadataRegistryActionTypes,
@@ -9,7 +8,8 @@ import {
MetadataRegistrySelectFieldAction,
MetadataRegistrySelectSchemaAction
} from './metadata-registry.actions';
import { MetadataField } from '../../../core/metadata/metadatafield.model';
import { MetadataField } from '../../../core/metadata/metadata-field.model';
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
/**
* The metadata registry state.

View File

@@ -10,7 +10,7 @@ import { EnumKeysPipe } from '../../../../shared/utils/enum-keys-pipe';
import { RegistryService } from '../../../../core/registry/registry.service';
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { MetadataSchema } from '../../../../core/metadata/metadataschema.model';
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
describe('MetadataSchemaFormComponent', () => {
let component: MetadataSchemaFormComponent;

View File

@@ -9,9 +9,9 @@ import { FormGroup } from '@angular/forms';
import { RegistryService } from '../../../../core/registry/registry.service';
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
import { take } from 'rxjs/operators';
import { MetadataSchema } from '../../../../core/metadata/metadataschema.model';
import { TranslateService } from '@ngx-translate/core';
import { combineLatest } from 'rxjs/internal/observable/combineLatest';
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
@Component({
selector: 'ds-metadata-schema-form',

View File

@@ -3,7 +3,6 @@ import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing'
import { MetadataFieldFormComponent } from './metadata-field-form.component';
import { RegistryService } from '../../../../core/registry/registry.service';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { MetadataField } from '../../../../core/metadata/metadatafield.model';
import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';
@@ -11,7 +10,8 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { EnumKeysPipe } from '../../../../shared/utils/enum-keys-pipe';
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { MetadataSchema } from '../../../../core/metadata/metadataschema.model';
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
describe('MetadataFieldFormComponent', () => {
let component: MetadataFieldFormComponent;

View File

@@ -1,5 +1,4 @@
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { MetadataSchema } from '../../../../core/metadata/metadataschema.model';
import {
DynamicFormControlModel,
DynamicFormLayout,
@@ -8,10 +7,11 @@ import {
import { FormGroup } from '@angular/forms';
import { RegistryService } from '../../../../core/registry/registry.service';
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
import { MetadataField } from '../../../../core/metadata/metadatafield.model';
import { take } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { combineLatest } from 'rxjs/internal/observable/combineLatest';
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
@Component({
selector: 'ds-metadata-field-form',

View File

@@ -3,7 +3,6 @@ import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing'
import { of as observableOf } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list';
import { MetadataSchema } from '../../../core/metadata/metadataschema.model';
import { TranslateModule } from '@ngx-translate/core';
import { CommonModule } from '@angular/common';
import { ActivatedRoute, Router } from '@angular/router';
@@ -21,6 +20,8 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
import { RestResponse } from '../../../core/cache/response.models';
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
describe('MetadataSchemaComponent', () => {
let comp: MetadataSchemaComponent;
@@ -74,12 +75,12 @@ describe('MetadataSchemaComponent', () => {
schema: mockSchemasList[1]
}
];
const mockSchemas = observableOf(new RemoteData(false, false, true, undefined, new PaginatedList(null, mockSchemasList)));
const mockSchemas = createSuccessfulRemoteDataObject$(new PaginatedList(null, mockSchemasList));
/* tslint:disable:no-empty */
const registryServiceStub = {
getMetadataSchemas: () => mockSchemas,
getMetadataFieldsBySchema: (schema: MetadataSchema) => observableOf(new RemoteData(false, false, true, undefined, new PaginatedList(null, mockFieldsList.filter((value) => value.schema === schema)))),
getMetadataSchemaByName: (schemaName: string) => observableOf(new RemoteData(false, false, true, undefined, mockSchemasList.filter((value) => value.prefix === schemaName)[0])),
getMetadataFieldsBySchema: (schema: MetadataSchema) => createSuccessfulRemoteDataObject$(new PaginatedList(null, mockFieldsList.filter((value) => value.schema === schema))),
getMetadataSchemaByName: (schemaName: string) => createSuccessfulRemoteDataObject$(mockSchemasList.filter((value) => value.prefix === schemaName)[0]),
getActiveMetadataField: () => observableOf(undefined),
getSelectedMetadataFields: () => observableOf([]),
editMetadataField: (schema) => {},

View File

@@ -4,8 +4,6 @@ import { ActivatedRoute, Router } from '@angular/router';
import { Observable, combineLatest as observableCombineLatest } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list';
import { MetadataField } from '../../../core/metadata/metadatafield.model';
import { MetadataSchema } from '../../../core/metadata/metadataschema.model';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { map, take } from 'rxjs/operators';
import { hasValue } from '../../../shared/empty.util';
@@ -13,6 +11,8 @@ import { RestResponse } from '../../../core/cache/response.models';
import { zip } from 'rxjs/internal/observable/zip';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { MetadataField } from '../../../core/metadata/metadata-field.model';
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
@Component({
selector: 'ds-metadata-schema',

View File

@@ -18,6 +18,7 @@ import { Item } from '../../core/shared/item.model';
import { ENV_CONFIG, GLOBAL_CONFIG } from '../../../config';
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
import { toRemoteData } from '../+browse-by-metadata-page/browse-by-metadata-page.component.spec';
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
describe('BrowseByDatePageComponent', () => {
let comp: BrowseByDatePageComponent;
@@ -48,11 +49,11 @@ describe('BrowseByDatePageComponent', () => {
const mockBrowseService = {
getBrowseEntriesFor: (options: BrowseEntrySearchOptions) => toRemoteData([]),
getBrowseItemsFor: (value: string, options: BrowseEntrySearchOptions) => toRemoteData([firstItem]),
getFirstItemFor: () => observableOf(new RemoteData(false, false, true, undefined, firstItem))
getFirstItemFor: () => createSuccessfulRemoteDataObject$(firstItem)
};
const mockDsoService = {
findById: () => observableOf(new RemoteData(false, false, true, null, mockCommunity))
findById: () => createSuccessfulRemoteDataObject$(mockCommunity)
};
const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {

View File

@@ -20,6 +20,9 @@ import { Item } from '../../core/shared/item.model';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { Community } from '../../core/shared/community.model';
import { MockRouter } from '../../shared/mocks/mock-router';
import { ResourceType } from '../../core/shared/resource-type';
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
import { BrowseEntry } from '../../core/shared/browse-entry.model';
describe('BrowseByMetadataPageComponent', () => {
let comp: BrowseByMetadataPageComponent;
@@ -39,21 +42,21 @@ describe('BrowseByMetadataPageComponent', () => {
const mockEntries = [
{
type: 'author',
type: BrowseEntry.type,
authority: null,
value: 'John Doe',
language: 'en',
count: 1
},
{
type: 'author',
type: BrowseEntry.type,
authority: null,
value: 'James Doe',
language: 'en',
count: 3
},
{
type: 'subject',
type: BrowseEntry.type,
authority: null,
value: 'Fake subject',
language: 'en',
@@ -68,12 +71,12 @@ describe('BrowseByMetadataPageComponent', () => {
];
const mockBrowseService = {
getBrowseEntriesFor: (options: BrowseEntrySearchOptions) => toRemoteData(mockEntries.filter((entry) => entry.type === options.metadataDefinition)),
getBrowseEntriesFor: (options: BrowseEntrySearchOptions) => toRemoteData(mockEntries),
getBrowseItemsFor: (value: string, options: BrowseEntrySearchOptions) => toRemoteData(mockItems)
};
const mockDsoService = {
findById: () => observableOf(new RemoteData(false, false, true, null, mockCommunity))
findById: () => createSuccessfulRemoteDataObject$(mockCommunity)
};
const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {
@@ -105,12 +108,6 @@ describe('BrowseByMetadataPageComponent', () => {
fixture.detectChanges();
});
it('should fetch the correct entries depending on the metadata definition', () => {
comp.browseEntries$.subscribe((result) => {
expect(result.payload.page).toEqual(mockEntries.filter((entry) => entry.type === 'author'));
});
});
it('should not fetch any items when no value is provided', () => {
expect(comp.items$).toBeUndefined();
});
@@ -160,5 +157,5 @@ describe('BrowseByMetadataPageComponent', () => {
});
export function toRemoteData(objects: any[]): Observable<RemoteData<PaginatedList<any>>> {
return observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), objects)));
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), objects));
}

View File

@@ -17,6 +17,7 @@ import { RemoteData } from '../../core/data/remote-data';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { BrowseService } from '../../core/browse/browse.service';
import { MockRouter } from '../../shared/mocks/mock-router';
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
describe('BrowseByTitlePageComponent', () => {
let comp: BrowseByTitlePageComponent;
@@ -52,7 +53,7 @@ describe('BrowseByTitlePageComponent', () => {
};
const mockDsoService = {
findById: () => observableOf(new RemoteData(false, false, true, null, mockCommunity))
findById: () => createSuccessfulRemoteDataObject$(mockCommunity)
};
const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {

View File

@@ -1,12 +1,9 @@
import { Component, Input } from '@angular/core';
import {
DynamicInputModel,
DynamicTextAreaModel
} from '@ng-dynamic-forms/core';
import { DynamicInputModel, DynamicTextAreaModel } from '@ng-dynamic-forms/core';
import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model';
import { ResourceType } from '../../core/shared/resource-type';
import { Collection } from '../../core/shared/collection.model';
import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component';
import { NormalizedCollection } from '../../core/cache/models/normalized-collection.model';
/**
* Form used for creating and editing collections
@@ -23,9 +20,9 @@ export class CollectionFormComponent extends ComColFormComponent<Collection> {
@Input() dso: Collection = new Collection();
/**
* @type {ResourceType.Collection} This is a collection-type form
* @type {Collection.type} This is a collection-type form
*/
protected type = ResourceType.Collection;
protected type = Collection.type;
/**
* The dynamic form fields used for creating/editing a collection

View File

@@ -1,9 +1,11 @@
import { CreateCollectionPageGuard } from './create-collection-page.guard';
import { MockRouter } from '../../shared/mocks/mock-router';
import { RemoteData } from '../../core/data/remote-data';
import { Community } from '../../core/shared/community.model';
import { of as observableOf } from 'rxjs';
import { first } from 'rxjs/operators';
import {
createFailedRemoteDataObject$,
createSuccessfulRemoteDataObject$
} from '../../shared/testing/utils';
describe('CreateCollectionPageGuard', () => {
describe('canActivate', () => {
@@ -15,11 +17,11 @@ describe('CreateCollectionPageGuard', () => {
communityDataServiceStub = {
findById: (id: string) => {
if (id === 'valid-id') {
return observableOf(new RemoteData(false, false, true, null, new Community()));
return createSuccessfulRemoteDataObject$(new Community());
} else if (id === 'invalid-id') {
return observableOf(new RemoteData(false, false, true, null, undefined));
return createSuccessfulRemoteDataObject$(undefined);
} else if (id === 'error-id') {
return observableOf(new RemoteData(false, false, false, null, new Community()));
return createFailedRemoteDataObject$(new Community());
}
}
};

View File

@@ -20,9 +20,9 @@ export class CommunityFormComponent extends ComColFormComponent<Community> {
@Input() dso: Community = new Community();
/**
* @type {ResourceType.Community} This is a community-type form
* @type {Community.type} This is a community-type form
*/
protected type = ResourceType.Community;
protected type = Community.type;
/**
* The dynamic form fields used for creating/editing a community

View File

@@ -1,9 +1,11 @@
import { CreateCommunityPageGuard } from './create-community-page.guard';
import { MockRouter } from '../../shared/mocks/mock-router';
import { RemoteData } from '../../core/data/remote-data';
import { Community } from '../../core/shared/community.model';
import { of as observableOf } from 'rxjs';
import { first } from 'rxjs/operators';
import {
createFailedRemoteDataObject$,
createSuccessfulRemoteDataObject$
} from '../../shared/testing/utils';
describe('CreateCommunityPageGuard', () => {
describe('canActivate', () => {
@@ -15,11 +17,11 @@ describe('CreateCommunityPageGuard', () => {
communityDataServiceStub = {
findById: (id: string) => {
if (id === 'valid-id') {
return observableOf(new RemoteData(false, false, true, null, new Community()));
return createSuccessfulRemoteDataObject$(new Community());
} else if (id === 'invalid-id') {
return observableOf(new RemoteData(false, false, true, null, undefined));
return createSuccessfulRemoteDataObject$(undefined);
} else if (id === 'error-id') {
return observableOf(new RemoteData(false, false, false, null, new Community()));
return createFailedRemoteDataObject$(new Community());
}
}
};

View File

@@ -11,6 +11,7 @@ import {RouterTestingModule} from '@angular/router/testing';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {By} from '@angular/platform-browser';
import {of as observableOf, Observable } from 'rxjs';
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
describe('SubCommunityList Component', () => {
let comp: CommunityPageSubCommunityListComponent;
@@ -40,8 +41,7 @@ describe('SubCommunityList Component', () => {
{ language: 'en_US', value: 'Test title' }
]
},
subcommunities: observableOf(new RemoteData(true, true, true,
undefined, new PaginatedList(new PageInfo(), [])))
subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), []))
});
const mockCommunity = Object.assign(new Community(), {
@@ -50,8 +50,7 @@ describe('SubCommunityList Component', () => {
{ language: 'en_US', value: 'Test title' }
]
},
subcommunities: observableOf(new RemoteData(true, true, true,
undefined, new PaginatedList(new PageInfo(), subcommunities)))
subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), subcommunities))
})
;

View File

@@ -2,6 +2,10 @@ import {RemoteData} from '../../core/data/remote-data';
import {hot} from 'jasmine-marbles';
import {Item} from '../../core/shared/item.model';
import {findSuccessfulAccordingTo} from './edit-item-operators';
import {
createFailedRemoteDataObject,
createSuccessfulRemoteDataObject
} from '../../shared/testing/utils';
describe('findSuccessfulAccordingTo', () => {
let mockItem1;
@@ -19,11 +23,11 @@ describe('findSuccessfulAccordingTo', () => {
});
it('should return first successful RemoteData Observable that complies to predicate', () => {
const testRD = {
a: new RemoteData(false, false, true, null, undefined),
b: new RemoteData(false, false, false, null, mockItem1),
c: new RemoteData(false, false, true, null, mockItem2),
d: new RemoteData(false, false, true, null, mockItem1),
e: new RemoteData(false, false, true, null, mockItem2),
a: createSuccessfulRemoteDataObject(undefined),
b: createFailedRemoteDataObject(mockItem1),
c: createSuccessfulRemoteDataObject(mockItem2),
d: createSuccessfulRemoteDataObject(mockItem1),
e: createSuccessfulRemoteDataObject(mockItem2),
};
const source = hot('abcde', testRD);

View File

@@ -17,6 +17,7 @@ import { By } from '@angular/platform-browser';
import { ItemDeleteComponent } from './item-delete.component';
import { getItemEditPath } from '../../item-page-routing.module';
import { RestResponse } from '../../../core/cache/response.models';
import { createSuccessfulRemoteDataObject } from '../../../shared/testing/utils';
let comp: ItemDeleteComponent;
let fixture: ComponentFixture<ItemDeleteComponent>;
@@ -49,7 +50,7 @@ describe('ItemDeleteComponent', () => {
routeStub = {
data: observableOf({
item: new RemoteData(false, false, true, null, mockItem)
item: createSuccessfulRemoteDataObject(mockItem)
})
};

View File

@@ -6,17 +6,18 @@ import { ObjectUpdatesService } from '../../../../core/data/object-updates/objec
import { of as observableOf } from 'rxjs';
import { RemoteData } from '../../../../core/data/remote-data';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { MetadataField } from '../../../../core/metadata/metadatafield.model';
import { By } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { SharedModule } from '../../../../shared/shared.module';
import { getTestScheduler } from 'jasmine-marbles';
import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model';
import { TestScheduler } from 'rxjs/testing';
import { MetadataSchema } from '../../../../core/metadata/metadataschema.model';
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
import { TranslateModule } from '@ngx-translate/core';
import { MetadatumViewModel } from '../../../../core/shared/metadata.models';
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
let comp: EditInPlaceFieldComponent;
let fixture: ComponentFixture<EditInPlaceFieldComponent>;
@@ -59,7 +60,7 @@ describe('EditInPlaceFieldComponent', () => {
paginatedMetadataFields = new PaginatedList(undefined, [mdField1, mdField2, mdField3]);
metadataFieldService = jasmine.createSpyObj({
queryMetadataFields: observableOf(new RemoteData(false, false, true, undefined, paginatedMetadataFields)),
queryMetadataFields: createSuccessfulRemoteDataObject$(paginatedMetadataFields),
});
objectUpdatesService = jasmine.createSpyObj('objectUpdatesService',
{

View File

@@ -4,13 +4,13 @@ import { RegistryService } from '../../../../core/registry/registry.service';
import { cloneDeep } from 'lodash';
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { MetadataField } from '../../../../core/metadata/metadatafield.model';
import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model';
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
import { FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer';
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
import { NgModel } from '@angular/forms';
import { MetadatumViewModel } from '../../../../core/shared/metadata.models';
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
@Component({
// tslint:disable-next-line:component-selector

View File

@@ -20,13 +20,16 @@ import { RouterStub } from '../../../shared/testing/router-stub';
import { GLOBAL_CONFIG } from '../../../../config';
import { Item } from '../../../core/shared/item.model';
import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions';
import { RemoteData } from '../../../core/data/remote-data';
import { MetadatumViewModel } from '../../../core/shared/metadata.models';
import { RegistryService } from '../../../core/registry/registry.service';
import { PaginatedList } from '../../../core/data/paginated-list';
import { MetadataSchema } from '../../../core/metadata/metadataschema.model';
import { MetadataField } from '../../../core/metadata/metadatafield.model';
import { Metadata } from '../../../core/shared/metadata.utils';
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
import { MetadataField } from '../../../core/metadata/metadata-field.model';
import {
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../../shared/testing/utils';
let comp: ItemMetadataComponent;
let fixture: ComponentFixture<ItemMetadataComponent>;
@@ -116,18 +119,18 @@ describe('ItemMetadataComponent', () => {
)
;
itemService = jasmine.createSpyObj('itemService', {
update: observableOf(new RemoteData(false, false, true, undefined, item)),
update: createSuccessfulRemoteDataObject$(item),
commitUpdates: {}
});
routeStub = {
parent: {
data: observableOf({ item: new RemoteData(false, false, true, null, item) })
data: observableOf({ item: createSuccessfulRemoteDataObject(item) })
}
};
paginatedMetadataFields = new PaginatedList(undefined, [mdField1, mdField2, mdField3]);
metadataFieldService = jasmine.createSpyObj({
getAllMetadataFields: observableOf(new RemoteData(false, false, true, undefined, paginatedMetadataFields))
getAllMetadataFields:createSuccessfulRemoteDataObject$(paginatedMetadataFields)
});
scheduler = getTestScheduler();
objectUpdatesService = jasmine.createSpyObj('objectUpdatesService',

View File

@@ -17,9 +17,9 @@ import { NotificationsService } from '../../../shared/notifications/notification
import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config';
import { TranslateService } from '@ngx-translate/core';
import { RegistryService } from '../../../core/registry/registry.service';
import { MetadataField } from '../../../core/metadata/metadatafield.model';
import { MetadatumViewModel } from '../../../core/shared/metadata.models';
import { Metadata } from '../../../core/shared/metadata.utils';
import { MetadataField } from '../../../core/metadata/metadata-field.model';
@Component({
selector: 'ds-item-metadata',

View File

@@ -16,6 +16,7 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { ItemPrivateComponent } from './item-private.component';
import { RestResponse } from '../../../core/cache/response.models';
import { createSuccessfulRemoteDataObject } from '../../../shared/testing/utils';
let comp: ItemPrivateComponent;
let fixture: ComponentFixture<ItemPrivateComponent>;
@@ -50,7 +51,7 @@ describe('ItemPrivateComponent', () => {
routeStub = {
data: observableOf({
item: new RemoteData(false, false, true, null, {
item: createSuccessfulRemoteDataObject({
id: 'fake-id'
})
})

View File

@@ -16,6 +16,7 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { ItemPublicComponent } from './item-public.component';
import { RestResponse } from '../../../core/cache/response.models';
import { createSuccessfulRemoteDataObject } from '../../../shared/testing/utils';
let comp: ItemPublicComponent;
let fixture: ComponentFixture<ItemPublicComponent>;
@@ -50,7 +51,7 @@ describe('ItemPublicComponent', () => {
routeStub = {
data: observableOf({
item: new RemoteData(false, false, true, null, {
item: createSuccessfulRemoteDataObject({
id: 'fake-id'
})
})

View File

@@ -16,6 +16,7 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { ItemReinstateComponent } from './item-reinstate.component';
import { RestResponse } from '../../../core/cache/response.models';
import { createSuccessfulRemoteDataObject } from '../../../shared/testing/utils';
let comp: ItemReinstateComponent;
let fixture: ComponentFixture<ItemReinstateComponent>;
@@ -50,7 +51,7 @@ describe('ItemReinstateComponent', () => {
routeStub = {
data: observableOf({
item: new RemoteData(false, false, true, null, {
item: createSuccessfulRemoteDataObject({
id: 'fake-id'
})
})

View File

@@ -12,7 +12,7 @@
{{'item.edit.tabs.status.labels.itemPage' | translate}}:
</div>
<div class="col-9 float-left status-data" id="status-itemPage">
<a href="{{getItemPage((itemRD$ | async)?.payload)}}">{{getItemPage((itemRD$ | async)?.payload)}}</a>
<a [routerLink]="getItemPage((itemRD$ | async)?.payload)">{{getItemPage((itemRD$ | async)?.payload)}}</a>
</div>
<div *ngFor="let operation of operations" class="w-100 pt-3">

View File

@@ -11,7 +11,7 @@ import { Item } from '../../../core/shared/item.model';
import { By } from '@angular/platform-browser';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { of as observableOf } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { createSuccessfulRemoteDataObject } from '../../../shared/testing/utils';
describe('ItemStatusComponent', () => {
let comp: ItemStatusComponent;
@@ -27,7 +27,7 @@ describe('ItemStatusComponent', () => {
const routeStub = {
parent: {
data: observableOf({ item: new RemoteData(false, false, true, null, mockItem) })
data: observableOf({ item: createSuccessfulRemoteDataObject(mockItem) })
}
};

View File

@@ -16,6 +16,7 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ItemWithdrawComponent } from './item-withdraw.component';
import { By } from '@angular/platform-browser';
import { RestResponse } from '../../../core/cache/response.models';
import { createSuccessfulRemoteDataObject } from '../../../shared/testing/utils';
let comp: ItemWithdrawComponent;
let fixture: ComponentFixture<ItemWithdrawComponent>;
@@ -50,7 +51,7 @@ describe('ItemWithdrawComponent', () => {
routeStub = {
data: observableOf({
item: new RemoteData(false, false, true, null, {
item: createSuccessfulRemoteDataObject({
id: 'fake-id'
})
})

View File

@@ -17,6 +17,10 @@ import { By } from '@angular/platform-browser';
import { of as observableOf } from 'rxjs';
import { getItemEditPath } from '../../item-page-routing.module';
import { RestResponse } from '../../../core/cache/response.models';
import {
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../../shared/testing/utils';
/**
* Test component that implements the AbstractSimpleItemActionComponent used to test the
@@ -65,12 +69,12 @@ describe('AbstractSimpleItemActionComponent', () => {
});
mockItemDataService = jasmine.createSpyObj({
findById: observableOf(new RemoteData(false, false, true, undefined, mockItem))
findById: createSuccessfulRemoteDataObject$(mockItem)
});
routeStub = {
data: observableOf({
item: new RemoteData(false, false, true, null, {
item: createSuccessfulRemoteDataObject({
id: 'fake-id'
})
})

View File

@@ -9,6 +9,10 @@ import { Item } from '../../../core/shared/item.model';
import { of as observableOf } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { TranslateModule } from '@ngx-translate/core';
import {
createFailedRemoteDataObject$,
createSuccessfulRemoteDataObject$
} from '../../../shared/testing/utils';
let collectionsComponent: CollectionsComponent;
let fixture: ComponentFixture<CollectionsComponent>;
@@ -24,8 +28,8 @@ const mockCollection1: Collection = Object.assign(new Collection(), {
}
});
const succeededMockItem: Item = Object.assign(new Item(), {owningCollection: observableOf(new RemoteData(false, false, true, null, mockCollection1))});
const failedMockItem: Item = Object.assign(new Item(), {owningCollection: observableOf(new RemoteData(false, false, false, null, mockCollection1))});
const succeededMockItem: Item = Object.assign(new Item(), {owningCollection: createSuccessfulRemoteDataObject$(mockCollection1)});
const failedMockItem: Item = Object.assign(new Item(), {owningCollection: createFailedRemoteDataObject$(mockCollection1)});
describe('CollectionsComponent', () => {
beforeEach(async(() => {

View File

@@ -17,9 +17,13 @@ import { RemoteData } from '../../core/data/remote-data';
import { of as observableOf } from 'rxjs';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { By } from '@angular/platform-browser';
import {
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../shared/testing/utils';
const mockItem: Item = Object.assign(new Item(), {
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{
@@ -30,7 +34,7 @@ const mockItem: Item = Object.assign(new Item(), {
}
});
const routeStub = Object.assign(new ActivatedRouteStub(), {
data: observableOf({ item: new RemoteData(false, false, true, null, mockItem) })
data: observableOf({ item: createSuccessfulRemoteDataObject(mockItem) })
});
const metadataServiceStub = {
/* tslint:disable:no-empty */

View File

@@ -11,6 +11,7 @@ import { ItemPageFieldComponent } from './item-page-field.component';
import { MetadataValuesComponent } from '../../../field-components/metadata-values/metadata-values.component';
import { of as observableOf } from 'rxjs';
import { MetadataMap, MetadataValue } from '../../../../core/shared/metadata.models';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
let comp: ItemPageFieldComponent;
let fixture: ComponentFixture<ItemPageFieldComponent>;
@@ -52,7 +53,7 @@ describe('ItemPageFieldComponent', () => {
export function mockItemWithMetadataFieldAndValue(field: string, value: string): Item {
const item = Object.assign(new Item(), {
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: new MetadataMap()
});
item.metadata[field] = [{

View File

@@ -16,9 +16,13 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { By } from '@angular/platform-browser';
import { createRelationshipsObservable } from './item-types/shared/item.component.spec';
import { of as observableOf } from 'rxjs';
import {
createFailedRemoteDataObject$, createPendingRemoteDataObject$, createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../shared/testing/utils';
const mockItem: Item = Object.assign(new Item(), {
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: [],
relationships: createRelationshipsObservable()
});
@@ -33,7 +37,7 @@ describe('ItemPageComponent', () => {
/* tslint:enable:no-empty */
};
const mockRoute = Object.assign(new ActivatedRouteStub(), {
data: observableOf({ item: new RemoteData(false, false, true, null, mockItem) })
data: observableOf({ item: createSuccessfulRemoteDataObject(mockItem) })
});
beforeEach(async(() => {
@@ -66,7 +70,8 @@ describe('ItemPageComponent', () => {
describe('when the item is loading', () => {
beforeEach(() => {
comp.itemRD$ = observableOf(new RemoteData(true, true, true, null, undefined));
comp.itemRD$ = createPendingRemoteDataObject$(undefined);
// comp.itemRD$ = observableOf(new RemoteData(true, true, true, null, undefined));
fixture.detectChanges();
});
@@ -78,7 +83,7 @@ describe('ItemPageComponent', () => {
describe('when the item failed loading', () => {
beforeEach(() => {
comp.itemRD$ = observableOf(new RemoteData(false, false, false, null, undefined));
comp.itemRD$ = createFailedRemoteDataObject$(undefined);
fixture.detectChanges();
});

View File

@@ -17,9 +17,10 @@ import { createRelationshipsObservable } from '../shared/item.component.spec';
import { PublicationComponent } from './publication.component';
import { of as observableOf } from 'rxjs';
import { MetadataMap } from '../../../../core/shared/metadata.models';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
const mockItem: Item = Object.assign(new Item(), {
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: new MetadataMap(),
relationships: createRelationshipsObservable()
});

View File

@@ -26,6 +26,7 @@ import { MetadatumRepresentation } from '../../../../core/shared/metadata-repres
import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
import { MetadataMap, MetadataValue } from '../../../../core/shared/metadata.models';
import { compareArraysUsing, compareArraysUsingIds } from './item-relationships-utils';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
/**
* Create a generic test for an item-page-fields component using a mockItem and the type of component
@@ -102,13 +103,13 @@ export function containsFieldInput(fields: DebugElement[], metadataKey: string):
}
export function createRelationshipsObservable() {
return observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [
Object.assign(new Relationship(), {
relationshipType: observableOf(new RemoteData(false, false, true, null, new RelationshipType())),
leftItem: observableOf(new RemoteData(false, false, true, null, new Item())),
rightItem: observableOf(new RemoteData(false, false, true, null, new Item()))
relationshipType: createSuccessfulRemoteDataObject$(new RelationshipType()),
leftItem: createSuccessfulRemoteDataObject$(new Item()),
rightItem: createSuccessfulRemoteDataObject$(new Item())
})
])));
]));
}
describe('ItemComponent', () => {
const arr1 = [
@@ -337,15 +338,15 @@ describe('ItemComponent', () => {
uuid: '1',
metadata: new MetadataMap()
});
mockItem.relationships = observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [
mockItem.relationships = createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [
Object.assign(new Relationship(), {
uuid: '123',
id: '123',
leftItem: observableOf(new RemoteData(false, false, true, null, mockItem)),
rightItem: observableOf(new RemoteData(false, false, true, null, relatedItem)),
relationshipType: observableOf(new RemoteData(false, false, true, null, new RelationshipType()))
leftItem: createSuccessfulRemoteDataObject$(mockItem),
rightItem: createSuccessfulRemoteDataObject$(relatedItem),
relationshipType: createSuccessfulRemoteDataObject$(new RelationshipType())
})
])));
]));
mockItem.metadata[metadataField] = [
{
value: 'Second value',
@@ -369,7 +370,7 @@ describe('ItemComponent', () => {
const mockItemDataService = Object.assign({
findById: (id) => {
if (id === relatedItem.id) {
return observableOf(new RemoteData(false, false, true, null, relatedItem))
return createSuccessfulRemoteDataObject$(relatedItem)
}
}
}) as ItemDataService;

View File

@@ -1,6 +1,6 @@
<ds-filtered-search-page
[fixedFilterQuery]="fixedFilter"
[fixedFilter$]="fixedFilter$"
[configuration$]="configuration$"
[searchEnabled]="searchEnabled"
[sideBarWidth]="sideBarWidth">
</ds-filtered-search-page>

View File

@@ -47,9 +47,9 @@ describe('RelatedEntitiesSearchComponent', () => {
expect(comp.fixedFilter).toEqual(mockFilter);
});
it('should create a fixedFilter$', () => {
comp.fixedFilter$.subscribe((fixedFilter) => {
expect(fixedFilter).toEqual(mockRelationEntityType);
it('should create a configuration$', () => {
comp.configuration$.subscribe((configuration) => {
expect(configuration).toEqual(mockRelationEntityType);
})
});

View File

@@ -47,7 +47,7 @@ export class RelatedEntitiesSearchComponent implements OnInit {
@Input() sideBarWidth = 4;
fixedFilter: string;
fixedFilter$: Observable<string>;
configuration$: Observable<string>;
constructor(private fixedFilterService: SearchFixedFilterService) {
}
@@ -57,7 +57,7 @@ export class RelatedEntitiesSearchComponent implements OnInit {
this.fixedFilter = this.fixedFilterService.getFilterByRelation(this.relationType, this.item.id);
}
if (isNotEmpty(this.relationEntityType)) {
this.fixedFilter$ = of(this.relationEntityType);
this.configuration$ = of(this.relationEntityType);
}
}

View File

@@ -8,14 +8,15 @@ import { PageInfo } from '../../../core/shared/page-info.model';
import { By } from '@angular/platform-browser';
import { createRelationshipsObservable } from '../item-types/shared/item.component.spec';
import { of as observableOf } from 'rxjs';
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
const mockItem1: Item = Object.assign(new Item(), {
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: [],
relationships: createRelationshipsObservable()
});
const mockItem2: Item = Object.assign(new Item(), {
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: [],
relationships: createRelationshipsObservable()
});

View File

@@ -29,6 +29,7 @@ import { RoleDirective } from '../shared/roles/role.directive';
import { RoleService } from '../core/roles/role.service';
import { MockRoleService } from '../shared/mocks/mock-role-service';
import { SearchFixedFilterService } from '../+search-page/search-filters/search-filter/search-fixed-filter.service';
import { createSuccessfulRemoteDataObject$ } from '../shared/testing/utils';
describe('MyDSpacePageComponent', () => {
let comp: MyDSpacePageComponent;
@@ -46,7 +47,7 @@ describe('MyDSpacePageComponent', () => {
pagination.currentPage = 1;
pagination.pageSize = 10;
const sort: SortOptions = new SortOptions('score', SortDirection.DESC);
const mockResults = observableOf(new RemoteData(false, false, true, null, ['test', 'data']));
const mockResults = createSuccessfulRemoteDataObject$(['test', 'data']);
const searchServiceStub = jasmine.createSpyObj('SearchService', {
search: mockResults,
getSearchLink: '/mydspace',

View File

@@ -0,0 +1,21 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { configureSearchComponentTestingModule } from './search-page.component.spec';
import { SearchConfigurationService } from './search-service/search-configuration.service';
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
describe('ConfigurationSearchPageComponent', () => {
let comp: ConfigurationSearchPageComponent;
let fixture: ComponentFixture<ConfigurationSearchPageComponent>;
let searchConfigService: SearchConfigurationService;
beforeEach(async(() => {
configureSearchComponentTestingModule(ConfigurationSearchPageComponent);
}));
beforeEach(() => {
fixture = TestBed.createComponent(ConfigurationSearchPageComponent);
comp = fixture.componentInstance;
searchConfigService = (comp as any).searchConfigService;
fixture.detectChanges();
});
});

View File

@@ -0,0 +1,71 @@
import { HostWindowService } from '../shared/host-window.service';
import { SearchService } from './search-service/search.service';
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
import { SearchPageComponent } from './search-page.component';
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
import { pushInOut } from '../shared/animations/push';
import { RouteService } from '../shared/services/route.service';
import { SearchConfigurationService } from './search-service/search-configuration.service';
import { Observable } from 'rxjs';
import { PaginatedSearchOptions } from './paginated-search-options.model';
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
import { map } from 'rxjs/operators';
/**
* This component renders a search page using a configuration as input.
*/
@Component({
selector: 'ds-configuration-search-page',
styleUrls: ['./search-page.component.scss'],
templateUrl: './search-page.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [pushInOut],
providers: [
{
provide: SEARCH_CONFIG_SERVICE,
useClass: SearchConfigurationService
}
]
})
export class ConfigurationSearchPageComponent extends SearchPageComponent implements OnInit {
/**
* The configuration to use for the search options
* If empty, the configuration will be determined by the route parameter called 'configuration'
*/
@Input() configuration: string;
constructor(protected service: SearchService,
protected sidebarService: SearchSidebarService,
protected windowService: HostWindowService,
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
protected routeService: RouteService) {
super(service, sidebarService, windowService, searchConfigService, routeService);
}
/**
* Listening to changes in the paginated search options
* If something changes, update the search results
*
* Listen to changes in the scope
* If something changes, update the list of scopes for the dropdown
*/
ngOnInit(): void {
super.ngOnInit();
}
/**
* Get the current paginated search options after updating the configuration using the configuration input
* This is to make sure the configuration is included in the paginated search options, as it is not part of any
* query or route parameters
* @returns {Observable<PaginatedSearchOptions>}
*/
protected getSearchOptions(): Observable<PaginatedSearchOptions> {
return this.searchConfigService.paginatedSearchOptions.pipe(
map((options: PaginatedSearchOptions) => {
const config = this.configuration || options.configuration;
return Object.assign(options, { configuration: config });
})
);
}
}

View File

@@ -0,0 +1,22 @@
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable()
/**
* Assemble the correct i18n key for the configuration search page's title depending on the current route's configuration parameter.
* The format of the key will be "{configuration}.search.title" with:
* - configuration: The current configuration stored in route.params
*/
export class ConfigurationSearchPageGuard implements CanActivate {
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
const configuration = route.params.configuration;
const newTitle = configuration + '.search.title';
route.data = { title: newTitle };
return true;
}
}

View File

@@ -1,22 +0,0 @@
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable()
/**
* Assemble the correct i18n key for the filtered search page's title depending on the current route's filter parameter.
* The format of the key will be "{filter}.search.title" with:
* - filter: The current filter stored in route.params
*/
export class FilteredSearchPageGuard implements CanActivate {
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
const filter = route.params.filter;
const newTitle = filter + '.search.title';
route.data = { title: newTitle };
return true;
}
}

View File

@@ -1,10 +1,12 @@
import { autoserialize, autoserializeAs } from 'cerialize';
import { autoserialize, inheritSerialization } from 'cerialize';
import { MetadataMap } from '../core/shared/metadata.models';
import { ListableObject } from '../shared/object-collection/shared/listable-object.model';
import { NormalizedObject } from '../core/cache/models/normalized-object.model';
/**
* Represents a normalized version of a search result object of a certain DSpaceObject
*/
@inheritSerialization(NormalizedObject)
export class NormalizedSearchResult implements ListableObject {
/**
* The UUID of the DSpaceObject that was found

View File

@@ -20,6 +20,7 @@ import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-d
import { SearchConfigurationServiceStub } from '../../../../shared/testing/search-configuration-service-stub';
import { SEARCH_CONFIG_SERVICE } from '../../../../+my-dspace-page/my-dspace-page.component';
import { tap } from 'rxjs/operators';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
describe('SearchFacetFilterComponent', () => {
let comp: SearchFacetFilterComponent;
@@ -61,7 +62,7 @@ describe('SearchFacetFilterComponent', () => {
let router;
const page = observableOf(0);
const mockValues = observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), values)));
const mockValues = createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), values));
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],

View File

@@ -20,6 +20,7 @@ import { RouteService } from '../../../../shared/services/route.service';
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
import { SEARCH_CONFIG_SERVICE } from '../../../../+my-dspace-page/my-dspace-page.component';
import { SearchConfigurationServiceStub } from '../../../../shared/testing/search-configuration-service-stub';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
describe('SearchRangeFilterComponent', () => {
let comp: SearchRangeFilterComponent;
@@ -66,7 +67,7 @@ describe('SearchRangeFilterComponent', () => {
let router;
const page = observableOf(0);
const mockValues = observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), values)));
const mockValues = createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), values));
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],

View File

@@ -2,14 +2,14 @@ import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { SearchPageComponent } from './search-page.component';
import { FilteredSearchPageComponent } from './filtered-search-page.component';
import { FilteredSearchPageGuard } from './filtered-search-page.guard';
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
@NgModule({
imports: [
RouterModule.forChild([
{ path: '', component: SearchPageComponent, data: { title: 'search.title' } },
{ path: ':filter', component: FilteredSearchPageComponent, canActivate: [FilteredSearchPageGuard]}
{ path: ':configuration', component: ConfigurationSearchPageComponent, canActivate: [ConfigurationSearchPageGuard]}
])
]
})

View File

@@ -33,7 +33,7 @@
</div>
<ds-search-results [searchResults]="resultsRD$ | async"
[searchConfig]="searchOptions$ | async"
[fixedFilter]="fixedFilter$ | async"
[configuration]="configuration$ | async"
[disableHeader]="!searchEnabled"></ds-search-results>
</div>
</div>

View File

@@ -25,6 +25,7 @@ import { RouteService } from '../shared/services/route.service';
import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub';
import { PaginatedSearchOptions } from './paginated-search-options.model';
import { SearchFixedFilterService } from './search-filters/search-filter/search-fixed-filter.service';
import { createSuccessfulRemoteDataObject$ } from '../shared/testing/utils';
let comp: SearchPageComponent;
let fixture: ComponentFixture<SearchPageComponent>;
@@ -41,7 +42,7 @@ pagination.id = 'search-results-pagination';
pagination.currentPage = 1;
pagination.pageSize = 10;
const sort: SortOptions = new SortOptions('score', SortDirection.DESC);
const mockResults = observableOf(new RemoteData(false, false, true, null, ['test', 'data']));
const mockResults = createSuccessfulRemoteDataObject$(['test', 'data']);
const searchServiceStub = jasmine.createSpyObj('SearchService', {
search: mockResults,
getSearchLink: '/search',

View File

@@ -86,10 +86,10 @@ export class SearchPageComponent implements OnInit {
sideBarWidth = 3;
/**
* The currently applied filter (determines title of search)
* The currently applied configuration (determines title of search)
*/
@Input()
fixedFilter$: Observable<string>;
configuration$: Observable<string>;
constructor(protected service: SearchService,
protected sidebarService: SearchSidebarService,
@@ -116,8 +116,8 @@ export class SearchPageComponent implements OnInit {
this.scopeListRD$ = this.searchConfigService.getCurrentScope('').pipe(
switchMap((scopeId) => this.service.getScopes(scopeId))
);
if (!isNotEmpty(this.fixedFilter$)) {
this.fixedFilter$ = this.routeService.getRouteParameterValue('filter');
if (!isNotEmpty(this.configuration$)) {
this.configuration$ = this.routeService.getRouteParameterValue('configuration');
}
}

View File

@@ -17,9 +17,7 @@ import { SearchFiltersComponent } from './search-filters/search-filters.componen
import { SearchFilterComponent } from './search-filters/search-filter/search-filter.component';
import { SearchFacetFilterComponent } from './search-filters/search-filter/search-facet-filter/search-facet-filter.component';
import { SearchFilterService } from './search-filters/search-filter/search-filter.service';
import { FilteredSearchPageComponent } from './filtered-search-page.component';
import { SearchFixedFilterService } from './search-filters/search-filter/search-fixed-filter.service';
import { FilteredSearchPageGuard } from './filtered-search-page.guard';
import { SearchLabelsComponent } from './search-labels/search-labels.component';
import { SearchRangeFilterComponent } from './search-filters/search-filter/search-range-filter/search-range-filter.component';
import { SearchTextFilterComponent } from './search-filters/search-filter/search-text-filter/search-text-filter.component';
@@ -32,6 +30,9 @@ import { SearchFacetSelectedOptionComponent } from './search-filters/search-filt
import { SearchFacetRangeOptionComponent } from './search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component';
import { SearchSwitchConfigurationComponent } from './search-switch-configuration/search-switch-configuration.component';
import { SearchAuthorityFilterComponent } from './search-filters/search-filter/search-authority-filter/search-authority-filter.component';
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
import { FilteredSearchPageComponent } from './filtered-search-page.component';
const effects = [
SearchSidebarEffects
@@ -60,7 +61,8 @@ const components = [
SearchFacetRangeOptionComponent,
SearchSwitchConfigurationComponent,
SearchAuthorityFilterComponent,
FilteredSearchPageComponent
FilteredSearchPageComponent,
ConfigurationSearchPageComponent
];
@NgModule({
@@ -76,7 +78,7 @@ const components = [
SearchSidebarService,
SearchFilterService,
SearchFixedFilterService,
FilteredSearchPageGuard,
ConfigurationSearchPageGuard,
SearchFilterService,
SearchConfigurationService
],

View File

@@ -1,4 +1,4 @@
<h2 *ngIf="!disableHeader">{{ getTitleKey() | translate }}</h2>
<h2 *ngIf="!disableHeader">{{ (configuration ? configuration + '.search.results.head' : 'search.results.head') | translate }}</h2>
<div *ngIf="searchResults?.hasSucceeded && !searchResults?.isLoading && searchResults?.payload?.page.length > 0" @fadeIn>
<ds-viewable-collection
[config]="searchConfig.pagination"

View File

@@ -114,7 +114,7 @@ export const objects = [
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/communities/7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f',
type: ResourceType.Community,
type: Community.type,
metadata: {
'dc.description': [
{
@@ -168,7 +168,7 @@ export const objects = [
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/communities/9076bd16-e69a-48d6-9e41-0238cb40d863',
id: '9076bd16-e69a-48d6-9e41-0238cb40d863',
uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863',
type: ResourceType.Community,
type: Community.type,
metadata: {
'dc.description': [
{

View File

@@ -45,9 +45,9 @@ export class SearchResultsComponent {
@Input() viewMode: SetViewMode;
/**
* An optional fixed filter to filter the result on one type
* An optional configuration to filter the result on one type
*/
@Input() fixedFilter: string;
@Input() configuration: string;
/**
* Whether or not to hide the header of the results
@@ -55,19 +55,6 @@ export class SearchResultsComponent {
*/
@Input() disableHeader = false;
/**
* Get the i18n key for the title depending on the fixed filter
* Defaults to 'search.results.head' if there's no fixed filter found
* @returns {string}
*/
getTitleKey() {
if (isNotEmpty(this.fixedFilter)) {
return this.fixedFilter + '.search.results.head'
} else {
return 'search.results.head';
}
}
/**
* Method to change the given string by surrounding it by quotes if not already present.
*/

View File

@@ -9,7 +9,7 @@ import {
of as observableOf,
Subscription
} from 'rxjs';
import { filter, flatMap, map, switchMap, tap } from 'rxjs/operators';
import { filter, flatMap, map, startWith, switchMap, tap } from 'rxjs/operators';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { SearchOptions } from '../search-options.model';
@@ -21,6 +21,7 @@ import { getSucceededRemoteData } from '../../core/shared/operators';
import { SearchFilter } from '../search-filter.model';
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model';
import { SearchFixedFilterService } from '../search-filters/search-filter/search-fixed-filter.service';
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
/**
* Service that performs all actions that have to do with the current search configuration
@@ -109,9 +110,14 @@ export class SearchConfigurationService implements OnDestroy {
* @returns {Observable<string>} Emits the current configuration string
*/
getCurrentConfiguration(defaultConfiguration: string) {
return this.routeService.getQueryParameterValue('configuration').pipe(map((configuration) => {
return configuration || defaultConfiguration;
}));
return observableCombineLatest(
this.routeService.getQueryParameterValue('configuration').pipe(startWith(undefined)),
this.routeService.getRouteParameterValue('configuration').pipe(startWith(undefined))
).pipe(
map(([queryConfig, routeConfig]) => {
return queryConfig || routeConfig || defaultConfiguration;
})
);
}
/**
@@ -269,7 +275,7 @@ export class SearchConfigurationService implements OnDestroy {
scope: this.defaultScope,
query: this.defaultQuery
});
this._defaults = observableOf(new RemoteData(false, false, true, null, options));
this._defaults = createSuccessfulRemoteDataObject$(options);
}
return this._defaults;
}

View File

@@ -1,6 +1,6 @@
import { GenericConstructor } from '../../core/shared/generic-constructor';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { isNull } from '../../shared/empty.util';
import { hasNoValue, isNull } from '../../shared/empty.util';
/**
* Contains the mapping between a search result component and a DSpaceObject
@@ -34,7 +34,7 @@ export function searchResultFor(domainConstructor: GenericConstructor<ListableOb
* @returns The component's constructor that matches the given DSpaceObject
*/
export function getSearchResultFor(domainConstructor: GenericConstructor<ListableObject>, configuration: string = null) {
if (isNull(configuration) || configuration === 'default') {
if (isNull(configuration) || configuration === 'default' || hasNoValue(searchResultMap.get(configuration))) {
return searchResultMap.get(domainConstructor);
} else {
return searchResultMap.get(configuration).get(domainConstructor);

View File

@@ -28,6 +28,7 @@ import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.serv
import { map } from 'rxjs/operators';
import { RouteService } from '../../shared/services/route.service';
import { routeServiceStub } from '../../shared/testing/route-service-stub';
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
@Component({ template: '' })
class DummyComponent {
@@ -91,7 +92,7 @@ describe('SearchService', () => {
);
},
aggregate: (input: Array<Observable<RemoteData<any>>>): Observable<RemoteData<any[]>> => {
return observableOf(new RemoteData(false, false, true, null, []));
return createSuccessfulRemoteDataObject$([]);
}
};

View File

@@ -1,6 +1,6 @@
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
import { Injectable, OnDestroy } from '@angular/core';
import { NavigationExtras, PRIMARY_OUTLET, Router, UrlSegmentGroup } from '@angular/router';
import { NavigationExtras, Router } from '@angular/router';
import { first, map, switchMap } from 'rxjs/operators';
import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service';
import {
@@ -40,7 +40,6 @@ import { PaginatedSearchOptions } from '../paginated-search-options.model';
import { Community } from '../../core/shared/community.model';
import { CommunityDataService } from '../../core/data/community-data.service';
import { ViewMode } from '../../core/shared/view-mode.model';
import { ResourceType } from '../../core/shared/resource-type';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { RouteService } from '../../shared/services/route.service';
@@ -296,7 +295,7 @@ export class SearchService implements OnDestroy {
const scopeObject: Observable<RemoteData<DSpaceObject>> = this.dspaceObjectService.findById(scopeId).pipe(getSucceededRemoteData());
const scopeList: Observable<DSpaceObject[]> = scopeObject.pipe(
switchMap((dsoRD: RemoteData<DSpaceObject>) => {
if (dsoRD.payload.type === ResourceType.Community) {
if ((dsoRD.payload as any).type === Community.type.value) {
const community: Community = dsoRD.payload as Community;
return observableCombineLatest(community.subcommunities, community.collections).pipe(
map(([subCommunities, collections]) => {

View File

@@ -20,4 +20,4 @@ import { SubmissionEditComponent } from '../submission/edit/submission-edit.comp
/**
* This module defines the default component to load when navigating to the workflowitems edit page path.
*/
export class WorkflowitemsEditPageRoutingModule { }
export class WorkflowItemsEditPageRoutingModule { }

View File

@@ -1,12 +1,12 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { WorkflowitemsEditPageRoutingModule } from './workflowitems-edit-page-routing.module';
import { WorkflowItemsEditPageRoutingModule } from './workflowitems-edit-page-routing.module';
import { SubmissionModule } from '../submission/submission.module';
@NgModule({
imports: [
WorkflowitemsEditPageRoutingModule,
WorkflowItemsEditPageRoutingModule,
CommonModule,
SharedModule,
SubmissionModule,
@@ -16,6 +16,6 @@ import { SubmissionModule } from '../submission/submission.module';
/**
* This module handles all modules that need to access the workflowitems edit page.
*/
export class WorkflowitemsEditPageModule {
export class WorkflowItemsEditPageModule {
}

View File

@@ -32,7 +32,7 @@ export function getCommunityModulePath() {
{ path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' },
{ path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' },
{ path: 'workspaceitems', loadChildren: './+workspaceitems-edit-page/workspaceitems-edit-page.module#WorkspaceitemsEditPageModule' },
{ path: 'workflowitems', loadChildren: './+workflowitems-edit-page/workflowitems-edit-page.module#WorkflowitemsEditPageModule' },
{ path: 'workflowitems', loadChildren: './+workflowitems-edit-page/workflowitems-edit-page.module#WorkflowItemsEditPageModule' },
{ path: '**', pathMatch: 'full', component: PageNotFoundComponent },
])
],

View File

@@ -1,29 +0,0 @@
import { AuthType } from './auth-type';
import { GenericConstructor } from '../shared/generic-constructor';
import { NormalizedAuthStatus } from './models/normalized-auth-status.model';
import { NormalizedEPerson } from '../eperson/models/normalized-eperson.model';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { CacheableObject } from '../cache/object-cache.reducer';
import { NormalizedGroup } from '../eperson/models/normalized-group.model';
export class AuthObjectFactory {
public static getConstructor(type): GenericConstructor<NormalizedObject<CacheableObject>> {
switch (type) {
case AuthType.EPerson: {
return NormalizedEPerson
}
case AuthType.Group: {
return NormalizedGroup
}
case AuthType.Status: {
return NormalizedAuthStatus
}
default: {
return undefined;
}
}
}
}

View File

@@ -35,95 +35,103 @@ describe('AuthResponseParsingService', () => {
});
describe('parse', () => {
const validRequest = new AuthPostRequest(
'69f375b5-19f4-4453-8c7a-7dc5c55aafbb',
'https://rest.api/dspace-spring-rest/api/authn/login',
'password=test&user=myself@testshib.org');
let validRequest;
let validRequest2;
let validResponse;
let validResponse1;
let validResponse2;
beforeEach(() => {
const validRequest2 = new AuthGetRequest(
'69f375b5-19f4-4453-8c7a-7dc5c55aafbb',
'https://rest.api/dspace-spring-rest/api/authn/status');
validRequest = new AuthPostRequest(
'69f375b5-19f4-4453-8c7a-7dc5c55aafbb',
'https://rest.api/dspace-spring-rest/api/authn/login',
'password=test&user=myself@testshib.org');
const validResponse = {
payload: {
authenticated: true,
id: null,
okay: true,
token: {
accessToken: 'eyJhbGciOiJIUzI1NiJ9.eyJlaWQiOiI0ZGM3MGFiNS1jZDczLTQ5MmYtYjAwNy0zMTc5ZDJkOTI5NmIiLCJzZyI6W10sImV4cCI6MTUyNjMxODMyMn0.ASmvcbJFBfzhN7D5ncloWnaVZr5dLtgTuOgHaCKiimc',
expires: 1526318322000
},
} as AuthStatus,
statusCode: 200,
statusText: '200'
};
validRequest2 = new AuthGetRequest(
'69f375b5-19f4-4453-8c7a-7dc5c55aafbb',
'https://rest.api/dspace-spring-rest/api/authn/status');
const validResponse1 = {
payload: {},
statusCode: 404,
statusText: '404'
};
validResponse = {
payload: {
authenticated: true,
id: null,
okay: true,
token: {
accessToken: 'eyJhbGciOiJIUzI1NiJ9.eyJlaWQiOiI0ZGM3MGFiNS1jZDczLTQ5MmYtYjAwNy0zMTc5ZDJkOTI5NmIiLCJzZyI6W10sImV4cCI6MTUyNjMxODMyMn0.ASmvcbJFBfzhN7D5ncloWnaVZr5dLtgTuOgHaCKiimc',
expires: 1526318322000
},
} as AuthStatus,
statusCode: 200,
statusText: '200'
};
const validResponse2 = {
payload: {
authenticated: true,
id: null,
okay: true,
type: 'status',
_embedded: {
eperson: {
canLogIn: true,
email: 'myself@testshib.org',
groups: [],
handle: null,
id: '4dc70ab5-cd73-492f-b007-3179d2d9296b',
lastActive: '2018-05-14T17:03:31.277+0000',
metadata: {
'eperson.firstname': [
{
language: null,
value: 'User'
validResponse1 = {
payload: {},
statusCode: 404,
statusText: '404'
};
validResponse2 = {
payload: {
authenticated: true,
id: null,
okay: true,
type: 'status',
_embedded: {
eperson: {
canLogIn: true,
email: 'myself@testshib.org',
groups: [],
handle: null,
id: '4dc70ab5-cd73-492f-b007-3179d2d9296b',
lastActive: '2018-05-14T17:03:31.277+0000',
metadata: {
'eperson.firstname': [
{
language: null,
value: 'User'
}
],
'eperson.lastname': [
{
language: null,
value: 'Test'
}
],
'eperson.language': [
{
language: null,
value: 'en'
}
]
},
name: 'User Test',
netid: 'myself@testshib.org',
requireCertificate: false,
selfRegistered: false,
type: 'eperson',
uuid: '4dc70ab5-cd73-492f-b007-3179d2d9296b',
_links: {
self: {
href: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/eperson/epersons/4dc70ab5-cd73-492f-b007-3179d2d9296b'
}
],
'eperson.lastname': [
{
language: null,
value: 'Test'
}
],
'eperson.language': [
{
language: null,
value: 'en'
}
]
},
name: 'User Test',
netid: 'myself@testshib.org',
requireCertificate: false,
selfRegistered: false,
type: 'eperson',
uuid: '4dc70ab5-cd73-492f-b007-3179d2d9296b',
_links: {
self: {
href: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/eperson/epersons/4dc70ab5-cd73-492f-b007-3179d2d9296b'
}
}
},
_links: {
eperson: {
href: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/eperson/epersons/4dc70ab5-cd73-492f-b007-3179d2d9296b'
},
self: {
href: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/authn/status'
}
}
},
_links: {
eperson: {
href: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/eperson/epersons/4dc70ab5-cd73-492f-b007-3179d2d9296b'
},
self: {
href: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/authn/status'
}
}
},
statusCode: 200,
statusText: '200'
statusCode: 200,
statusText: '200'
};
};
});
it('should return a AuthStatusResponse if data contains a valid AuthStatus object as payload', () => {
const response = service.parse(validRequest, validResponse);

View File

@@ -1,6 +1,5 @@
import { Inject, Injectable } from '@angular/core';
import { AuthObjectFactory } from './auth-object-factory';
import { BaseResponseParsingService } from '../data/base-response-parsing.service';
import { AuthStatusResponse, RestResponse } from '../cache/response.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
@@ -10,7 +9,6 @@ import { isNotEmpty } from '../../shared/empty.util';
import { ObjectCacheService } from '../cache/object-cache.service';
import { ResponseParsingService } from '../data/parsing.service';
import { RestRequest } from '../data/request.models';
import { AuthType } from './auth-type';
import { AuthStatus } from './models/auth-status.model';
import { NormalizedAuthStatus } from './models/normalized-auth-status.model';
import { NormalizedObject } from '../cache/models/normalized-object.model';
@@ -18,7 +16,6 @@ import { NormalizedObject } from '../cache/models/normalized-object.model';
@Injectable()
export class AuthResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
protected objectFactory = AuthObjectFactory;
protected toCache = true;
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
@@ -28,7 +25,7 @@ export class AuthResponseParsingService extends BaseResponseParsingService imple
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links) && (data.statusCode === 200)) {
const response = this.process<NormalizedObject<AuthStatus>, AuthType>(data.payload, request.uuid);
const response = this.process<NormalizedObject<AuthStatus>>(data.payload, request.uuid);
return new AuthStatusResponse(response, data.statusCode, data.statusText);
} else {
return new AuthStatusResponse(data.payload as NormalizedAuthStatus, data.statusCode, data.statusText);

View File

@@ -1,5 +0,0 @@
export enum AuthType {
EPerson = 'eperson',
Status = 'status',
Group = 'group'
}

View File

@@ -4,20 +4,51 @@ import { EPerson } from '../../eperson/models/eperson.model';
import { RemoteData } from '../../data/remote-data';
import { Observable } from 'rxjs';
import { CacheableObject } from '../../cache/object-cache.reducer';
import { ResourceType } from '../../shared/resource-type';
/**
* Object that represents the authenticated status of a user
*/
export class AuthStatus implements CacheableObject {
static type = new ResourceType('status');
/**
* The unique identifier of this auth status
*/
id: string;
/**
* The unique uuid of this auth status
*/
uuid: string;
/**
* True if REST API is up and running, should never return false
*/
okay: boolean;
/**
* If the auth status represents an authenticated state
*/
authenticated: boolean;
/**
* Authentication error if there was one for this status
*/
error?: AuthError;
/**
* The eperson of this auth status
*/
eperson: Observable<RemoteData<EPerson>>;
/**
* True if the token is valid, false if there was no token or the token wasn't valid
*/
token?: AuthTokenInfo;
/**
* The self link of this auth status' REST object
*/
self: string;
}

View File

@@ -1,16 +1,22 @@
import { AuthStatus } from './auth-status.model';
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
import { mapsTo, relationship } from '../../cache/builders/build-decorators';
import { ResourceType } from '../../shared/resource-type';
import { NormalizedObject } from '../../cache/models/normalized-object.model';
import { IDToUUIDSerializer } from '../../cache/id-to-uuid-serializer';
import { EPerson } from '../../eperson/models/eperson.model';
@mapsTo(AuthStatus)
@inheritSerialization(NormalizedObject)
export class NormalizedAuthStatus extends NormalizedObject<AuthStatus> {
/**
* The unique identifier of this auth status
*/
@autoserialize
id: string;
/**
* The unique generated uuid of this auth status
*/
@autoserializeAs(new IDToUUIDSerializer('auth-status'), 'id')
uuid: string;
@@ -26,7 +32,10 @@ export class NormalizedAuthStatus extends NormalizedObject<AuthStatus> {
@autoserialize
authenticated: boolean;
@relationship(ResourceType.EPerson, false)
/**
* The self link to the eperson of this auth status
*/
@relationship(EPerson, false)
@autoserialize
eperson: string;
}

View File

@@ -1,8 +1,9 @@
import { Injectable } from '@angular/core';
import { Observable, of as observableOf } from 'rxjs';
import { distinctUntilChanged, map, startWith, take } from 'rxjs/operators';
import { distinctUntilChanged, map, startWith } from 'rxjs/operators';
import {
ensureArrayHasValue, hasValue,
ensureArrayHasValue,
hasValue,
hasValueOperator,
isEmpty,
isNotEmpty,
@@ -23,7 +24,9 @@ import { BrowseEntry } from '../shared/browse-entry.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import {
configureRequest,
filterSuccessfulResponses, getBrowseDefinitionLinks, getFirstOccurrence,
filterSuccessfulResponses,
getBrowseDefinitionLinks,
getFirstOccurrence,
getRemoteDataPayload,
getRequestFromRequestHref
} from '../shared/operators';
@@ -32,7 +35,6 @@ import { Item } from '../shared/item.model';
import { DSpaceObject } from '../shared/dspace-object.model';
import { BrowseEntrySearchOptions } from './browse-entry-search-options.model';
import { GenericSuccessResponse } from '../cache/response.models';
import { RequestEntry } from '../data/request.reducer';
/**
* The service handling all browse requests

View File

@@ -1,23 +1,59 @@
import 'reflect-metadata';
import { GenericConstructor } from '../../shared/generic-constructor';
import { CacheableObject } from '../object-cache.reducer';
import { CacheableObject, TypedObject } from '../object-cache.reducer';
import { ResourceType } from '../../shared/resource-type';
const mapsToMetadataKey = Symbol('mapsTo');
const relationshipKey = Symbol('relationship');
const relationshipMap = new Map();
const typeMap = new Map();
export function mapsTo(value: GenericConstructor<CacheableObject>) {
return Reflect.metadata(mapsToMetadataKey, value);
/**
* Decorator function to map a normalized class to it's not-normalized counter part class
* It will also maps a type to the matching class
* @param value The not-normalized class to map to
*/
export function mapsTo(value: GenericConstructor<TypedObject>) {
return function decorator(objectConstructor: GenericConstructor<TypedObject>) {
Reflect.defineMetadata(mapsToMetadataKey, value, objectConstructor);
mapsToType((value as any).type, objectConstructor);
}
}
/**
* Maps a type to the matching class
* @param value The resourse type
* @param objectConstructor The class to map to
*/
function mapsToType(value: ResourceType, objectConstructor: GenericConstructor<TypedObject>) {
if (!objectConstructor || !value) {
return;
}
typeMap.set(value.value, objectConstructor);
}
/**
* Returns the mapped class for the given normalized class
* @param target The normalized class
*/
export function getMapsTo(target: any) {
return Reflect.getOwnMetadata(mapsToMetadataKey, target);
}
export function relationship(value: ResourceType, isList: boolean = false): any {
/**
* Returns the mapped class for the given type
* @param type The resource type
*/
export function getMapsToType(type: string | ResourceType) {
if (typeof(type) === 'object') {
type = (type as ResourceType).value;
}
return typeMap.get(type);
}
export function relationship<T extends CacheableObject>(value: GenericConstructor<T>, isList: boolean = false): any {
return function r(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
if (!target || !propertyKey) {
return;
@@ -28,8 +64,10 @@ export function relationship(value: ResourceType, isList: boolean = false): any
metaDataList.push(propertyKey);
}
relationshipMap.set(target.constructor, metaDataList);
return Reflect.metadata(relationshipKey, { resourceType: value, isList }).apply(this, arguments);
return Reflect.metadata(relationshipKey, {
resourceType: (value as any).type.value,
isList
}).apply(this, arguments);
};
}

View File

@@ -1,9 +1,8 @@
import { Injectable } from '@angular/core';
import { NormalizedObject } from '../models/normalized-object.model';
import { CacheableObject } from '../object-cache.reducer';
import { getRelationships } from './build-decorators';
import { NormalizedObjectFactory } from '../models/normalized-object-factory';
import { getMapsToType, getRelationships } from './build-decorators';
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
import { TypedObject } from '../object-cache.reducer';
/**
* Return true if halObj has a value for `_links.self`
@@ -35,8 +34,8 @@ export class NormalizedObjectBuildService {
*
* @param {TDomain} domainModel a domain model
*/
normalize<T extends CacheableObject>(domainModel: T): NormalizedObject<T> {
const normalizedConstructor = NormalizedObjectFactory.getConstructor(domainModel.type);
normalize<T extends TypedObject>(domainModel: T): NormalizedObject<T> {
const normalizedConstructor = getMapsToType((domainModel as any).type);
const relationships = getRelationships(normalizedConstructor) || [];
const normalizedModel = Object.assign({}, domainModel) as any;

View File

@@ -4,6 +4,7 @@ import { PaginatedList } from '../../data/paginated-list';
import { PageInfo } from '../../shared/page-info.model';
import { RemoteData } from '../../data/remote-data';
import { of as observableOf } from 'rxjs';
import { createSuccessfulRemoteDataObject } from '../../../shared/testing/utils';
const pageInfo = new PageInfo();
const array = [
@@ -29,8 +30,8 @@ const array = [
})
];
const paginatedList = new PaginatedList(pageInfo, array);
const arrayRD = new RemoteData(false, false, true, undefined, array);
const paginatedListRD = new RemoteData(false, false, true, undefined, paginatedList);
const arrayRD = createSuccessfulRemoteDataObject(array);
const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
describe('RemoteDataBuildService', () => {
let service: RemoteDataBuildService;

View File

@@ -21,7 +21,8 @@ import {
getRequestFromRequestUUID,
getResourceLinksFromResponse
} from '../../shared/operators';
import { CacheableObject } from '../object-cache.reducer';
import { CacheableObject, TypedObject } from '../object-cache.reducer';
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
@Injectable()
export class RemoteDataBuildService {
@@ -200,7 +201,7 @@ export class RemoteDataBuildService {
aggregate<T>(input: Array<Observable<RemoteData<T>>>): Observable<RemoteData<T[]>> {
if (isEmpty(input)) {
return observableOf(new RemoteData(false, false, true, null, []));
return createSuccessfulRemoteDataObject$([]);
}
return observableCombineLatest(...input).pipe(

View File

@@ -1,6 +1,5 @@
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
import { ItemType } from '../../../shared/item-relationships/item-type.model';
import { ResourceType } from '../../../shared/resource-type';
import { mapsTo } from '../../builders/build-decorators';
import { NormalizedObject } from '../normalized-object.model';
import { IDToUUIDSerializer } from '../../id-to-uuid-serializer';
@@ -11,7 +10,6 @@ import { IDToUUIDSerializer } from '../../id-to-uuid-serializer';
@mapsTo(ItemType)
@inheritSerialization(NormalizedObject)
export class NormalizedItemType extends NormalizedObject<ItemType> {
/**
* The label that describes the ResourceType of the Item
*/
@@ -27,6 +25,6 @@ export class NormalizedItemType extends NormalizedObject<ItemType> {
/**
* The universally unique identifier of this ItemType
*/
@autoserializeAs(new IDToUUIDSerializer(ResourceType.ItemType), 'id')
@autoserializeAs(new IDToUUIDSerializer(ItemType.type.value), 'id')
uuid: string;
}

View File

@@ -5,6 +5,7 @@ import { mapsTo, relationship } from '../../builders/build-decorators';
import { NormalizedDSpaceObject } from '../normalized-dspace-object.model';
import { NormalizedObject } from '../normalized-object.model';
import { IDToUUIDSerializer } from '../../id-to-uuid-serializer';
import { ItemType } from '../../../shared/item-relationships/item-type.model';
/**
* Normalized model class for a DSpace RelationshipType
@@ -12,7 +13,6 @@ import { IDToUUIDSerializer } from '../../id-to-uuid-serializer';
@mapsTo(RelationshipType)
@inheritSerialization(NormalizedDSpaceObject)
export class NormalizedRelationshipType extends NormalizedObject<RelationshipType> {
/**
* The identifier of this RelationshipType
*/
@@ -59,19 +59,19 @@ export class NormalizedRelationshipType extends NormalizedObject<RelationshipTyp
* The type of Item found to the left of this RelationshipType
*/
@autoserialize
@relationship(ResourceType.ItemType, false)
@relationship(ItemType, false)
leftType: string;
/**
* The type of Item found to the right of this RelationshipType
*/
@autoserialize
@relationship(ResourceType.ItemType, false)
@relationship(ItemType, false)
rightType: string;
/**
* The universally unique identifier of this RelationshipType
*/
@autoserializeAs(new IDToUUIDSerializer(ResourceType.RelationshipType), 'id')
@autoserializeAs(new IDToUUIDSerializer(RelationshipType.type.value), 'id')
uuid: string;
}

View File

@@ -1,9 +1,10 @@
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
import { Relationship } from '../../../shared/item-relationships/relationship.model';
import { ResourceType } from '../../../shared/resource-type';
import { mapsTo, relationship } from '../../builders/build-decorators';
import { NormalizedObject } from '../normalized-object.model';
import { IDToUUIDSerializer } from '../../id-to-uuid-serializer';
import { RelationshipType } from '../../../shared/item-relationships/relationship-type.model';
import { Item } from '../../../shared/item.model';
/**
* Normalized model class for a DSpace Relationship
@@ -22,14 +23,14 @@ export class NormalizedRelationship extends NormalizedObject<Relationship> {
* The item to the left of this relationship
*/
@autoserialize
@relationship(ResourceType.Item, false)
@relationship(Item, false)
leftItem: string;
/**
* The item to the right of this relationship
*/
@autoserialize
@relationship(ResourceType.Item, false)
@relationship(Item, false)
rightItem: string;
/**
@@ -48,12 +49,12 @@ export class NormalizedRelationship extends NormalizedObject<Relationship> {
* The type of Relationship
*/
@autoserialize
@relationship(ResourceType.RelationshipType, false)
@relationship(RelationshipType, false)
relationshipType: string;
/**
* The universally unique identifier of this Relationship
*/
@autoserializeAs(new IDToUUIDSerializer(ResourceType.Relationship), 'id')
@autoserializeAs(new IDToUUIDSerializer(Relationship.type.value), 'id')
uuid: string;
}

View File

@@ -12,7 +12,6 @@ import { SupportLevel } from './support-level.model';
@mapsTo(BitstreamFormat)
@inheritSerialization(NormalizedObject)
export class NormalizedBitstreamFormat extends NormalizedObject<BitstreamFormat> {
/**
* Short description of this Bitstream Format
*/

View File

@@ -1,9 +1,10 @@
import { inheritSerialization, autoserialize } from 'cerialize';
import { autoserialize, inheritSerialization } from 'cerialize';
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
import { Bitstream } from '../../shared/bitstream.model';
import { mapsTo, relationship } from '../builders/build-decorators';
import { ResourceType } from '../../shared/resource-type';
import { Item } from '../../shared/item.model';
import { BitstreamFormat } from '../../shared/bitstream-format.model';
/**
* Normalized model class for a DSpace Bitstream
@@ -11,7 +12,6 @@ import { ResourceType } from '../../shared/resource-type';
@mapsTo(Bitstream)
@inheritSerialization(NormalizedDSpaceObject)
export class NormalizedBitstream extends NormalizedDSpaceObject<Bitstream> {
/**
* The size of this bitstream in bytes
*/
@@ -28,7 +28,7 @@ export class NormalizedBitstream extends NormalizedDSpaceObject<Bitstream> {
* The format of this Bitstream
*/
@autoserialize
@relationship(ResourceType.BitstreamFormat, false)
@relationship(BitstreamFormat, false)
format: string;
/**
@@ -41,14 +41,14 @@ export class NormalizedBitstream extends NormalizedDSpaceObject<Bitstream> {
* An array of Bundles that are direct parents of this Bitstream
*/
@autoserialize
@relationship(ResourceType.Item, true)
@relationship(Item, true)
parents: string[];
/**
* The Bundle that owns this Bitstream
*/
@autoserialize
@relationship(ResourceType.Item, false)
@relationship(Item, false)
owner: string;
/**

View File

@@ -3,7 +3,7 @@ import { autoserialize, inheritSerialization } from 'cerialize';
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
import { Bundle } from '../../shared/bundle.model';
import { mapsTo, relationship } from '../builders/build-decorators';
import { ResourceType } from '../../shared/resource-type';
import { Bitstream } from '../../shared/bitstream.model';
/**
* Normalized model class for a DSpace Bundle
@@ -15,7 +15,7 @@ export class NormalizedBundle extends NormalizedDSpaceObject<Bundle> {
* The primary bitstream of this Bundle
*/
@autoserialize
@relationship(ResourceType.Bitstream, false)
@relationship(Bitstream, false)
primaryBitstream: string;
/**
@@ -32,7 +32,7 @@ export class NormalizedBundle extends NormalizedDSpaceObject<Bundle> {
* List of Bitstreams that are part of this Bundle
*/
@autoserialize
@relationship(ResourceType.Bitstream, true)
@relationship(Bitstream, true)
bitstreams: string[];
}

View File

@@ -3,7 +3,15 @@ import { autoserialize, deserialize, inheritSerialization } from 'cerialize';
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
import { Collection } from '../../shared/collection.model';
import { mapsTo, relationship } from '../builders/build-decorators';
import { ResourceType } from '../../shared/resource-type';
import { NormalizedResourcePolicy } from './normalized-resource-policy.model';
import { NormalizedBitstream } from './normalized-bitstream.model';
import { NormalizedCommunity } from './normalized-community.model';
import { NormalizedItem } from './normalized-item.model';
import { License } from '../../shared/license.model';
import { ResourcePolicy } from '../../shared/resource-policy.model';
import { Bitstream } from '../../shared/bitstream.model';
import { Community } from '../../shared/community.model';
import { Item } from '../../shared/item.model';
/**
* Normalized model class for a DSpace Collection
@@ -22,42 +30,42 @@ export class NormalizedCollection extends NormalizedDSpaceObject<Collection> {
* The Bitstream that represents the license of this Collection
*/
@autoserialize
@relationship(ResourceType.License, false)
@relationship(License, false)
license: string;
/**
* The Bitstream that represents the default Access Conditions of this Collection
*/
@autoserialize
@relationship(ResourceType.ResourcePolicy, false)
@relationship(ResourcePolicy, false)
defaultAccessConditions: string;
/**
* The Bitstream that represents the logo of this Collection
*/
@deserialize
@relationship(ResourceType.Bitstream, false)
@relationship(Bitstream, false)
logo: string;
/**
* An array of Communities that are direct parents of this Collection
*/
@deserialize
@relationship(ResourceType.Community, true)
@relationship(Community, true)
parents: string[];
/**
* The Community that owns this Collection
*/
@deserialize
@relationship(ResourceType.Community, false)
@relationship(Community, false)
owner: string;
/**
* List of Items that are part of (not necessarily owned by) this Collection
*/
@deserialize
@relationship(ResourceType.Item, true)
@relationship(Item, true)
items: string[];
}

View File

@@ -1,9 +1,13 @@
import { autoserialize, deserialize, inheritSerialization, serialize } from 'cerialize';
import { autoserialize, deserialize, inheritSerialization } from 'cerialize';
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
import { Community } from '../../shared/community.model';
import { mapsTo, relationship } from '../builders/build-decorators';
import { ResourceType } from '../../shared/resource-type';
import { NormalizedBitstream } from './normalized-bitstream.model';
import { NormalizedCollection } from './normalized-collection.model';
import { Bitstream } from '../../shared/bitstream.model';
import { Collection } from '../../shared/collection.model';
/**
* Normalized model class for a DSpace Community
@@ -11,7 +15,6 @@ import { ResourceType } from '../../shared/resource-type';
@mapsTo(Community)
@inheritSerialization(NormalizedDSpaceObject)
export class NormalizedCommunity extends NormalizedDSpaceObject<Community> {
/**
* A string representing the unique handle of this Community
*/
@@ -22,32 +25,32 @@ export class NormalizedCommunity extends NormalizedDSpaceObject<Community> {
* The Bitstream that represents the logo of this Community
*/
@deserialize
@relationship(ResourceType.Bitstream, false)
@relationship(Bitstream, false)
logo: string;
/**
* An array of Communities that are direct parents of this Community
*/
@deserialize
@relationship(ResourceType.Community, true)
@relationship(Community, true)
parents: string[];
/**
* The Community that owns this Community
*/
@deserialize
@relationship(ResourceType.Community, false)
@relationship(Community, false)
owner: string;
/**
* List of Collections that are owned by this Community
*/
@deserialize
@relationship(ResourceType.Collection, true)
@relationship(Collection, true)
collections: string[];
@deserialize
@relationship(ResourceType.Community, true)
@relationship(Community, true)
subcommunities: string[];
}

View File

@@ -1,15 +1,15 @@
import { autoserializeAs, deserializeAs } from 'cerialize';
import { autoserializeAs, deserializeAs, autoserialize } from 'cerialize';
import { DSpaceObject } from '../../shared/dspace-object.model';
import { MetadataMap, MetadataMapSerializer } from '../../shared/metadata.models';
import { ResourceType } from '../../shared/resource-type';
import { mapsTo } from '../builders/build-decorators';
import { NormalizedObject } from './normalized-object.model';
import { TypedObject } from '../object-cache.reducer';
/**
* An model class for a DSpaceObject.
*/
@mapsTo(DSpaceObject)
export class NormalizedDSpaceObject<T extends DSpaceObject> extends NormalizedObject<T> {
export class NormalizedDSpaceObject<T extends DSpaceObject> extends NormalizedObject<T> implements TypedObject {
/**
* The link to the rest endpoint where this object can be found
@@ -38,8 +38,8 @@ export class NormalizedDSpaceObject<T extends DSpaceObject> extends NormalizedOb
/**
* A string representing the kind of DSpaceObject, e.g. community, item, …
*/
@autoserializeAs(String)
type: ResourceType;
@autoserialize
type: string;
/**
* All metadata of this DSpaceObject

View File

@@ -4,6 +4,12 @@ import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
import { Item } from '../../shared/item.model';
import { mapsTo, relationship } from '../builders/build-decorators';
import { ResourceType } from '../../shared/resource-type';
import { NormalizedCollection } from './normalized-collection.model';
import { NormalizedBitstream } from './normalized-bitstream.model';
import { NormalizedRelationship } from './items/normalized-relationship.model';
import { Collection } from '../../shared/collection.model';
import { Bitstream } from '../../shared/bitstream.model';
import { Relationship } from '../../shared/item-relationships/relationship.model';
/**
* Normalized model class for a DSpace Item
@@ -46,25 +52,25 @@ export class NormalizedItem extends NormalizedDSpaceObject<Item> {
* An array of Collections that are direct parents of this Item
*/
@deserialize
@relationship(ResourceType.Collection, true)
@relationship(Collection, true)
parents: string[];
/**
* The Collection that owns this Item
*/
@deserialize
@relationship(ResourceType.Collection, false)
@relationship(Collection, false)
owningCollection: string;
/**
* List of Bitstreams that are owned by this Item
*/
@deserialize
@relationship(ResourceType.Bitstream, true)
@relationship(Bitstream, true)
bitstreams: string[];
@autoserialize
@relationship(ResourceType.Relationship, true)
@relationship(Relationship, true)
relationships: string[];
}

View File

@@ -1,104 +0,0 @@
import { NormalizedItemType } from './items/normalized-item-type.model';
import { NormalizedRelationshipType } from './items/normalized-relationship-type.model';
import { NormalizedRelationship } from './items/normalized-relationship.model';
import { NormalizedBitstream } from './normalized-bitstream.model';
import { NormalizedBundle } from './normalized-bundle.model';
import { NormalizedItem } from './normalized-item.model';
import { NormalizedCollection } from './normalized-collection.model';
import { GenericConstructor } from '../../shared/generic-constructor';
import { NormalizedCommunity } from './normalized-community.model';
import { ResourceType } from '../../shared/resource-type';
import { NormalizedObject } from './normalized-object.model';
import { NormalizedLicense } from './normalized-license.model';
import { NormalizedResourcePolicy } from './normalized-resource-policy.model';
import { NormalizedWorkspaceItem } from '../../submission/models/normalized-workspaceitem.model';
import { NormalizedEPerson } from '../../eperson/models/normalized-eperson.model';
import { NormalizedGroup } from '../../eperson/models/normalized-group.model';
import { NormalizedWorkflowItem } from '../../submission/models/normalized-workflowitem.model';
import { NormalizedClaimedTask } from '../../tasks/models/normalized-claimed-task-object.model';
import { NormalizedPoolTask } from '../../tasks/models/normalized-pool-task-object.model';
import { NormalizedBitstreamFormat } from './normalized-bitstream-format.model';
import { NormalizedMetadataSchema } from '../../metadata/normalized-metadata-schema.model';
import { CacheableObject } from '../object-cache.reducer';
import { NormalizedSubmissionDefinitionsModel } from '../../config/models/normalized-config-submission-definitions.model';
import { NormalizedSubmissionFormsModel } from '../../config/models/normalized-config-submission-forms.model';
import { NormalizedSubmissionSectionModel } from '../../config/models/normalized-config-submission-section.model';
export class NormalizedObjectFactory {
public static getConstructor(type: ResourceType): GenericConstructor<NormalizedObject<CacheableObject>> {
switch (type) {
case ResourceType.Bitstream: {
return NormalizedBitstream
}
case ResourceType.Bundle: {
return NormalizedBundle
}
case ResourceType.Item: {
return NormalizedItem
}
case ResourceType.Collection: {
return NormalizedCollection
}
case ResourceType.Community: {
return NormalizedCommunity
}
case ResourceType.BitstreamFormat: {
return NormalizedBitstreamFormat
}
case ResourceType.License: {
return NormalizedLicense
}
case ResourceType.ResourcePolicy: {
return NormalizedResourcePolicy
}
case ResourceType.Relationship: {
return NormalizedRelationship
}
case ResourceType.RelationshipType: {
return NormalizedRelationshipType
}
case ResourceType.ItemType: {
return NormalizedItemType
}
case ResourceType.EPerson: {
return NormalizedEPerson
}
case ResourceType.Group: {
return NormalizedGroup
}
case ResourceType.MetadataSchema: {
return NormalizedMetadataSchema
}
case ResourceType.MetadataField: {
return NormalizedGroup
}
case ResourceType.Workspaceitem: {
return NormalizedWorkspaceItem
}
case ResourceType.Workflowitem: {
return NormalizedWorkflowItem
}
case ResourceType.ClaimedTask: {
return NormalizedClaimedTask
}
case ResourceType.PoolTask: {
return NormalizedPoolTask
}
case ResourceType.SubmissionDefinition:
case ResourceType.SubmissionDefinitions: {
return NormalizedSubmissionDefinitionsModel
}
case ResourceType.SubmissionForm:
case ResourceType.SubmissionForms: {
return NormalizedSubmissionFormsModel
}
case ResourceType.SubmissionSection:
case ResourceType.SubmissionSections: {
return NormalizedSubmissionSectionModel
}
default: {
return undefined;
}
}
}
}

View File

@@ -1,25 +1,24 @@
import { CacheableObject } from '../object-cache.reducer';
import { CacheableObject, TypedObject } from '../object-cache.reducer';
import { autoserialize } from 'cerialize';
import { ResourceType } from '../../shared/resource-type';
/**
* An abstract model class for a NormalizedObject.
*/
export abstract class NormalizedObject<T extends CacheableObject> implements CacheableObject {
export abstract class NormalizedObject<T extends TypedObject> implements CacheableObject {
/**
* The link to the rest endpoint where this object can be found
*/
@autoserialize
self: string;
/**
* A string representing the kind of DSpaceObject, e.g. community, item, …
*/
@autoserialize
type: ResourceType;
@autoserialize
_links: {
[name: string]: string
}
};
/**
* A string representing the kind of object
*/
@autoserialize
type: string;
}

View File

@@ -12,7 +12,6 @@ import { ActionType } from './action-type.model';
@mapsTo(ResourcePolicy)
@inheritSerialization(NormalizedObject)
export class NormalizedResourcePolicy extends NormalizedObject<ResourcePolicy> {
/**
* The action that is allowed by this Resource Policy
*/

View File

@@ -8,7 +8,7 @@ export enum SupportLevel {
Unknown = 0,
/**
* Unknown for Bitstream Formats that are known to the system, but not fully supported
* Known for Bitstream Formats that are known to the system, but not fully supported
*/
Known = 1,

View File

@@ -9,6 +9,7 @@ import {
ResetObjectCacheTimestampsAction
} from './object-cache.actions';
import { Operation } from 'fast-json-patch';
import { Item } from '../shared/item.model';
class NullAction extends RemoveFromObjectCacheAction {
type = null;
@@ -28,6 +29,7 @@ describe('objectCacheReducer', () => {
const testState = {
[selfLink1]: {
data: {
type: Item.type,
self: selfLink1,
foo: 'bar'
},
@@ -39,6 +41,7 @@ describe('objectCacheReducer', () => {
},
[selfLink2]: {
data: {
type: Item.type,
self: requestUUID2,
foo: 'baz'
},
@@ -67,7 +70,7 @@ describe('objectCacheReducer', () => {
it('should add the payload to the cache in response to an ADD action', () => {
const state = Object.create(null);
const objectToCache = { self: selfLink1 };
const objectToCache = { self: selfLink1, type: Item.type };
const timeAdded = new Date().getTime();
const msToLive = 900000;
const requestUUID = requestUUID1;
@@ -80,7 +83,12 @@ describe('objectCacheReducer', () => {
});
it('should overwrite an object in the cache in response to an ADD action if it already exists', () => {
const objectToCache = { self: selfLink1, foo: 'baz', somethingElse: true };
const objectToCache = {
self: selfLink1,
foo: 'baz',
somethingElse: true,
type: Item.type
};
const timeAdded = new Date().getTime();
const msToLive = 900000;
const requestUUID = requestUUID1;
@@ -95,7 +103,7 @@ describe('objectCacheReducer', () => {
it('should perform the ADD action without affecting the previous state', () => {
const state = Object.create(null);
const objectToCache = { self: selfLink1 };
const objectToCache = { self: selfLink1, type: Item.type };
const timeAdded = new Date().getTime();
const msToLive = 900000;
const requestUUID = requestUUID1;

View File

@@ -32,15 +32,19 @@ export interface Patch {
operations: Operation[];
}
export abstract class TypedObject {
static type: ResourceType;
}
/* tslint:disable:max-classes-per-file */
/**
* An interface to represent objects that can be cached
*
* A cacheable object should have a self link
*/
export interface CacheableObject {
export class CacheableObject extends TypedObject {
uuid?: string;
self: string;
type?: ResourceType;
// isNew: boolean;
// dirtyType: DirtyType;
// hasDirtyAttributes: boolean;
@@ -59,6 +63,7 @@ export class ObjectCacheEntry implements CacheEntry {
patches: Patch[] = [];
isDirty: boolean;
}
/* tslint:enable:max-classes-per-file */
/**
* The ObjectCache State

View File

@@ -10,13 +10,13 @@ import {
RemoveFromObjectCacheAction
} from './object-cache.actions';
import { CoreState } from '../core.reducers';
import { ResourceType } from '../shared/resource-type';
import { NormalizedItem } from './models/normalized-item.model';
import { first } from 'rxjs/operators';
import { Operation } from 'fast-json-patch';
import { RestRequestMethod } from '../data/rest-request-method';
import { AddToSSBAction } from './server-sync-buffer.actions';
import { Patch } from './object-cache.reducer';
import { Item } from '../shared/item.model';
describe('ObjectCacheService', () => {
let service: ObjectCacheService;
@@ -28,7 +28,7 @@ describe('ObjectCacheService', () => {
const msToLive = 900000;
let objectToCache = {
self: selfLink,
type: ResourceType.Item
type: Item.type
};
let cacheEntry;
let invalidCacheEntry;
@@ -37,7 +37,7 @@ describe('ObjectCacheService', () => {
function init() {
objectToCache = {
self: selfLink,
type: ResourceType.Item
type: Item.type
};
cacheEntry = {
data: objectToCache,

View File

@@ -10,7 +10,6 @@ import { coreSelector } from '../core.selectors';
import { RestRequestMethod } from '../data/rest-request-method';
import { selfLinkFromUuidSelector } from '../index/index.selectors';
import { GenericConstructor } from '../shared/generic-constructor';
import { NormalizedObjectFactory } from './models/normalized-object-factory';
import { NormalizedObject } from './models/normalized-object.model';
import {
AddPatchObjectCacheAction,
@@ -21,6 +20,7 @@ import {
import { CacheableObject, ObjectCacheEntry, ObjectCacheState } from './object-cache.reducer';
import { AddToSSBAction } from './server-sync-buffer.actions';
import { getMapsToType } from './builders/build-decorators';
/**
* The base selector function to select the object cache in the store
@@ -109,7 +109,7 @@ export class ObjectCacheService {
}
),
map((entry: ObjectCacheEntry) => {
const type: GenericConstructor<NormalizedObject<T>> = NormalizedObjectFactory.getConstructor(entry.data.type);
const type: GenericConstructor<NormalizedObject<T>> = getMapsToType((entry.data as any).type);
return Object.assign(new type(), entry.data) as NormalizedObject<T>
})
);

View File

@@ -8,12 +8,12 @@ import { IntegrationModel } from '../integration/models/integration.model';
import { RegistryMetadataschemasResponse } from '../registry/registry-metadataschemas-response.model';
import { RegistryMetadatafieldsResponse } from '../registry/registry-metadatafields-response.model';
import { RegistryBitstreamformatsResponse } from '../registry/registry-bitstreamformats-response.model';
import { MetadataSchema } from '../metadata/metadataschema.model';
import { MetadataField } from '../metadata/metadatafield.model';
import { PaginatedList } from '../data/paginated-list';
import { SubmissionObject } from '../submission/models/submission-object.model';
import { DSpaceObject } from '../shared/dspace-object.model';
import { NormalizedAuthStatus } from '../auth/models/normalized-auth-status.model';
import { MetadataSchema } from '../metadata/metadata-schema.model';
import { MetadataField } from '../metadata/metadata-field.model';
/* tslint:disable:max-classes-per-file */
export class RestResponse {

View File

@@ -8,8 +8,8 @@ import { Store } from '@ngrx/store';
import { CoreState } from '../core.reducers';
import { PaginatedList } from '../data/paginated-list';
import { PageInfo } from '../shared/page-info.model';
import { NormalizedSubmissionDefinitionsModel } from './models/normalized-config-submission-definitions.model';
import { NormalizedSubmissionSectionModel } from './models/normalized-config-submission-section.model';
import { NormalizedSubmissionDefinitionModel } from './models/normalized-config-submission-definition.model';
describe('ConfigResponseParsingService', () => {
let service: ConfigResponseParsingService;
@@ -173,7 +173,7 @@ describe('ConfigResponseParsingService', () => {
self: 'https://rest.api/config/submissiondefinitions/traditional/sections'
});
const definitions =
Object.assign(new NormalizedSubmissionDefinitionsModel(), {
Object.assign(new NormalizedSubmissionDefinitionModel(), {
isDefault: true,
name: 'traditional',
type: 'submissiondefinition',

View File

@@ -5,10 +5,8 @@ import { RestRequest } from '../data/request.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { ConfigSuccessResponse, ErrorResponse, RestResponse } from '../cache/response.models';
import { isNotEmpty } from '../../shared/empty.util';
import { ConfigObjectFactory } from './models/config-object-factory';
import { ConfigObject } from './models/config.model';
import { ConfigType } from './models/config-type';
import { BaseResponseParsingService } from '../data/base-response-parsing.service';
import { GLOBAL_CONFIG } from '../../../config';
import { GlobalConfig } from '../../../config/global-config.interface';
@@ -16,8 +14,6 @@ import { ObjectCacheService } from '../cache/object-cache.service';
@Injectable()
export class ConfigResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
protected objectFactory = ConfigObjectFactory;
protected toCache = false;
constructor(
@@ -28,7 +24,7 @@ export class ConfigResponseParsingService extends BaseResponseParsingService imp
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links) && (data.statusCode === 201 || data.statusCode === 200)) {
const configDefinition = this.process<ConfigObject,ConfigType>(data.payload, request.uuid);
const configDefinition = this.process<ConfigObject>(data.payload, request.uuid);
return new ConfigSuccessResponse(configDefinition, data.statusCode, data.statusText, this.processPageInfo(data.payload));
} else {
return new ErrorResponse(

View File

@@ -1,36 +0,0 @@
import { GenericConstructor } from '../../shared/generic-constructor';
import { ConfigType } from './config-type';
import { ConfigObject } from './config.model';
import { NormalizedSubmissionDefinitionsModel } from './normalized-config-submission-definitions.model';
import { NormalizedSubmissionFormsModel } from './normalized-config-submission-forms.model';
import { NormalizedSubmissionSectionModel } from './normalized-config-submission-section.model';
import { NormalizedSubmissionUploadsModel } from './normalized-config-submission-uploads.model';
/**
* Class to return normalized models for config objects
*/
export class ConfigObjectFactory {
public static getConstructor(type): GenericConstructor<ConfigObject> {
switch (type) {
case ConfigType.SubmissionDefinition:
case ConfigType.SubmissionDefinitions: {
return NormalizedSubmissionDefinitionsModel
}
case ConfigType.SubmissionForm:
case ConfigType.SubmissionForms: {
return NormalizedSubmissionFormsModel
}
case ConfigType.SubmissionSection:
case ConfigType.SubmissionSections: {
return NormalizedSubmissionSectionModel
}
case ConfigType.SubmissionUpload:
case ConfigType.SubmissionUploads: {
return NormalizedSubmissionUploadsModel
}
default: {
return undefined;
}
}
}
}

View File

@@ -0,0 +1,22 @@
import { ConfigObject } from './config.model';
import { SubmissionSectionModel } from './config-submission-section.model';
import { PaginatedList } from '../../data/paginated-list';
import { ResourceType } from '../../shared/resource-type';
/**
* Class for the configuration describing the submission
*/
export class SubmissionDefinitionModel extends ConfigObject {
static type = new ResourceType('submissiondefinition');
/**
* A boolean representing if this submission definition is the default or not
*/
isDefault: boolean;
/**
* A list of SubmissionSectionModel that are present in this submission definition
*/
sections: PaginatedList<SubmissionSectionModel>;
}

Some files were not shown because too many files have changed in this diff Show More