87382: fixing circular dependencies

This commit is contained in:
lotte
2022-02-16 13:52:32 +01:00
parent 9fc7b57157
commit 143b7c3e0d
210 changed files with 1192 additions and 1033 deletions

View File

@@ -1,10 +1,10 @@
import { Component, Inject, Injector, OnInit } from '@angular/core';
import { MenuSectionComponent } from '../../../shared/menu/menu-section/menu-section.component';
import { MenuID } from '../../../shared/menu/initial-menus-state';
import { MenuService } from '../../../shared/menu/menu.service';
import { rendersSectionForMenu } from '../../../shared/menu/menu-section.decorator';
import { LinkMenuItemModel } from '../../../shared/menu/menu-item/models/link.model';
import { MenuSection } from '../../../shared/menu/menu.reducer';
import { MenuSection } from '../../../shared/menu/menu-section.model';
import { MenuID } from '../../../shared/menu/menu-id.model';
/**
* Represents a non-expandable section in the admin sidebar

View File

@@ -12,7 +12,6 @@ import { EditCollectionSelectorComponent } from '../../shared/dso-selector/modal
import { EditCommunitySelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component';
import { EditItemSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component';
import { ExportMetadataSelectorComponent } from '../../shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component';
import { MenuID, MenuItemType } from '../../shared/menu/initial-menus-state';
import { LinkMenuItemModel } from '../../shared/menu/menu-item/models/link.model';
import { OnClickMenuItemModel } from '../../shared/menu/menu-item/models/onclick.model';
import { TextMenuItemModel } from '../../shared/menu/menu-item/models/text.model';
@@ -21,6 +20,8 @@ import { MenuService } from '../../shared/menu/menu.service';
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
import { MenuID } from '../../shared/menu/menu-id.model';
import { MenuItemType } from '../../shared/menu/menu-item-type.model';
/**
* Component representing the admin sidebar

View File

@@ -4,11 +4,11 @@ import { AdminSidebarSectionComponent } from '../admin-sidebar-section/admin-sid
import { slide } from '../../../shared/animations/slide';
import { CSSVariableService } from '../../../shared/sass-helper/sass-helper.service';
import { bgColor } from '../../../shared/animations/bgColor';
import { MenuID } from '../../../shared/menu/initial-menus-state';
import { MenuService } from '../../../shared/menu/menu.service';
import { combineLatest as combineLatestObservable, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { rendersSectionForMenu } from '../../../shared/menu/menu-section.decorator';
import { MenuID } from '../../../shared/menu/menu-id.model';
/**
* Represents a expandable section in the sidebar

View File

@@ -22,7 +22,7 @@ import {
nameVariantReducer
} from './shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
import { formReducer, FormState } from './shared/form/form.reducer';
import { menusReducer, MenusState } from './shared/menu/menu.reducer';
import { menusReducer} from './shared/menu/menu.reducer';
import {
notificationsReducer,
NotificationsState
@@ -49,6 +49,7 @@ import {
import { sidebarReducer, SidebarState } from './shared/sidebar/sidebar.reducer';
import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer';
import { ThemeState, themeReducer } from './shared/theme-support/theme.reducer';
import { MenusState } from './shared/menu/menus-state.model';
export interface AppState {
router: fromRouter.RouterReducerState;

View File

@@ -2,8 +2,8 @@ import { LegacyBitstreamUrlResolver } from './legacy-bitstream-url.resolver';
import { of as observableOf, EMPTY } from 'rxjs';
import { BitstreamDataService } from '../core/data/bitstream-data.service';
import { RemoteData } from '../core/data/remote-data';
import { RequestEntryState } from '../core/data/request.reducer';
import { TestScheduler } from 'rxjs/testing';
import { RequestEntryState } from '../core/data/request-entry-state.model';
describe(`LegacyBitstreamUrlResolver`, () => {
let resolver: LegacyBitstreamUrlResolver;

View File

@@ -16,7 +16,7 @@ import { Collection } from '../../core/shared/collection.model';
import { RemoteData } from '../../core/data/remote-data';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { EventEmitter } from '@angular/core';
import { ChangeDetectionStrategy, EventEmitter } from '@angular/core';
import { HostWindowService } from '../../shared/host-window.service';
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
import { By } from '@angular/platform-browser';
@@ -41,6 +41,8 @@ import {
} from '../../shared/remote-data.utils';
import { createPaginatedList } from '../../shared/testing/utils.test';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { MyDSpacePageComponent, SEARCH_CONFIG_SERVICE } from '../../my-dspace-page/my-dspace-page.component';
import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-service.stub';
describe('CollectionItemMapperComponent', () => {
let comp: CollectionItemMapperComponent;
@@ -159,6 +161,14 @@ describe('CollectionItemMapperComponent', () => {
{ provide: RouteService, useValue: routeServiceStub },
{ provide: AuthorizationDataService, useValue: authorizationDataService }
]
}).overrideComponent(CollectionItemMapperComponent, {
set: {
providers: [
{
provide: SEARCH_CONFIG_SERVICE,
useClass: SearchConfigurationServiceStub
}
] }
}).compileComponents();
}));

View File

@@ -18,9 +18,9 @@ import {
COLLECTION_CREATE_PATH
} from './collection-page-routing-paths';
import { CollectionPageAdministratorGuard } from './collection-page-administrator.guard';
import { MenuItemType } from '../shared/menu/initial-menus-state';
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
import { MenuItemType } from '../shared/menu/menu-item-type.model';
@NgModule({
imports: [

View File

@@ -21,7 +21,6 @@ import { Item } from '../core/shared/item.model';
import {
getAllSucceededRemoteDataPayload,
getFirstSucceededRemoteData,
redirectOn4xx,
toDSpaceObjectListRD
} from '../core/shared/operators';
@@ -33,6 +32,7 @@ import { PaginationService } from '../core/pagination/pagination.service';
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../core/data/feature-authorization/feature-id';
import { getCollectionPageRoute } from './collection-page-routing-paths';
import { redirectOn4xx } from '../core/shared/authorized.operators';
@Component({
selector: 'ds-collection-page',

View File

@@ -9,7 +9,6 @@ import { ContentSource, ContentSourceHarvestType } from '../../../core/shared/co
import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
import { INotification, Notification } from '../../../shared/notifications/models/notification.model';
import { NotificationType } from '../../../shared/notifications/models/notification-type';
import { FieldUpdate } from '../../../core/data/object-updates/object-updates.reducer';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { DynamicFormControlModel, DynamicFormService } from '@ng-dynamic-forms/core';
import { hasValue } from '../../../shared/empty.util';
@@ -20,6 +19,7 @@ import { Collection } from '../../../core/shared/collection.model';
import { CollectionDataService } from '../../../core/data/collection-data.service';
import { RequestService } from '../../../core/data/request.service';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
import { FieldUpdate } from '../../../core/data/object-updates/field-update.model';
const infoNotification: INotification = new Notification('id', NotificationType.Info, 'info');
const warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning');

View File

@@ -23,7 +23,6 @@ import { RemoteData } from '../../../core/data/remote-data';
import { Collection } from '../../../core/shared/collection.model';
import { first, map, switchMap, take } from 'rxjs/operators';
import { ActivatedRoute, Router } from '@angular/router';
import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
import { cloneDeep } from 'lodash';
import { CollectionDataService } from '../../../core/data/collection-data.service';
import { getFirstSucceededRemoteData, getFirstCompletedRemoteData } from '../../../core/shared/operators';
@@ -31,6 +30,8 @@ import { MetadataConfig } from '../../../core/shared/metadata-config.model';
import { INotification } from '../../../shared/notifications/models/notification.model';
import { RequestService } from '../../../core/data/request.service';
import { environment } from '../../../../environments/environment';
import { FieldUpdate } from '../../../core/data/object-updates/field-update.model';
import { FieldUpdates } from '../../../core/data/object-updates/field-updates.model';
/**
* Component for managing the content source of the collection

View File

@@ -1,10 +1,11 @@
import { Subscription } from 'rxjs/internal/Subscription';
import { FindListOptions } from '../core/data/request.models';
import { hasValue } from '../shared/empty.util';
import { CommunityListService, FlatNode } from './community-list-service';
import { CommunityListService} from './community-list-service';
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { BehaviorSubject, Observable, } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { FlatNode } from './flat-node.model';
/**
* DataSource object needed by a CDK Tree to render its nodes.

View File

@@ -7,13 +7,14 @@ import { SortDirection, SortOptions } from '../core/cache/models/sort-options.mo
import { buildPaginatedList } from '../core/data/paginated-list.model';
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
import { StoreMock } from '../shared/testing/store.mock';
import { CommunityListService, FlatNode, toFlatNode } from './community-list-service';
import { CommunityListService, toFlatNode } from './community-list-service';
import { CollectionDataService } from '../core/data/collection-data.service';
import { CommunityDataService } from '../core/data/community-data.service';
import { Community } from '../core/shared/community.model';
import { Collection } from '../core/shared/collection.model';
import { FindListOptions } from '../core/data/request.models';
import { PageInfo } from '../core/shared/page-info.model';
import { FlatNode } from './flat-node.model';
describe('CommunityListService', () => {
let store: StoreMock<AppState>;

View File

@@ -12,39 +12,16 @@ import { Collection } from '../core/shared/collection.model';
import { PageInfo } from '../core/shared/page-info.model';
import { hasValue, isNotEmpty } from '../shared/empty.util';
import { RemoteData } from '../core/data/remote-data';
import { PaginatedList, buildPaginatedList } from '../core/data/paginated-list.model';
import { buildPaginatedList, PaginatedList } from '../core/data/paginated-list.model';
import { CollectionDataService } from '../core/data/collection-data.service';
import { CommunityListSaveAction } from './community-list.actions';
import { CommunityListState } from './community-list.reducer';
import { getCommunityPageRoute } from '../community-page/community-page-routing-paths';
import { getCollectionPageRoute } from '../collection-page/collection-page-routing-paths';
import { getFirstSucceededRemoteData, getFirstCompletedRemoteData } from '../core/shared/operators';
import { getFirstCompletedRemoteData, getFirstSucceededRemoteData } from '../core/shared/operators';
import { followLink } from '../shared/utils/follow-link-config.model';
/**
* Each node in the tree is represented by a flatNode which contains info about the node itself and its position and
* state in the tree. There are nodes representing communities, collections and show more links.
*/
export interface FlatNode {
isExpandable$: Observable<boolean>;
name: string;
id: string;
level: number;
isExpanded?: boolean;
parent?: FlatNode;
payload: Community | Collection | ShowMoreFlatNode;
isShowMoreNode: boolean;
route?: string;
currentCommunityPage?: number;
currentCollectionPage?: number;
}
/**
* The show more links in the community tree are also represented by a flatNode so we know where in
* the tree it should be rendered an who its parent is (needed for the action resulting in clicking this link)
*/
export class ShowMoreFlatNode {
}
import { FlatNode } from './flat-node.model';
import { ShowMoreFlatNode } from './show-more-flat-node.model';
// Helper method to combine an flatten an array of observables of flatNode arrays
export const combineAndFlatten = (obsList: Observable<FlatNode[]>[]): Observable<FlatNode[]> =>

