mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
87382: fixing circular dependencies
This commit is contained in:
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
}));
|
||||
|
||||
|
@@ -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: [
|
||||
|
@@ -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',
|
||||
|
@@ -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');
|
||||
|
@@ -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
|
||||
|
@@ -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.
|
||||
|
@@ -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>;
|
||||
|
@@ -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[]> =>
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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.
|
||||
|
22
src/app/community-list-page/flat-node.model.ts
Normal file
22
src/app/community-list-page/flat-node.model.ts
Normal 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;
|
||||
}
|
6
src/app/community-list-page/show-more-flat-node.model.ts
Normal file
6
src/app/community-list-page/show-more-flat-node.model.ts
Normal 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 {
|
||||
}
|
@@ -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: [
|
||||
|
@@ -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',
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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',
|
||||
|
@@ -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;
|
||||
|
@@ -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 {
|
||||
|
22
src/app/core/cache/cacheable-object.model.ts
vendored
Normal file
22
src/app/core/cache/cacheable-object.model.ts
vendored
Normal 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;
|
||||
}
|
2
src/app/core/cache/object-cache.actions.ts
vendored
2
src/app/core/cache/object-cache.actions.ts
vendored
@@ -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
|
||||
|
36
src/app/core/cache/object-cache.reducer.ts
vendored
36
src/app/core/cache/object-cache.reducer.ts
vendored
@@ -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
|
||||
*/
|
||||
|
@@ -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;
|
||||
|
5
src/app/core/cache/object-cache.service.ts
vendored
5
src/app/core/cache/object-cache.service.ts
vendored
@@ -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
|
||||
|
2
src/app/core/cache/response.models.ts
vendored
2
src/app/core/cache/response.models.ts
vendored
@@ -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 {
|
||||
|
6
src/app/core/cache/typed-object.model.ts
vendored
Normal file
6
src/app/core/cache/typed-object.model.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import { ResourceType } from '../shared/resource-type';
|
||||
|
||||
export abstract class TypedObject {
|
||||
static type: ResourceType;
|
||||
type: ResourceType;
|
||||
}
|
@@ -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 {
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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 */
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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 = {
|
||||
|
@@ -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>>> {
|
||||
|
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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';
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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 */
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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> {
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
}
|
10
src/app/core/data/object-updates/field-update.model.ts
Normal file
10
src/app/core/data/object-updates/field-update.model.ts
Normal 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;
|
||||
}
|
8
src/app/core/data/object-updates/field-updates.model.ts
Normal file
8
src/app/core/data/object-updates/field-updates.model.ts
Normal 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;
|
||||
}
|
6
src/app/core/data/object-updates/identifiable.model.ts
Normal file
6
src/app/core/data/object-updates/identifiable.model.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Represents every object that has a UUID
|
||||
*/
|
||||
export interface Identifiable {
|
||||
uuid: string;
|
||||
}
|
@@ -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
|
||||
*/
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
*/
|
||||
|
@@ -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;
|
||||
|
@@ -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']);
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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
|
||||
|
88
src/app/core/data/request-entry-state.model.ts
Normal file
88
src/app/core/data/request-entry-state.model.ts
Normal 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);
|
4
src/app/core/data/request-error.model.ts
Normal file
4
src/app/core/data/request-error.model.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export class RequestError extends Error {
|
||||
statusCode: number;
|
||||
statusText: string;
|
||||
}
|
@@ -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 {
|
||||
|
@@ -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 */
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
17
src/app/core/index/index-name.model.ts
Normal file
17
src/app/core/index/index-name.model.ts
Normal 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'
|
||||
}
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
|
91
src/app/core/shared/authorized.operators.ts
Normal file
91
src/app/core/shared/authorized.operators.ts
Normal 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 });
|
||||
}));
|
@@ -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
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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));
|
||||
|
32
src/app/core/shared/request.operators.ts
Normal file
32
src/app/core/shared/request.operators.ts
Normal 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)));
|
@@ -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
Reference in New Issue
Block a user