View File

@@ -1,6 +1,6 @@
import { Action } from '@ngrx/store';
import { type } from '../shared/ngrx/type';
import { FlatNode } from './community-list-service';
import { FlatNode } from './flat-node.model';
/**
* All the action types of the community-list

View File

@@ -1,5 +1,5 @@
import { FlatNode } from './community-list-service';
import { CommunityListActions, CommunityListActionTypes, CommunityListSaveAction } from './community-list.actions';
import { FlatNode } from './flat-node.model';
/**
* States we wish to put in store concerning the community list

View File

@@ -1,7 +1,7 @@
import { ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { CommunityListComponent } from './community-list.component';
import { CommunityListService, FlatNode, showMoreFlatNode, toFlatNode } from '../community-list-service';
import { CommunityListService, showMoreFlatNode, toFlatNode } from '../community-list-service';
import { CdkTreeModule } from '@angular/cdk/tree';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
@@ -15,6 +15,7 @@ import { Collection } from '../../core/shared/collection.model';
import { of as observableOf } from 'rxjs';
import { By } from '@angular/platform-browser';
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
import { FlatNode } from '../flat-node.model';
describe('CommunityListComponent', () => {
let component: CommunityListComponent;

View File

@@ -2,10 +2,11 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { take } from 'rxjs/operators';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { FindListOptions } from '../../core/data/request.models';
import { CommunityListService, FlatNode } from '../community-list-service';
import { CommunityListService} from '../community-list-service';
import { CommunityListDatasource } from '../community-list-datasource';
import { FlatTreeControl } from '@angular/cdk/tree';
import { isEmpty } from '../../shared/empty.util';
import { FlatNode } from '../flat-node.model';
/**
* A tree-structured list of nodes representing the communities, their subCommunities and collections.

View File

@@ -0,0 +1,22 @@
import { Observable } from 'rxjs';
import { Community } from '../core/shared/community.model';
import { Collection } from '../core/shared/collection.model';
import { ShowMoreFlatNode } from './show-more-flat-node.model';
/**
* Each node in the tree is represented by a flatNode which contains info about the node itself and its position and
* state in the tree. There are nodes representing communities, collections and show more links.
*/
export interface FlatNode {
isExpandable$: Observable<boolean>;
name: string;
id: string;
level: number;
isExpanded?: boolean;
parent?: FlatNode;
payload: Community | Collection | ShowMoreFlatNode;
isShowMoreNode: boolean;
route?: string;
currentCommunityPage?: number;
currentCollectionPage?: number;
}

View File

@@ -0,0 +1,6 @@
/**
* The show more links in the community tree are also represented by a flatNode so we know where in
* the tree it should be rendered an who its parent is (needed for the action resulting in clicking this link)
*/
export class ShowMoreFlatNode {
}

View File

@@ -11,9 +11,9 @@ import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.servi
import { LinkService } from '../core/cache/builders/link.service';
import { COMMUNITY_EDIT_PATH, COMMUNITY_CREATE_PATH } from './community-page-routing-paths';
import { CommunityPageAdministratorGuard } from './community-page-administrator.guard';
import { MenuItemType } from '../shared/menu/initial-menus-state';
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
import { ThemedCommunityPageComponent } from './themed-community-page.component';
import { MenuItemType } from '../shared/menu/menu-item-type.model';
@NgModule({
imports: [

View File

@@ -13,11 +13,12 @@ import { MetadataService } from '../core/metadata/metadata.service';
import { fadeInOut } from '../shared/animations/fade';
import { hasValue } from '../shared/empty.util';
import { getAllSucceededRemoteDataPayload, redirectOn4xx } from '../core/shared/operators';
import { getAllSucceededRemoteDataPayload} from '../core/shared/operators';
import { AuthService } from '../core/auth/auth.service';
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../core/data/feature-authorization/feature-id';
import { getCommunityPageRoute } from './community-page-routing-paths';
import { redirectOn4xx } from '../core/shared/authorized.operators';
@Component({
selector: 'ds-community-page',

View File

@@ -2,7 +2,6 @@ import { autoserialize, deserialize, deserializeAs } from 'cerialize';
import { Observable } from 'rxjs';
import { link, typedObject } from '../../cache/builders/build-decorators';
import { IDToUUIDSerializer } from '../../cache/id-to-uuid-serializer';
import { CacheableObject } from '../../cache/object-cache.reducer';
import { RemoteData } from '../../data/remote-data';
import { EPerson } from '../../eperson/models/eperson.model';
import { EPERSON } from '../../eperson/models/eperson.resource-type';
@@ -13,6 +12,7 @@ import { AuthError } from './auth-error.model';
import { AUTH_STATUS } from './auth-status.resource-type';
import { AuthTokenInfo } from './auth-token-info.model';
import { AuthMethod } from './auth.method';
import { CacheableObject } from '../../cache/cacheable-object.model';
/**
* Object that represents the authenticated status of a user

View File

@@ -1,10 +1,10 @@
import { CacheableObject } from '../../cache/object-cache.reducer';
import { typedObject } from '../../cache/builders/build-decorators';
import { excludeFromEquals } from '../../utilities/equals.decorators';
import { autoserialize, autoserializeAs, deserialize } from 'cerialize';
import { ResourceType } from '../../shared/resource-type';
import { SHORT_LIVED_TOKEN } from './short-lived-token.resource-type';
import { HALLink } from '../../shared/hal-link.model';
import { CacheableObject } from '../../cache/cacheable-object.model';
/**
* A short-lived token that can be used to authenticate a rest request

View File

@@ -4,11 +4,11 @@ import { GenericConstructor } from '../../shared/generic-constructor';
import { HALResource } from '../../shared/hal-resource.model';
import { ResourceType } from '../../shared/resource-type';
import {
CacheableObject,
TypedObject,
getResourceTypeValueFor
} from '../object-cache.reducer';
import { InjectionToken } from '@angular/core';
import { CacheableObject } from '../cacheable-object.model';
import { TypedObject } from '../typed-object.model';
export const DATA_SERVICE_FACTORY = new InjectionToken<(resourceType: ResourceType) => GenericConstructor<any>>('getDataServiceFor', {
providedIn: 'root',

View File

@@ -13,10 +13,11 @@ import { RequestService } from '../../data/request.service';
import { UnCacheableObject } from '../../shared/uncacheable-object.model';
import { RemoteData } from '../../data/remote-data';
import { Observable, of as observableOf } from 'rxjs';
import { RequestEntry, RequestEntryState } from '../../data/request.reducer';
import { RequestEntry} from '../../data/request.reducer';
import { followLink, FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
import { take } from 'rxjs/operators';
import { HALLink } from '../../shared/hal-link.model';
import { RequestEntryState } from '../../data/request-entry-state.model';
describe('RemoteDataBuildService', () => {
let service: RemoteDataBuildService;

View File

@@ -13,12 +13,9 @@ import { PaginatedList } from '../../data/paginated-list.model';
import { RemoteData } from '../../data/remote-data';
import {
RequestEntry,
ResponseState,
RequestEntryState,
hasSucceeded
ResponseState
} from '../../data/request.reducer';
import { RequestService } from '../../data/request.service';
import { getRequestFromRequestHref, getRequestFromRequestUUID } from '../../shared/operators';
import { ObjectCacheService } from '../object-cache.service';
import { LinkService } from './link.service';
import { HALLink } from '../../shared/hal-link.model';
@@ -28,6 +25,8 @@ import { HALResource } from '../../shared/hal-resource.model';
import { PAGINATED_LIST } from '../../data/paginated-list.resource-type';
import { getUrlWithoutEmbedParams } from '../../index/index.selectors';
import { getResourceTypeValueFor } from '../object-cache.reducer';
import { hasSucceeded, RequestEntryState } from '../../data/request-entry-state.model';
import { getRequestFromRequestHref, getRequestFromRequestUUID } from '../../shared/request.operators';
@Injectable()
export class RemoteDataBuildService {

View File

@@ -0,0 +1,22 @@
/* tslint:disable:max-classes-per-file */
import { HALResource } from '../shared/hal-resource.model';
import { HALLink } from '../shared/hal-link.model';
import { TypedObject } from './typed-object.model';
/**
* An interface to represent objects that can be cached
*
* A cacheable object should have a self link
*/
export class CacheableObject extends TypedObject implements HALResource {
uuid?: string;
handle?: string;
_links: {
self: HALLink;
};
// isNew: boolean;
// dirtyType: DirtyType;
// hasDirtyAttributes: boolean;
// changedAttributes: AttributeDiffh;
// save(): void;
}

View File

@@ -1,8 +1,8 @@
import { Action } from '@ngrx/store';
import { type } from '../../shared/ngrx/type';
import { CacheableObject } from './object-cache.reducer';
import { Operation } from 'fast-json-patch';
import { CacheableObject } from './cacheable-object.model';
/**
* The list of ObjectCacheAction type definitions

View File

@@ -1,18 +1,16 @@
import { HALLink } from '../shared/hal-link.model';
import { HALResource } from '../shared/hal-resource.model';
import {
AddPatchObjectCacheAction,
AddToObjectCacheAction,
ApplyPatchObjectCacheAction,
ObjectCacheAction,
ObjectCacheActionTypes,
AddToObjectCacheAction,
RemoveFromObjectCacheAction,
ResetObjectCacheTimestampsAction,
AddPatchObjectCacheAction,
ApplyPatchObjectCacheAction
ResetObjectCacheTimestampsAction
} from './object-cache.actions';
import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { CacheEntry } from './cache-entry';
import { ResourceType } from '../shared/resource-type';
import { applyPatch, Operation } from 'fast-json-patch';
import { CacheableObject } from './cacheable-object.model';
/**
* An interface to represent a JsonPatch
@@ -29,11 +27,6 @@ export interface Patch {
operations: Operation[];
}
export abstract class TypedObject {
static type: ResourceType;
type: ResourceType;
}
/**
* Get the string value for an object that may be a string or a ResourceType
*
@@ -49,25 +42,6 @@ export const getResourceTypeValueFor = (type: any): string => {
}
};
/* tslint:disable:max-classes-per-file */
/**
* An interface to represent objects that can be cached
*
* A cacheable object should have a self link
*/
export class CacheableObject extends TypedObject implements HALResource {
uuid?: string;
handle?: string;
_links: {
self: HALLink;
};
// isNew: boolean;
// dirtyType: DirtyType;
// hasDirtyAttributes: boolean;
// changedAttributes: AttributeDiffh;
// save(): void;
}
/**
* An entry in the ObjectCache
*/

View File

@@ -20,10 +20,10 @@ import { Patch } from './object-cache.reducer';
import { ObjectCacheService } from './object-cache.service';
import { AddToSSBAction } from './server-sync-buffer.actions';
import { RemoveFromIndexBySubstringAction } from '../index/index.actions';
import { IndexName } from '../index/index.reducer';
import { HALLink } from '../shared/hal-link.model';
import { storeModuleConfig } from '../../app.reducer';
import { TestColdObservable } from 'jasmine-marbles/src/test-observables';
import { IndexName } from '../index/index-name.model';
describe('ObjectCacheService', () => {
let service: ObjectCacheService;

View File

@@ -22,11 +22,12 @@ import {
RemoveFromObjectCacheAction
} from './object-cache.actions';
import { CacheableObject, ObjectCacheEntry, ObjectCacheState } from './object-cache.reducer';
import { ObjectCacheEntry, ObjectCacheState } from './object-cache.reducer';
import { AddToSSBAction } from './server-sync-buffer.actions';
import { RemoveFromIndexBySubstringAction } from '../index/index.actions';
import { IndexName } from '../index/index.reducer';
import { HALLink } from '../shared/hal-link.model';
import { CacheableObject } from './cacheable-object.model';
import { IndexName } from '../index/index-name.model';
/**
* The base selector function to select the object cache in the store

View File

@@ -1,9 +1,9 @@
import { RequestError } from '../data/request.models';
import { PageInfo } from '../shared/page-info.model';
import { ConfigObject } from '../config/models/config.model';
import { DSpaceObject } from '../shared/dspace-object.model';
import { HALLink } from '../shared/hal-link.model';
import { UnCacheableObject } from '../shared/uncacheable-object.model';
import { RequestError } from '../data/request-error.model';
/* tslint:disable:max-classes-per-file */
export class RestResponse {

View File

@@ -0,0 +1,6 @@
import { ResourceType } from '../shared/resource-type';
export abstract class TypedObject {
static type: ResourceType;
type: ResourceType;
}

View File

@@ -1,8 +1,8 @@
import { autoserialize, deserialize } from 'cerialize';
import { CacheableObject } from '../../cache/object-cache.reducer';
import { HALLink } from '../../shared/hal-link.model';
import { ResourceType } from '../../shared/resource-type';
import { excludeFromEquals } from '../../utilities/equals.decorators';
import { CacheableObject } from '../../cache/cacheable-object.model';
export abstract class ConfigObject implements CacheableObject {

View File

@@ -1,8 +1,8 @@
import { BaseResponseParsingService } from './base-response-parsing.service';
import { ObjectCacheService } from '../cache/object-cache.service';
import { CacheableObject } from '../cache/object-cache.reducer';
import { GetRequest, RestRequest } from './request.models';
import { DSpaceObject } from '../shared/dspace-object.model';
import { CacheableObject } from '../cache/cacheable-object.model';
/* tslint:disable:max-classes-per-file */
class TestService extends BaseResponseParsingService {

View File

@@ -1,6 +1,5 @@
import { hasNoValue, hasValue, isNotEmpty } from '../../shared/empty.util';
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
import { CacheableObject } from '../cache/object-cache.reducer';
import { Serializer } from '../serializer';
import { PageInfo } from '../shared/page-info.model';
import { ObjectCacheService } from '../cache/object-cache.service';
@@ -9,6 +8,7 @@ import { PaginatedList, buildPaginatedList } from './paginated-list.model';
import { getClassForType } from '../cache/builders/build-decorators';
import { RestRequest } from './request.models';
import { environment } from '../../../environments/environment';
import { CacheableObject } from '../cache/cacheable-object.model';
/* tslint:disable:max-classes-per-file */

View File

@@ -25,10 +25,10 @@ import { RequestService } from './request.service';
import { BitstreamFormatDataService } from './bitstream-format-data.service';
import { BitstreamFormat } from '../shared/bitstream-format.model';
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
import { sendRequest } from '../shared/operators';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { PageInfo } from '../shared/page-info.model';
import { RequestParam } from '../cache/models/request-param.model';
import { sendRequest } from '../shared/request.operators';
/**
* A service to retrieve {@link Bitstream}s from the REST API

View File

@@ -19,12 +19,12 @@ import { BitstreamFormat } from '../shared/bitstream-format.model';
import { BITSTREAM_FORMAT } from '../shared/bitstream-format.resource-type';
import { Bitstream } from '../shared/bitstream.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { sendRequest } from '../shared/operators';
import { DataService } from './data.service';
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
import { RemoteData } from './remote-data';
import { PostRequest, PutRequest } from './request.models';
import { RequestService } from './request.service';
import { sendRequest } from '../shared/request.operators';
const bitstreamFormatsStateSelector = createSelector(
coreSelector,

View File

@@ -22,7 +22,7 @@ import { FindListOptions, GetRequest } from './request.models';
import { RequestService } from './request.service';
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
import { Bitstream } from '../shared/bitstream.model';
import { RequestEntryState } from './request.reducer';
import { RequestEntryState } from './request-entry-state.model';
/**
* A service to retrieve {@link Bundle}s from the REST API

View File

@@ -1,6 +1,6 @@
import { Operation } from 'fast-json-patch';
import { TypedObject } from '../cache/object-cache.reducer';
import { TypedObject } from '../cache/typed-object.model';
/**
* An interface to determine what differs between two

View File

@@ -214,4 +214,12 @@ export class CollectionDataService extends ComColDataService<Collection> {
findOwningCollectionFor(item: Item): Observable<RemoteData<Collection>> {
return this.findByHref(item._links.owningCollection.href);
}
protected getScopeCommunityHref(options: FindListOptions) {
return this.cds.getEndpoint().pipe(
map((endpoint: string) => this.cds.getIDHref(endpoint, options.scopeID)),
filter((href: string) => isNotEmpty(href)),
take(1)
);
}
}

View File

@@ -25,6 +25,12 @@ import { BitstreamDataService } from './bitstream-data.service';
const LINK_NAME = 'test';
const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d';
const communitiesEndpoint = 'https://rest.api/core/communities';
const communityEndpoint = `${communitiesEndpoint}/${scopeID}`;
class TestService extends ComColDataService<any> {
constructor(
@@ -47,6 +53,11 @@ class TestService extends ComColDataService<any> {
// implementation in subclasses for communities/collections
return undefined;
}
protected getScopeCommunityHref(options: FindListOptions): Observable<string> {
// implementation in subclasses for communities/collections
return observableOf(communityEndpoint);
}
}
// tslint:disable:no-shadowed-variable
@@ -66,12 +77,9 @@ describe('ComColDataService', () => {
const http = {} as HttpClient;
const comparator = {} as any;
const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d';
const options = Object.assign(new FindListOptions(), {
scopeID: scopeID
});
const communitiesEndpoint = 'https://rest.api/core/communities';
const communityEndpoint = `${communitiesEndpoint}/${scopeID}`;
const scopedEndpoint = `${communityEndpoint}/${LINK_NAME}`;
const mockHalService = {

View File

@@ -4,8 +4,6 @@ import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
import { ObjectCacheService } from '../cache/object-cache.service';
import { Community } from '../shared/community.model';
import { HALLink } from '../shared/hal-link.model';
import { CommunityDataService } from './community-data.service';
import { DataService } from './data.service';
import { FindListOptions } from './request.models';
import { PaginatedList } from './paginated-list.model';
@@ -21,7 +19,6 @@ import { URLCombiner } from '../url-combiner/url-combiner';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
export abstract class ComColDataService<T extends Community | Collection> extends DataService<T> {
protected abstract cds: CommunityDataService;
protected abstract objectCache: ObjectCacheService;
protected abstract halService: HALEndpointService;
protected abstract bitstreamDataService: BitstreamDataService;
@@ -40,11 +37,7 @@ export abstract class ComColDataService<T extends Community | Collection> extend
if (isEmpty(options.scopeID)) {
return this.halService.getEndpoint(linkPath);
} else {
const scopeCommunityHrefObs = this.cds.getEndpoint().pipe(
map((endpoint: string) => this.cds.getIDHref(endpoint, options.scopeID)),
filter((href: string) => isNotEmpty(href)),
take(1)
);
const scopeCommunityHrefObs = this.getScopeCommunityHref(options);
this.createAndSendGetRequest(scopeCommunityHrefObs, true);
@@ -65,6 +58,8 @@ export abstract class ComColDataService<T extends Community | Collection> extend
}
}
protected abstract getScopeCommunityHref(options: FindListOptions): Observable<string>;
protected abstract getFindByParentHref(parentUUID: string): Observable<string>;
public findByParent(parentUUID: string, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {

View File

@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { filter, map, switchMap, take } from 'rxjs/operators';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
@@ -20,13 +20,13 @@ import { FindListOptions } from './request.models';
import { RequestService } from './request.service';
import { BitstreamDataService } from './bitstream-data.service';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { isNotEmpty } from '../../shared/empty.util';
@Injectable()
@dataService(COMMUNITY)
export class CommunityDataService extends ComColDataService<Community> {
protected linkPath = 'communities';
protected topLinkPath = 'search/top';
protected cds = this;
constructor(
protected requestService: RequestService,
@@ -58,4 +58,11 @@ export class CommunityDataService extends ComColDataService<Community> {
);
}
protected getScopeCommunityHref(options: FindListOptions) {
return this.getEndpoint().pipe(
map((endpoint: string) => this.getIDHref(endpoint, options.scopeID)),
filter((href: string) => isNotEmpty(href)),
take(1)
);
}
}

View File

@@ -22,7 +22,7 @@ import { RequestParam } from '../cache/models/request-param.model';
import { getMockRemoteDataBuildService } from '../../shared/mocks/remote-data-build.service.mock';
import { TestScheduler } from 'rxjs/testing';
import { RemoteData } from './remote-data';
import { RequestEntryState } from './request.reducer';
import { RequestEntryState } from './request-entry-state.model';
const endpoint = 'https://rest.api/core';

View File

@@ -21,7 +21,6 @@ import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { getClassForType } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { RequestParam } from '../cache/models/request-param.model';
import { CacheableObject } from '../cache/object-cache.reducer';
import { ObjectCacheService } from '../cache/object-cache.service';
import { CoreState } from '../core.reducers';
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
@@ -45,6 +44,7 @@ import { RestRequestMethod } from './rest-request-method';
import { UpdateDataService } from './update-data.service';
import { GenericConstructor } from '../shared/generic-constructor';
import { NoContent } from '../shared/NoContent.model';
import { CacheableObject } from '../cache/cacheable-object.model';
export abstract class DataService<T extends CacheableObject> implements UpdateDataService<T> {
protected abstract requestService: RequestService;

View File

@@ -2,9 +2,9 @@ import { Injectable } from '@angular/core';
import { compare } from 'fast-json-patch';
import { Operation } from 'fast-json-patch';
import { getClassForType } from '../cache/builders/build-decorators';
import { TypedObject } from '../cache/object-cache.reducer';
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
import { ChangeAnalyzer } from './change-analyzer';
import { TypedObject } from '../cache/typed-object.model';
/**
* A class to determine what differs between two

View File

@@ -1,6 +1,5 @@
import { hasNoValue, hasValue, isNotEmpty } from '../../shared/empty.util';
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
import { CacheableObject } from '../cache/object-cache.reducer';
import { Serializer } from '../serializer';
import { PageInfo } from '../shared/page-info.model';
import { ObjectCacheService } from '../cache/object-cache.service';
@@ -17,6 +16,7 @@ import { ParsedResponse } from '../cache/response.models';
import { RestRequestMethod } from './rest-request-method';
import { getUrlWithoutEmbedParams, getEmbedSizeParams } from '../index/index.selectors';
import { URLCombiner } from '../url-combiner/url-combiner';
import { CacheableObject } from '../cache/cacheable-object.model';
/* tslint:disable:max-classes-per-file */

View File

@@ -11,8 +11,8 @@ import { RestRequest } from './request.models';
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
import { ParsedResponse } from '../cache/response.models';
import { DSpaceObject } from '../shared/dspace-object.model';
import { CacheableObject } from '../cache/object-cache.reducer';
import { environment } from '../../../environments/environment';
import { CacheableObject } from '../cache/cacheable-object.model';
/**
* ResponseParsingService able to deal with HAL Endpoints that are only needed as steps

View File

@@ -2,9 +2,9 @@ import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTr
import { AuthorizationDataService } from '../authorization-data.service';
import { FeatureID } from '../feature-id';
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
import { returnForbiddenUrlTreeOrLoginOnAllFalse } from '../../../shared/operators';
import { switchMap } from 'rxjs/operators';
import { AuthService } from '../../../auth/auth.service';
import { returnForbiddenUrlTreeOrLoginOnAllFalse } from '../../../shared/authorized.operators';
/**
* Abstract Guard for preventing unauthorized activating and loading of routes when a user

View File

@@ -18,7 +18,7 @@ import { Observable } from 'rxjs/internal/Observable';
import { PaginatedList } from './paginated-list.model';
import { ITEM_TYPE } from '../shared/item-relationships/item-type.resource-type';
import { LICENSE } from '../shared/license.resource-type';
import { CacheableObject } from '../cache/object-cache.reducer';
import { CacheableObject } from '../cache/cacheable-object.model';
/* tslint:disable:max-classes-per-file */
class DataServiceImpl extends DataService<any> {

View File

@@ -16,7 +16,6 @@ import { ExternalSourceEntry } from '../shared/external-source-entry.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { Item } from '../shared/item.model';
import { ITEM } from '../shared/item.resource-type';
import { sendRequest } from '../shared/operators';
import { URLCombiner } from '../url-combiner/url-combiner';
import { DataService } from './data.service';
@@ -34,6 +33,7 @@ import { NoContent } from '../shared/NoContent.model';
import { GenericConstructor } from '../shared/generic-constructor';
import { ResponseParsingService } from './parsing.service';
import { StatusCodeOnlyResponseParsingService } from './status-code-only-response-parsing.service';
import { sendRequest } from '../shared/request.operators';
@Injectable()
@dataService(ITEM)

View File

@@ -0,0 +1,8 @@
/**
* Enum that represents the different types of updates that can be performed on a field in the ObjectUpdates store
*/
export enum FieldChangeType {
UPDATE = 0,
ADD = 1,
REMOVE = 2
}

View File

@@ -0,0 +1,10 @@
import { Identifiable } from './identifiable.model';
import { FieldChangeType } from './field-change-type.model';
/**
* The state of a single field update
*/
export interface FieldUpdate {
field: Identifiable;
changeType: FieldChangeType;
}

View File

@@ -0,0 +1,8 @@
import { FieldUpdate } from './field-update.model';
/**
* The states of all field updates available for a single page, mapped by uuid
*/
export interface FieldUpdates {
[uuid: string]: FieldUpdate;
}

View File

@@ -0,0 +1,6 @@
/**
* Represents every object that has a UUID
*/
export interface Identifiable {
uuid: string;
}

View File

@@ -1,9 +1,10 @@
import { type } from '../../../shared/ngrx/type';
import { Action } from '@ngrx/store';
import {Identifiable} from './object-updates.reducer';
import { INotification } from '../../../shared/notifications/models/notification.model';
import { PatchOperationService } from './patch-operation-service/patch-operation.service';
import { GenericConstructor } from '../../shared/generic-constructor';
import { Identifiable } from './identifiable.model';
import { FieldChangeType } from './field-change-type.model';
/**
* The list of ObjectUpdatesAction type definitions
@@ -23,15 +24,6 @@ export const ObjectUpdatesActionTypes = {
/* tslint:disable:max-classes-per-file */
/**
* Enum that represents the different types of updates that can be performed on a field in the ObjectUpdates store
*/
export enum FieldChangeType {
UPDATE = 0,
ADD = 1,
REMOVE = 2
}
/**
* An ngrx action to initialize a new page's fields in the ObjectUpdates state
*/

View File

@@ -2,7 +2,6 @@ import * as deepFreeze from 'deep-freeze';
import {
AddFieldUpdateAction,
DiscardObjectUpdatesAction,
FieldChangeType,
InitializeFieldsAction,
ReinstateObjectUpdatesAction,
RemoveAllObjectUpdatesAction,
@@ -14,6 +13,7 @@ import {
} from './object-updates.actions';
import { OBJECT_UPDATES_TRASH_PATH, objectUpdatesReducer } from './object-updates.reducer';
import { Relationship } from '../../shared/item-relationships/relationship.model';
import { FieldChangeType } from './field-change-type.model';
class NullAction extends RemoveFieldUpdateAction {
type = null;

View File

@@ -1,16 +1,15 @@
import {
AddFieldUpdateAction,
DiscardObjectUpdatesAction,
FieldChangeType,
InitializeFieldsAction,
ObjectUpdatesAction,
ObjectUpdatesActionTypes,
ReinstateObjectUpdatesAction,
RemoveFieldUpdateAction,
RemoveObjectUpdatesAction,
SelectVirtualMetadataAction,
SetEditableFieldUpdateAction,
SetValidFieldUpdateAction,
SelectVirtualMetadataAction,
} from './object-updates.actions';
import { hasNoValue, hasValue } from '../../../shared/empty.util';
import { Relationship } from '../../shared/item-relationships/relationship.model';
@@ -18,6 +17,9 @@ import { PatchOperationService } from './patch-operation-service/patch-operation
import { Item } from '../../shared/item.model';
import { RelationshipType } from '../../shared/item-relationships/relationship-type.model';
import { GenericConstructor } from '../../shared/generic-constructor';
import { Identifiable } from './identifiable.model';
import { FieldUpdates } from './field-updates.model';
import { FieldChangeType } from './field-change-type.model';
/**
* Path where discarded objects are saved
@@ -40,28 +42,6 @@ export interface FieldStates {
[uuid: string]: FieldState;
}
/**
* Represents every object that has a UUID
*/
export interface Identifiable {
uuid: string;
}
/**
* The state of a single field update
*/
export interface FieldUpdate {
field: Identifiable;
changeType: FieldChangeType;
}
/**
* The states of all field updates available for a single page, mapped by uuid
*/
export interface FieldUpdates {
[uuid: string]: FieldUpdate;
}
/**
* The states of all virtual metadata selections available for a single page, mapped by the relationship uuid
*/

View File

@@ -3,7 +3,6 @@ import { CoreState } from '../../core.reducers';
import { ObjectUpdatesService } from './object-updates.service';
import {
DiscardObjectUpdatesAction,
FieldChangeType,
InitializeFieldsAction,
ReinstateObjectUpdatesAction,
RemoveFieldUpdateAction,
@@ -16,6 +15,7 @@ import { NotificationType } from '../../../shared/notifications/models/notificat
import { OBJECT_UPDATES_TRASH_PATH } from './object-updates.reducer';
import { Relationship } from '../../shared/item-relationships/relationship.model';
import { Injector } from '@angular/core';
import { FieldChangeType } from './field-change-type.model';
describe('ObjectUpdatesService', () => {
let service: ObjectUpdatesService;

View File

@@ -4,8 +4,6 @@ import { CoreState } from '../../core.reducers';
import { coreSelector } from '../../core.selectors';
import {
FieldState,
FieldUpdates,
Identifiable,
OBJECT_UPDATES_TRASH_PATH,
ObjectUpdatesEntry,
ObjectUpdatesState,
@@ -15,7 +13,6 @@ import { Observable } from 'rxjs';
import {
AddFieldUpdateAction,
DiscardObjectUpdatesAction,
FieldChangeType,
InitializeFieldsAction,
ReinstateObjectUpdatesAction,
RemoveFieldUpdateAction,
@@ -35,6 +32,9 @@ import { INotification } from '../../../shared/notifications/models/notification
import { Operation } from 'fast-json-patch';
import { PatchOperationService } from './patch-operation-service/patch-operation.service';
import { GenericConstructor } from '../../shared/generic-constructor';
import { Identifiable } from './identifiable.model';
import { FieldUpdates } from './field-updates.model';
import { FieldChangeType } from './field-change-type.model';
function objectUpdatesStateSelector(): MemoizedSelector<CoreState, ObjectUpdatesState> {
return createSelector(coreSelector, (state: CoreState) => state['cache/object-updates']);

View File

@@ -1,8 +1,8 @@
import { MetadataPatchOperationService } from './metadata-patch-operation.service';
import { FieldUpdates } from '../object-updates.reducer';
import { Operation } from 'fast-json-patch';
import { FieldChangeType } from '../object-updates.actions';
import { MetadatumViewModel } from '../../../shared/metadata.models';
import { FieldUpdates } from '../field-updates.model';
import { FieldChangeType } from '../field-change-type.model';
describe('MetadataPatchOperationService', () => {
let service: MetadataPatchOperationService;

View File

@@ -1,14 +1,14 @@
import { PatchOperationService } from './patch-operation.service';
import { MetadatumViewModel } from '../../../shared/metadata.models';
import { FieldUpdates } from '../object-updates.reducer';
import { Operation } from 'fast-json-patch';
import { FieldChangeType } from '../object-updates.actions';
import { Injectable } from '@angular/core';
import { MetadataPatchOperation } from './operations/metadata/metadata-patch-operation.model';
import { hasValue } from '../../../../shared/empty.util';
import { MetadataPatchAddOperation } from './operations/metadata/metadata-patch-add-operation.model';
import { MetadataPatchRemoveOperation } from './operations/metadata/metadata-patch-remove-operation.model';
import { MetadataPatchReplaceOperation } from './operations/metadata/metadata-patch-replace-operation.model';
import { FieldUpdates } from '../field-updates.model';
import { FieldChangeType } from '../field-change-type.model';
/**
* Service transforming {@link FieldUpdates} into {@link Operation}s for metadata values

View File

@@ -1,5 +1,5 @@
import { FieldUpdates } from '../object-updates.reducer';
import { Operation } from 'fast-json-patch';
import { FieldUpdates } from '../field-updates.model';
/**
* Interface for a service dealing with the transformations of patch operations from the object-updates store

View File

@@ -3,11 +3,11 @@ import { hasValue, isEmpty, hasNoValue, isUndefined } from '../../shared/empty.u
import { HALResource } from '../shared/hal-resource.model';
import { HALLink } from '../shared/hal-link.model';
import { typedObject } from '../cache/builders/build-decorators';
import { CacheableObject } from '../cache/object-cache.reducer';
import { PAGINATED_LIST } from './paginated-list.resource-type';
import { ResourceType } from '../shared/resource-type';
import { excludeFromEquals } from '../utilities/equals.decorators';
import { autoserialize, deserialize } from 'cerialize';
import { CacheableObject } from '../cache/cacheable-object.model';
/**
* Factory function for a paginated list

View File

@@ -29,7 +29,6 @@ import { Relationship } from '../shared/item-relationships/relationship.model';
import { RELATIONSHIP } from '../shared/item-relationships/relationship.resource-type';
import { Item } from '../shared/item.model';
import {
sendRequest,
getFirstCompletedRemoteData,
getFirstSucceededRemoteData,
getFirstSucceededRemoteDataPayload,
@@ -42,8 +41,9 @@ import { PaginatedList } from './paginated-list.model';
import { RemoteData } from './remote-data';
import { DeleteRequest, FindListOptions, PostRequest, RestRequest } from './request.models';
import { RequestService } from './request.service';
import { RequestEntryState } from './request.reducer';
import { NoContent } from '../shared/NoContent.model';
import { RequestEntryState } from './request-entry-state.model';
import { sendRequest } from '../shared/request.operators';
const relationshipListsStateSelector = (state: AppState) => state.relationshipLists;

View File

@@ -1,17 +1,17 @@
import {
RequestEntryState,
isStale,
hasCompleted,
hasSucceeded,
hasFailed,
isLoading,
isSuccessStale,
isErrorStale,
isSuccess,
hasSucceeded,
isError,
isErrorStale,
isLoading,
isRequestPending,
isResponsePending,
isRequestPending
} from './request.reducer';
isStale,
isSuccess,
isSuccessStale,
RequestEntryState
} from './request-entry-state.model';
/**
* A class to represent the state of a remote resource

View File

@@ -0,0 +1,88 @@
export enum RequestEntryState {
RequestPending = 'RequestPending',
ResponsePending = 'ResponsePending',
Error = 'Error',
Success = 'Success',
ErrorStale = 'ErrorStale',
SuccessStale = 'SuccessStale'
}
/**
* Returns true if the given state is RequestPending, false otherwise
*/
export const isRequestPending = (state: RequestEntryState) =>
state === RequestEntryState.RequestPending;
/**
* Returns true if the given state is Error, false otherwise
*/
export const isError = (state: RequestEntryState) =>
state === RequestEntryState.Error;
/**
* Returns true if the given state is Success, false otherwise
*/
export const isSuccess = (state: RequestEntryState) =>
state === RequestEntryState.Success;
/**
* Returns true if the given state is ErrorStale, false otherwise
*/
export const isErrorStale = (state: RequestEntryState) =>
state === RequestEntryState.ErrorStale;
/**
* Returns true if the given state is SuccessStale, false otherwise
*/
export const isSuccessStale = (state: RequestEntryState) =>
state === RequestEntryState.SuccessStale;
/**
* Returns true if the given state is ResponsePending, false otherwise
*/
export const isResponsePending = (state: RequestEntryState) =>
state === RequestEntryState.ResponsePending;
/**
* Returns true if the given state is RequestPending or ResponsePending,
* false otherwise
*/
export const isLoading = (state: RequestEntryState) =>
isRequestPending(state) || isResponsePending(state);
/**
* If isLoading is true for the given state, this method returns undefined, we can't know yet.
* If it isn't this method will return true if the the given state is Error or ErrorStale,
* false otherwise
*/
export const hasFailed = (state: RequestEntryState) => {
if (isLoading(state)) {
return undefined;
} else {
return isError(state) || isErrorStale(state);
}
};
/**
* If isLoading is true for the given state, this method returns undefined, we can't know yet.
* If it isn't this method will return true if the the given state is Success or SuccessStale,
* false otherwise
*/
export const hasSucceeded = (state: RequestEntryState) => {
if (isLoading(state)) {
return undefined;
} else {
return isSuccess(state) || isSuccessStale(state);
}
};
/**
* Returns true if the given state is not loading, false otherwise
*/
export const hasCompleted = (state: RequestEntryState) =>
!isLoading(state);
/**
* Returns true if the given state is SuccessStale or ErrorStale, false otherwise
*/
export const isStale = (state: RequestEntryState) =>
isSuccessStale(state) || isErrorStale(state);

View File

@@ -0,0 +1,4 @@
export class RequestError extends Error {
statusCode: number;
statusText: string;
}

View File

@@ -16,10 +16,11 @@ import {
RequestSuccessAction,
ResetResponseTimestampsAction
} from './request.actions';
import { RequestError, RestRequest } from './request.models';
import { RestRequest } from './request.models';
import { RequestEntry } from './request.reducer';
import { RequestService } from './request.service';
import { ParsedResponse } from '../cache/response.models';
import { RequestError } from './request-error.model';
@Injectable()
export class RequestEffects {

View File

@@ -275,8 +275,4 @@ export class MyDSpaceRequest extends GetRequest {
public responseMsToLive = 10 * 1000;
}
export class RequestError extends Error {
statusCode: number;
statusText: string;
}
/* tslint:enable:max-classes-per-file */

View File

@@ -9,7 +9,8 @@ import {
ResetResponseTimestampsAction
} from './request.actions';
import { GetRequest } from './request.models';
import { RequestEntryState, requestReducer, RequestState } from './request.reducer';
import { requestReducer, RequestState } from './request.reducer';
import { RequestEntryState } from './request-entry-state.model';
class NullAction extends RequestSuccessAction {
type = null;

View File

@@ -2,107 +2,18 @@ import {
RequestAction,
RequestActionTypes,
RequestConfigureAction,
RequestErrorAction,
RequestExecuteAction,
RequestRemoveAction,
ResetResponseTimestampsAction,
RequestStaleAction,
RequestSuccessAction,
RequestErrorAction,
RequestStaleAction
ResetResponseTimestampsAction
} from './request.actions';
import { RestRequest } from './request.models';
import { HALLink } from '../shared/hal-link.model';
import { UnCacheableObject } from '../shared/uncacheable-object.model';
import { isNull } from '../../shared/empty.util';
export enum RequestEntryState {
RequestPending = 'RequestPending',
ResponsePending = 'ResponsePending',
Error = 'Error',
Success = 'Success',
ErrorStale = 'ErrorStale',
SuccessStale = 'SuccessStale'
}
/**
* Returns true if the given state is RequestPending, false otherwise
*/
export const isRequestPending = (state: RequestEntryState) =>
state === RequestEntryState.RequestPending;
/**
* Returns true if the given state is ResponsePending, false otherwise
*/
export const isResponsePending = (state: RequestEntryState) =>
state === RequestEntryState.ResponsePending;
/**
* Returns true if the given state is Error, false otherwise
*/
export const isError = (state: RequestEntryState) =>
state === RequestEntryState.Error;
/**
* Returns true if the given state is Success, false otherwise
*/
export const isSuccess = (state: RequestEntryState) =>
state === RequestEntryState.Success;
/**
* Returns true if the given state is ErrorStale, false otherwise
*/
export const isErrorStale = (state: RequestEntryState) =>
state === RequestEntryState.ErrorStale;
/**
* Returns true if the given state is SuccessStale, false otherwise
*/
export const isSuccessStale = (state: RequestEntryState) =>
state === RequestEntryState.SuccessStale;
/**
* Returns true if the given state is RequestPending or ResponsePending,
* false otherwise
*/
export const isLoading = (state: RequestEntryState) =>
isRequestPending(state) || isResponsePending(state);
/**
* If isLoading is true for the given state, this method returns undefined, we can't know yet.
* If it isn't this method will return true if the the given state is Error or ErrorStale,
* false otherwise
*/
export const hasFailed = (state: RequestEntryState) => {
if (isLoading(state)) {
return undefined;
} else {
return isError(state) || isErrorStale(state);
}
};
/**
* If isLoading is true for the given state, this method returns undefined, we can't know yet.
* If it isn't this method will return true if the the given state is Success or SuccessStale,
* false otherwise
*/
export const hasSucceeded = (state: RequestEntryState) => {
if (isLoading(state)) {
return undefined;
} else {
return isSuccess(state) || isSuccessStale(state);
}
};
/**
* Returns true if the given state is not loading, false otherwise
*/
export const hasCompleted = (state: RequestEntryState) =>
!isLoading(state);
/**
* Returns true if the given state is SuccessStale or ErrorStale, false otherwise
*/
export const isStale = (state: RequestEntryState) =>
isSuccessStale(state) || isErrorStale(state);
import { hasSucceeded, isStale, RequestEntryState } from './request-entry-state.model';
export class ResponseState {
timeCompleted: number;

View File

@@ -19,11 +19,12 @@ import {
PutRequest,
RestRequest
} from './request.models';
import { RequestEntry, RequestEntryState } from './request.reducer';
import { RequestEntry} from './request.reducer';
import { RequestService } from './request.service';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { storeModuleConfig } from '../../app.reducer';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { RequestEntryState } from './request-entry-state.model';
describe('RequestService', () => {
let scheduler: TestScheduler;

View File

@@ -18,10 +18,11 @@ import {
RequestStaleAction
} from './request.actions';
import { GetRequest, RestRequest } from './request.models';
import { RequestEntry, RequestState, isStale, isLoading } from './request.reducer';
import { RequestEntry, RequestState} from './request.reducer';
import { CommitSSBAction } from '../cache/server-sync-buffer.actions';
import { RestRequestMethod } from './rest-request-method';
import { coreSelector } from '../core.selectors';
import { isLoading, isStale } from './request-entry-state.model';
/**
* The base selector function to select the request state in the store

View File

@@ -1,10 +1,10 @@
import { typedObject } from '../cache/builders/build-decorators';
import { CacheableObject } from '../cache/object-cache.reducer';
import { ROOT } from './root.resource-type';
import { excludeFromEquals } from '../utilities/equals.decorators';
import { autoserialize, deserialize } from 'cerialize';
import { ResourceType } from '../shared/resource-type';
import { HALLink } from '../shared/hal-link.model';
import { CacheableObject } from '../cache/cacheable-object.model';
/**
* The root rest api resource

View File

@@ -1,6 +1,6 @@
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { returnEndUserAgreementUrlTreeOnFalse } from '../shared/operators';
import { returnEndUserAgreementUrlTreeOnFalse } from '../shared/authorized.operators';
/**
* An abstract guard for redirecting users to the user agreement page if a certain condition is met

View File

@@ -0,0 +1,17 @@
/**
* An enum containing all index names
*/
export enum IndexName {
// Contains all objects in the object cache indexed by UUID
OBJECT = 'object/uuid-to-self-link',
// contains all requests in the request cache indexed by UUID
REQUEST = 'get-request/href-to-uuid',
/**
* Contains the alternative link for an objects
* Maps these link on to their matching self link in the object cache
* Eg. /workspaceitems/12/item --> /items/12345
*/
ALTERNATIVE_OBJECT_LINK = 'object/alt-link-to-self-link'
}

View File

@@ -1,7 +1,7 @@
import { Action } from '@ngrx/store';
import { type } from '../../shared/ngrx/type';
import { IndexName } from './index.reducer';
import { IndexName } from './index-name.model';
/**
* The list of HrefIndexAction type definitions

View File

@@ -6,9 +6,9 @@ import { cold, hot } from 'jasmine-marbles';
import { AddToObjectCacheAction } from '../cache/object-cache.actions';
import { Item } from '../shared/item.model';
import { AddToIndexAction } from './index.actions';
import { IndexName } from './index.reducer';
import { provideMockStore } from '@ngrx/store/testing';
import { NoOpAction } from '../../shared/ngrx/no-op.action';
import { IndexName } from './index-name.model';
describe('ObjectUpdatesEffects', () => {
let indexEffects: UUIDIndexEffects;

View File

@@ -14,12 +14,12 @@ import {
} from '../data/request.actions';
import { AddToIndexAction, RemoveFromIndexByValueAction } from './index.actions';
import { hasValue } from '../../shared/empty.util';
import { IndexName } from './index.reducer';
import { RestRequestMethod } from '../data/rest-request-method';
import { getUrlWithoutEmbedParams, uuidFromHrefSelector } from './index.selectors';
import { Store, select } from '@ngrx/store';
import { CoreState } from '../core.reducers';
import { NoOpAction } from '../../shared/ngrx/no-op.action';
import { IndexName } from './index-name.model';
@Injectable()
export class UUIDIndexEffects {

View File

@@ -1,11 +1,12 @@
import * as deepFreeze from 'deep-freeze';
import { IndexName, indexReducer, MetaIndexState } from './index.reducer';
import { indexReducer, MetaIndexState } from './index.reducer';
import {
AddToIndexAction,
RemoveFromIndexBySubstringAction,
RemoveFromIndexByValueAction
} from './index.actions';
import { IndexName } from './index-name.model';
class NullAction extends AddToIndexAction {
type = null;

View File

@@ -1,28 +1,5 @@
import {
AddToIndexAction,
IndexAction,
IndexActionTypes,
RemoveFromIndexBySubstringAction,
RemoveFromIndexByValueAction
} from './index.actions';
/**
* An enum containing all index names
*/
export enum IndexName {
// Contains all objects in the object cache indexed by UUID
OBJECT = 'object/uuid-to-self-link',
// contains all requests in the request cache indexed by UUID
REQUEST = 'get-request/href-to-uuid',
/**
* Contains the alternative link for an objects
* Maps these link on to their matching self link in the object cache
* Eg. /workspaceitems/12/item --> /items/12345
*/
ALTERNATIVE_OBJECT_LINK = 'object/alt-link-to-self-link'
}
import { AddToIndexAction, IndexAction, IndexActionTypes, RemoveFromIndexBySubstringAction, RemoveFromIndexByValueAction } from './index.actions';
import { IndexName } from './index-name.model';
/**
* The state of a single index

View File

@@ -3,8 +3,9 @@ import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { CoreState } from '../core.reducers';
import { coreSelector } from '../core.selectors';
import { URLCombiner } from '../url-combiner/url-combiner';
import { IndexName, IndexState, MetaIndexState } from './index.reducer';
import { IndexState, MetaIndexState } from './index.reducer';
import * as parse from 'url-parse';
import { IndexName } from './index-name.model';
/**
* Return the given url without `embed` params.

View File

@@ -2,7 +2,6 @@ import { autoserialize, deserialize, deserializeAs } from 'cerialize';
import { link, typedObject } from '../../cache/builders/build-decorators';
import { IDToUUIDSerializer } from '../../cache/id-to-uuid-serializer';
import { ActionType } from './action-type.model';
import { CacheableObject } from '../../cache/object-cache.reducer';
import { HALLink } from '../../shared/hal-link.model';
import { RESOURCE_POLICY } from './resource-policy.resource-type';
import { excludeFromEquals } from '../../utilities/equals.decorators';
@@ -14,6 +13,7 @@ import { GROUP } from '../../eperson/models/group.resource-type';
import { Group } from '../../eperson/models/group.model';
import { EPERSON } from '../../eperson/models/eperson.resource-type';
import { EPerson } from '../../eperson/models/eperson.model';
import { CacheableObject } from '../../cache/cacheable-object.model';
/**
* Model class for a Resource Policy

View File

@@ -0,0 +1,91 @@
import { Router, UrlTree } from '@angular/router';
import { AuthService } from '../auth/auth.service';
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { filter, map, withLatestFrom } from 'rxjs/operators';
import { InjectionToken } from '@angular/core';
import { RemoteData } from '../data/remote-data';
import { getEndUserAgreementPath } from '../../info/info-routing-paths';
import { getForbiddenRoute, getPageNotFoundRoute } from '../../app-routing-paths';
export const REDIRECT_ON_4XX = new InjectionToken<<T>(router: Router, authService: AuthService) => (source: Observable<RemoteData<T>>) => Observable<RemoteData<T>>>('redirectOn4xx', {
providedIn: 'root',
factory: () => redirectOn4xx
});
/**
* Operator that checks if a remote data object returned a 4xx error
* When it does contain such an error, it will redirect the user to the related error page, without
* altering the current URL
*
* @param router The router used to navigate to a new page
* @param authService Service to check if the user is authenticated
*/
export const redirectOn4xx = <T>(router: Router, authService: AuthService) =>
(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
source.pipe(
withLatestFrom(authService.isAuthenticated()),
filter(([rd, isAuthenticated]: [RemoteData<T>, boolean]) => {
if (rd.hasFailed) {
if (rd.statusCode === 404 || rd.statusCode === 422) {
router.navigateByUrl(getPageNotFoundRoute(), { skipLocationChange: true });
return false;
} else if (rd.statusCode === 403 || rd.statusCode === 401) {
if (isAuthenticated) {
router.navigateByUrl(getForbiddenRoute(), { skipLocationChange: true });
return false;
} else {
authService.setRedirectUrl(router.url);
router.navigateByUrl('login');
return false;
}
}
}
return true;
}),
map(([rd,]: [RemoteData<T>, boolean]) => rd)
);
/**
* Operator that returns a UrlTree to a forbidden page or the login page when the boolean received is false
* @param router The router used to navigate to a forbidden page
* @param authService The AuthService used to determine whether or not the user is logged in
* @param redirectUrl The URL to redirect back to after logging in
*/
export const returnForbiddenUrlTreeOrLoginOnFalse = (router: Router, authService: AuthService, redirectUrl: string) =>
(source: Observable<boolean>): Observable<boolean | UrlTree> =>
source.pipe(
map((authorized) => [authorized]),
returnForbiddenUrlTreeOrLoginOnAllFalse(router, authService, redirectUrl),
);
/**
* Operator that returns a UrlTree to a forbidden page or the login page when the booleans received are all false
* @param router The router used to navigate to a forbidden page
* @param authService The AuthService used to determine whether or not the user is logged in
* @param redirectUrl The URL to redirect back to after logging in
*/
export const returnForbiddenUrlTreeOrLoginOnAllFalse = (router: Router, authService: AuthService, redirectUrl: string) =>
(source: Observable<boolean[]>): Observable<boolean | UrlTree> =>
observableCombineLatest(source, authService.isAuthenticated()).pipe(
map(([authorizedList, authenticated]: [boolean[], boolean]) => {
if (authorizedList.some((b: boolean) => b === true)) {
return true;
} else {
if (authenticated) {
return router.parseUrl(getForbiddenRoute());
} else {
authService.setRedirectUrl(redirectUrl);
return router.parseUrl('login');
}
}
}));
/**
* Operator that returns a UrlTree to the unauthorized page when the boolean received is false
* @param router Router
* @param redirect Redirect URL to add to the UrlTree. This is used to redirect back to the original route after the
* user accepts the agreement.
*/
export const returnEndUserAgreementUrlTreeOnFalse = (router: Router, redirect: string) =>
(source: Observable<boolean>): Observable<boolean | UrlTree> =>
source.pipe(
map((hasAgreed: boolean) => {
const queryParams = { redirect: encodeURIComponent(redirect) };
return hasAgreed ? hasAgreed : router.createUrlTree([getEndUserAgreementPath()], { queryParams });
}));

View File

@@ -1,12 +1,12 @@
import { autoserialize, deserialize, deserializeAs } from 'cerialize';
import { typedObject } from '../cache/builders/build-decorators';
import { IDToUUIDSerializer } from '../cache/id-to-uuid-serializer';
import { CacheableObject } from '../cache/object-cache.reducer';
import { excludeFromEquals } from '../utilities/equals.decorators';
import { BitstreamFormatSupportLevel } from './bitstream-format-support-level';
import { BITSTREAM_FORMAT } from './bitstream-format.resource-type';
import { HALLink } from './hal-link.model';
import { ResourceType } from './resource-type';
import { CacheableObject } from '../cache/cacheable-object.model';
/**
* Model class for a Bitstream Format

View File

@@ -1,11 +1,11 @@
import { autoserialize, autoserializeAs, deserialize } from 'cerialize';
import { typedObject } from '../cache/builders/build-decorators';
import { CacheableObject } from '../cache/object-cache.reducer';
import { excludeFromEquals } from '../utilities/equals.decorators';
import { BROWSE_DEFINITION } from './browse-definition.resource-type';
import { HALLink } from './hal-link.model';
import { ResourceType } from './resource-type';
import { SortOption } from './sort-option.model';
import { CacheableObject } from '../cache/cacheable-object.model';
@typedObject
export class BrowseDefinition extends CacheableObject {

View File

@@ -1,12 +1,12 @@
import { autoserialize, autoserializeAs, deserialize } from 'cerialize';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { typedObject } from '../cache/builders/build-decorators';
import { TypedObject } from '../cache/object-cache.reducer';
import { excludeFromEquals } from '../utilities/equals.decorators';
import { BROWSE_ENTRY } from './browse-entry.resource-type';
import { GenericConstructor } from './generic-constructor';
import { HALLink } from './hal-link.model';
import { ResourceType } from './resource-type';
import { TypedObject } from '../cache/typed-object.model';
/**
* Class object representing a browse entry

View File

@@ -1,10 +1,10 @@
import { autoserialize, autoserializeAs, deserialize } from 'cerialize';
import { typedObject } from '../cache/builders/build-decorators';
import { CacheableObject } from '../cache/object-cache.reducer';
import { excludeFromEquals } from '../utilities/equals.decorators';
import { HALLink } from './hal-link.model';
import { ResourceType } from './resource-type';
import { CONFIG_PROPERTY } from './config-property.resource-type';
import { CacheableObject } from '../cache/cacheable-object.model';
/**
* Model class for a Configuration Property

View File

@@ -1,11 +1,11 @@
import { autoserializeAs, deserializeAs, deserialize } from 'cerialize';
import { HALLink } from './hal-link.model';
import { MetadataConfig } from './metadata-config.model';
import { CacheableObject } from '../cache/object-cache.reducer';
import { typedObject } from '../cache/builders/build-decorators';
import { CONTENT_SOURCE } from './content-source.resource-type';
import { excludeFromEquals } from '../utilities/equals.decorators';
import { ResourceType } from './resource-type';
import { CacheableObject } from '../cache/cacheable-object.model';
/**
* The type of content harvesting used

View File

@@ -2,7 +2,6 @@ import { autoserialize, autoserializeAs, deserialize, deserializeAs } from 'ceri
import { hasNoValue, hasValue, isUndefined } from '../../shared/empty.util';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { typedObject } from '../cache/builders/build-decorators';
import { CacheableObject } from '../cache/object-cache.reducer';
import { excludeFromEquals } from '../utilities/equals.decorators';
import { DSPACE_OBJECT } from './dspace-object.resource-type';
import { GenericConstructor } from './generic-constructor';
@@ -16,6 +15,7 @@ import {
} from './metadata.models';
import { Metadata } from './metadata.utils';
import { ResourceType } from './resource-type';
import { CacheableObject } from '../cache/cacheable-object.model';
/**
* An abstract model class for a DSpaceObject.

View File

@@ -1,10 +1,10 @@
import { autoserialize, deserialize } from 'cerialize';
import { typedObject } from '../cache/builders/build-decorators';
import { CacheableObject } from '../cache/object-cache.reducer';
import { excludeFromEquals } from '../utilities/equals.decorators';
import { EXTERNAL_SOURCE } from './external-source.resource-type';
import { HALLink } from './hal-link.model';
import { ResourceType } from './resource-type';
import { CacheableObject } from '../cache/cacheable-object.model';
/**
* Model class for an external source

View File

@@ -1,11 +1,11 @@
import { autoserialize, deserialize, deserializeAs } from 'cerialize';
import { typedObject } from '../../cache/builders/build-decorators';
import { IDToUUIDSerializer } from '../../cache/id-to-uuid-serializer';
import { CacheableObject } from '../../cache/object-cache.reducer';
import { excludeFromEquals } from '../../utilities/equals.decorators';
import { HALLink } from '../hal-link.model';
import { ResourceType } from '../resource-type';
import { ITEM_TYPE } from './item-type.resource-type';
import { CacheableObject } from '../../cache/cacheable-object.model';
/**
* Describes a type of Item

View File

@@ -2,7 +2,6 @@ import { autoserialize, deserialize, deserializeAs } from 'cerialize';
import { Observable } from 'rxjs';
import { link, typedObject } from '../../cache/builders/build-decorators';
import { IDToUUIDSerializer } from '../../cache/id-to-uuid-serializer';
import { CacheableObject } from '../../cache/object-cache.reducer';
import { RemoteData } from '../../data/remote-data';
import { excludeFromEquals } from '../../utilities/equals.decorators';
import { HALLink } from '../hal-link.model';
@@ -10,6 +9,7 @@ import { ResourceType } from '../resource-type';
import { ItemType } from './item-type.model';
import { ITEM_TYPE } from './item-type.resource-type';
import { RELATIONSHIP_TYPE } from './relationship-type.resource-type';
import { CacheableObject } from '../../cache/cacheable-object.model';
/**
* Describes a type of Relationship between multiple possible Items

View File

@@ -2,7 +2,6 @@ import { autoserialize, deserialize, deserializeAs } from 'cerialize';
import { Observable } from 'rxjs';
import { link, typedObject } from '../../cache/builders/build-decorators';
import { IDToUUIDSerializer } from '../../cache/id-to-uuid-serializer';
import { CacheableObject } from '../../cache/object-cache.reducer';
import { RemoteData } from '../../data/remote-data';
import { excludeFromEquals } from '../../utilities/equals.decorators';
import { HALLink } from '../hal-link.model';
@@ -12,6 +11,7 @@ import { ResourceType } from '../resource-type';
import { RelationshipType } from './relationship-type.model';
import { RELATIONSHIP_TYPE } from './relationship-type.resource-type';
import { RELATIONSHIP } from './relationship.resource-type';
import { CacheableObject } from '../../cache/cacheable-object.model';
/**
* Describes a Relationship between two Items

View File

@@ -5,20 +5,17 @@ import { GetRequest } from '../data/request.models';
import { RequestEntry } from '../data/request.reducer';
import { RequestService } from '../data/request.service';
import {
sendRequest,
getAllSucceededRemoteData,
getFirstSucceededRemoteData,
getRemoteDataPayload,
getRequestFromRequestHref,
getRequestFromRequestUUID,
getResponseFromEntry,
redirectOn4xx
getRemoteDataPayload
} from './operators';
import { of as observableOf } from 'rxjs';
import {
createFailedRemoteDataObject,
createSuccessfulRemoteDataObject
} from '../../shared/remote-data.utils';
import { getRequestFromRequestHref, getRequestFromRequestUUID, getResponseFromEntry, sendRequest } from './request.operators';
import { redirectOn4xx } from './authorized.operators';
// tslint:disable:no-shadowed-variable

View File

@@ -1,31 +1,13 @@
import { Router, UrlTree } from '@angular/router';
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import {
debounceTime,
filter,
find,
map,
mergeMap,
switchMap,
take,
takeWhile,
tap,
withLatestFrom
} from 'rxjs/operators';
import { debounceTime, filter, find, map, switchMap, take, takeWhile } from 'rxjs/operators';
import { hasNoValue, hasValue, hasValueOperator, isNotEmpty } from '../../shared/empty.util';
import { SearchResult } from '../../shared/search/search-result.model';
import { PaginatedList } from '../data/paginated-list.model';
import { RemoteData } from '../data/remote-data';
import { RestRequest } from '../data/request.models';
import { RequestEntry, ResponseState } from '../data/request.reducer';
import { RequestService } from '../data/request.service';
import { MetadataField } from '../metadata/metadata-field.model';
import { MetadataSchema } from '../metadata/metadata-schema.model';
import { BrowseDefinition } from './browse-definition.model';
import { DSpaceObject } from './dspace-object.model';
import { getForbiddenRoute, getPageNotFoundRoute } from '../../app-routing-paths';
import { getEndUserAgreementPath } from '../../info/info-routing-paths';
import { AuthService } from '../auth/auth.service';
import { InjectionToken } from '@angular/core';
export const DEBOUNCE_TIME_OPERATOR = new InjectionToken<<T>(dueTime: number) => (source: Observable<T>) => Observable<T>>('debounceTime', {
@@ -33,40 +15,6 @@ export const DEBOUNCE_TIME_OPERATOR = new InjectionToken<<T>(dueTime: number) =>
factory: () => debounceTime
});
export const REDIRECT_ON_4XX = new InjectionToken<<T>(router: Router, authService: AuthService) => (source: Observable<RemoteData<T>>) => Observable<RemoteData<T>>>('redirectOn4xx', {
providedIn: 'root',
factory: () => redirectOn4xx
});
/**
* This file contains custom RxJS operators that can be used in multiple places
*/
export const getRequestFromRequestHref = (requestService: RequestService) =>
(source: Observable<string>): Observable<RequestEntry> =>
source.pipe(
mergeMap((href: string) => requestService.getByHref(href)),
hasValueOperator()
);
export const getRequestFromRequestUUID = (requestService: RequestService) =>
(source: Observable<string>): Observable<RequestEntry> =>
source.pipe(
mergeMap((uuid: string) => requestService.getByUUID(uuid)),
hasValueOperator()
);
export const getResponseFromEntry = () =>
(source: Observable<RequestEntry>): Observable<ResponseState> =>
source.pipe(
filter((entry: RequestEntry) => hasValue(entry) && hasValue(entry.response)),
map((entry: RequestEntry) => entry.response)
);
export const sendRequest = (requestService: RequestService) =>
(source: Observable<RestRequest>): Observable<RestRequest> =>
source.pipe(tap((request: RestRequest) => requestService.send(request)));
export const getRemoteDataPayload = <T>() =>
(source: Observable<RemoteData<T>>): Observable<T> =>
source.pipe(map((remoteData: RemoteData<T>) => remoteData.payload));
@@ -190,88 +138,6 @@ export const getAllSucceededRemoteListPayload = <T>() =>
getPaginatedListPayload()
);
/**
* Operator that checks if a remote data object returned a 4xx error
* When it does contain such an error, it will redirect the user to the related error page, without
* altering the current URL
*
* @param router The router used to navigate to a new page
* @param authService Service to check if the user is authenticated
*/
export const redirectOn4xx = <T>(router: Router, authService: AuthService) =>
(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
source.pipe(
withLatestFrom(authService.isAuthenticated()),
filter(([rd, isAuthenticated]: [RemoteData<T>, boolean]) => {
if (rd.hasFailed) {
if (rd.statusCode === 404 || rd.statusCode === 422) {
router.navigateByUrl(getPageNotFoundRoute(), { skipLocationChange: true });
return false;
} else if (rd.statusCode === 403 || rd.statusCode === 401) {
if (isAuthenticated) {
router.navigateByUrl(getForbiddenRoute(), { skipLocationChange: true });
return false;
} else {
authService.setRedirectUrl(router.url);
router.navigateByUrl('login');
return false;
}
}
}
return true;
}),
map(([rd,]: [RemoteData<T>, boolean]) => rd)
);
/**
* Operator that returns a UrlTree to a forbidden page or the login page when the boolean received is false
* @param router The router used to navigate to a forbidden page
* @param authService The AuthService used to determine whether or not the user is logged in
* @param redirectUrl The URL to redirect back to after logging in
*/
export const returnForbiddenUrlTreeOrLoginOnFalse = (router: Router, authService: AuthService, redirectUrl: string) =>
(source: Observable<boolean>): Observable<boolean | UrlTree> =>
source.pipe(
map((authorized) => [authorized]),
returnForbiddenUrlTreeOrLoginOnAllFalse(router, authService, redirectUrl),
);
/**
* Operator that returns a UrlTree to a forbidden page or the login page when the booleans received are all false
* @param router The router used to navigate to a forbidden page
* @param authService The AuthService used to determine whether or not the user is logged in
* @param redirectUrl The URL to redirect back to after logging in
*/
export const returnForbiddenUrlTreeOrLoginOnAllFalse = (router: Router, authService: AuthService, redirectUrl: string) =>
(source: Observable<boolean[]>): Observable<boolean | UrlTree> =>
observableCombineLatest(source, authService.isAuthenticated()).pipe(
map(([authorizedList, authenticated]: [boolean[], boolean]) => {
if (authorizedList.some((b: boolean) => b === true)) {
return true;
} else {
if (authenticated) {
return router.parseUrl(getForbiddenRoute());
} else {
authService.setRedirectUrl(redirectUrl);
return router.parseUrl('login');
}
}
}));
/**
* Operator that returns a UrlTree to the unauthorized page when the boolean received is false
* @param router Router
* @param redirect Redirect URL to add to the UrlTree. This is used to redirect back to the original route after the
* user accepts the agreement.
*/
export const returnEndUserAgreementUrlTreeOnFalse = (router: Router, redirect: string) =>
(source: Observable<boolean>): Observable<boolean | UrlTree> =>
source.pipe(
map((hasAgreed: boolean) => {
const queryParams = { redirect: encodeURIComponent(redirect) };
return hasAgreed ? hasAgreed : router.createUrlTree([getEndUserAgreementPath()], { queryParams });
}));
export const getFinishedRemoteData = <T>() =>
(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
source.pipe(find((rd: RemoteData<T>) => !rd.isLoading));

View File

@@ -0,0 +1,32 @@
import { RequestService } from '../data/request.service';
import { Observable } from 'rxjs';
import { RestRequest } from '../data/request.models';
import { filter, map, mergeMap, tap } from 'rxjs/operators';
import { RequestEntry, ResponseState } from '../data/request.reducer';
import { hasValue, hasValueOperator } from '../../shared/empty.util';
/**
* This file contains custom RxJS operators that can be used in multiple places
*/
export const getRequestFromRequestHref = (requestService: RequestService) =>
(source: Observable<string>): Observable<RequestEntry> =>
source.pipe(
mergeMap((href: string) => requestService.getByHref(href)),
hasValueOperator()
);
export const getRequestFromRequestUUID = (requestService: RequestService) =>
(source: Observable<string>): Observable<RequestEntry> =>
source.pipe(
mergeMap((uuid: string) => requestService.getByUUID(uuid)),
hasValueOperator()
);
export const getResponseFromEntry = () =>
(source: Observable<RequestEntry>): Observable<ResponseState> =>
source.pipe(
filter((entry: RequestEntry) => hasValue(entry) && hasValue(entry.response)),
map((entry: RequestEntry) => entry.response)
);
export const sendRequest = (requestService: RequestService) =>
(source: Observable<RestRequest>): Observable<RestRequest> =>
source.pipe(tap((request: RestRequest) => requestService.send(request)));

View File

@@ -4,8 +4,16 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
import { SearchFilter } from '../../../shared/search/search-filter.model';
import { of as observableOf } from 'rxjs';
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
import { HALEndpointServiceStub } from '../../../shared/testing/hal-endpoint-service.stub';
import { getMockRemoteDataBuildService } from '../../../shared/mocks/remote-data-build.service.mock';
import { RequestEntry } from '../../data/request.reducer';
import { map } from 'rxjs/operators';
import { RemoteData } from '../../data/remote-data';
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
import { SearchObjects } from '../../../shared/search/search-objects.model';
import { getMockRequestService } from '../../../shared/mocks/request.service.mock';
describe('SearchConfigurationService', () => {
let service: SearchConfigurationService;
@@ -38,10 +46,37 @@ describe('SearchConfigurationService', () => {
const activatedRoute: any = new ActivatedRouteStub();
const linkService: any = {};
const requestService: any = getMockRequestService();
const halService: any = {
/* tslint:disable:no-empty */
getEndpoint: () => {
}
/* tslint:enable:no-empty */
};
const rdb: any = {
toRemoteDataObservable: (requestEntryObs: Observable<RequestEntry>, payloadObs: Observable<any>) => {
return observableCombineLatest([requestEntryObs, payloadObs]).pipe(
map(([req, pay]) => {
return { req, pay };
})
);
},
aggregate: (input: Observable<RemoteData<any>>[]): Observable<RemoteData<any[]>> => {
return createSuccessfulRemoteDataObject$([]);
},
buildFromHref: (href: string): Observable<RemoteData<any>> => {
return createSuccessfulRemoteDataObject$(Object.assign(new SearchObjects(), {
page: []
}));
}
};
beforeEach(() => {
service = new SearchConfigurationService(routeService, paginationService as any, activatedRoute);
service = new SearchConfigurationService(routeService, paginationService as any, activatedRoute, linkService, halService, requestService, rdb);
});
describe('when the scope is called', () => {
beforeEach(() => {
service.getCurrentScope('');
@@ -160,4 +195,100 @@ describe('SearchConfigurationService', () => {
});
});
describe('when getSearchConfigurationFor is called with a scope', () => {
const endPoint = 'http://endpoint.com/test/config';
const scope = 'test';
const requestUrl = endPoint + '?scope=' + scope;
beforeEach(() => {
spyOn((service as any).halService, 'getEndpoint').and.returnValue(observableOf(endPoint));
/* tslint:disable:no-empty */
service.getSearchConfigurationFor(scope).subscribe((t) => {
}); // subscribe to make sure all methods are called
/* tslint:enable:no-empty */
});
it('should call getEndpoint on the halService', () => {
expect((service as any).halService.getEndpoint).toHaveBeenCalled();
});
it('should send out the request on the request service', () => {
expect((service as any).requestService.send).toHaveBeenCalled();
});
it('should call send containing a request with the correct request url', () => {
expect((service as any).requestService.send).toHaveBeenCalledWith(jasmine.objectContaining({ href: requestUrl }), true);
});
});
describe('when getSearchConfigurationFor is called without a scope', () => {
const endPoint = 'http://endpoint.com/test/config';
beforeEach(() => {
spyOn((service as any).halService, 'getEndpoint').and.returnValue(observableOf(endPoint));
spyOn((service as any).rdb, 'buildFromHref').and.callThrough();
/* tslint:disable:no-empty */
service.getSearchConfigurationFor(null).subscribe((t) => {
}); // subscribe to make sure all methods are called
/* tslint:enable:no-empty */
});
it('should call getEndpoint on the halService', () => {
expect((service as any).halService.getEndpoint).toHaveBeenCalled();
});
it('should send out the request on the request service', () => {
expect((service as any).requestService.send).toHaveBeenCalled();
});
it('should call send containing a request with the correct request url', () => {
expect((service as any).requestService.send).toHaveBeenCalledWith(jasmine.objectContaining({ href: endPoint }), true);
});
});
describe('when getConfig is called without a scope', () => {
const endPoint = 'http://endpoint.com/test/config';
beforeEach(() => {
spyOn((service as any).halService, 'getEndpoint').and.returnValue(observableOf(endPoint));
spyOn((service as any).rdb, 'buildFromHref').and.callThrough();
/* tslint:disable:no-empty */
service.getConfig(null).subscribe((t) => {
}); // subscribe to make sure all methods are called
/* tslint:enable:no-empty */
});
it('should call getEndpoint on the halService', () => {
expect((service as any).halService.getEndpoint).toHaveBeenCalled();
});
it('should send out the request on the request service', () => {
expect((service as any).requestService.send).toHaveBeenCalled();
});
it('should call send containing a request with the correct request url', () => {
expect((service as any).requestService.send).toHaveBeenCalledWith(jasmine.objectContaining({ href: endPoint }), true);
});
});
describe('when getConfig is called with a scope', () => {
const endPoint = 'http://endpoint.com/test/config';
const scope = 'test';
const requestUrl = endPoint + '?scope=' + scope;
beforeEach(() => {
spyOn((service as any).halService, 'getEndpoint').and.returnValue(observableOf(endPoint));
/* tslint:disable:no-empty */
service.getConfig(scope).subscribe((t) => {
}); // subscribe to make sure all methods are called
/* tslint:enable:no-empty */
});
it('should call getEndpoint on the halService', () => {
expect((service as any).halService.getEndpoint).toHaveBeenCalled();
});
it('should send out the request on the request service', () => {
expect((service as any).requestService.send).toHaveBeenCalled();
});
it('should call send containing a request with the correct request url', () => {
expect((service as any).requestService.send).toHaveBeenCalledWith(jasmine.objectContaining({ href: requestUrl }), true);
});
});
});

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