mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-09 19:13:08 +00:00
Merge branch 'main' into CST-12109-WITHDRAWN-REINSTATE-requests
This commit is contained in:
@@ -75,7 +75,7 @@ cache:
|
|||||||
anonymousCache:
|
anonymousCache:
|
||||||
# Maximum number of pages to cache. Default is zero (0) which means anonymous user cache is disabled.
|
# Maximum number of pages to cache. Default is zero (0) which means anonymous user cache is disabled.
|
||||||
# As all pages are cached in server memory, increasing this value will increase memory needs.
|
# As all pages are cached in server memory, increasing this value will increase memory needs.
|
||||||
# Individual cached pages are usually small (<100KB), so a value of max=1000 would only require ~100MB of memory.
|
# Individual cached pages are usually small (<100KB), so a value of max=1000 would only require ~100MB of memory.
|
||||||
max: 0
|
max: 0
|
||||||
# Amount of time after which cached pages are considered stale (in ms). After becoming stale, the cached
|
# Amount of time after which cached pages are considered stale (in ms). After becoming stale, the cached
|
||||||
# copy is automatically refreshed on the next request.
|
# copy is automatically refreshed on the next request.
|
||||||
@@ -382,7 +382,13 @@ vocabularies:
|
|||||||
vocabulary: 'srsc'
|
vocabulary: 'srsc'
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
# Default collection/community sorting order at Advanced search, Create/update community and collection when there are not a query.
|
# Default collection/community sorting order at Advanced search, Create/update community and collection when there are not a query.
|
||||||
comcolSelectionSort:
|
comcolSelectionSort:
|
||||||
sortField: 'dc.title'
|
sortField: 'dc.title'
|
||||||
sortDirection: 'ASC'
|
sortDirection: 'ASC'
|
||||||
|
|
||||||
|
# Example of fallback collection for suggestions import
|
||||||
|
# suggestion:
|
||||||
|
# - collectionId: 8f7df5ca-f9c2-47a4-81ec-8a6393d6e5af
|
||||||
|
# source: "openaire"
|
||||||
|
|
||||||
|
@@ -5,9 +5,9 @@ describe('Browse By Author', () => {
|
|||||||
cy.visit('/browse/author');
|
cy.visit('/browse/author');
|
||||||
|
|
||||||
// Wait for <ds-browse-by-metadata-page> to be visible
|
// Wait for <ds-browse-by-metadata-page> to be visible
|
||||||
cy.get('ds-browse-by-metadata-page').should('be.visible');
|
cy.get('ds-browse-by-metadata').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-browse-by-metadata-page> for accessibility
|
// Analyze <ds-browse-by-metadata-page> for accessibility
|
||||||
testA11y('ds-browse-by-metadata-page');
|
testA11y('ds-browse-by-metadata');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -5,9 +5,9 @@ describe('Browse By Date Issued', () => {
|
|||||||
cy.visit('/browse/dateissued');
|
cy.visit('/browse/dateissued');
|
||||||
|
|
||||||
// Wait for <ds-browse-by-date-page> to be visible
|
// Wait for <ds-browse-by-date-page> to be visible
|
||||||
cy.get('ds-browse-by-date-page').should('be.visible');
|
cy.get('ds-browse-by-date').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-browse-by-date-page> for accessibility
|
// Analyze <ds-browse-by-date-page> for accessibility
|
||||||
testA11y('ds-browse-by-date-page');
|
testA11y('ds-browse-by-date');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -5,9 +5,9 @@ describe('Browse By Subject', () => {
|
|||||||
cy.visit('/browse/subject');
|
cy.visit('/browse/subject');
|
||||||
|
|
||||||
// Wait for <ds-browse-by-metadata-page> to be visible
|
// Wait for <ds-browse-by-metadata-page> to be visible
|
||||||
cy.get('ds-browse-by-metadata-page').should('be.visible');
|
cy.get('ds-browse-by-metadata').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-browse-by-metadata-page> for accessibility
|
// Analyze <ds-browse-by-metadata-page> for accessibility
|
||||||
testA11y('ds-browse-by-metadata-page');
|
testA11y('ds-browse-by-metadata');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -5,9 +5,9 @@ describe('Browse By Title', () => {
|
|||||||
cy.visit('/browse/title');
|
cy.visit('/browse/title');
|
||||||
|
|
||||||
// Wait for <ds-browse-by-title-page> to be visible
|
// Wait for <ds-browse-by-title-page> to be visible
|
||||||
cy.get('ds-browse-by-title-page').should('be.visible');
|
cy.get('ds-browse-by-title').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-browse-by-title-page> for accessibility
|
// Analyze <ds-browse-by-title-page> for accessibility
|
||||||
testA11y('ds-browse-by-title-page');
|
testA11y('ds-browse-by-title');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -0,0 +1,32 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for the route parameters.
|
||||||
|
*/
|
||||||
|
export interface AdminNotificationsPublicationClaimPageParams {
|
||||||
|
pageId?: string;
|
||||||
|
pageSize?: number;
|
||||||
|
currentPage?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a resolver that retrieve the route data before the route is activated.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AdminNotificationsPublicationClaimPageResolver implements Resolve<AdminNotificationsPublicationClaimPageParams> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method for resolving the parameters in the current route.
|
||||||
|
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||||
|
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||||
|
* @returns AdminNotificationsSuggestionTargetsPageParams Emits the route parameters
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): AdminNotificationsPublicationClaimPageParams {
|
||||||
|
return {
|
||||||
|
pageId: route.queryParams.pageId,
|
||||||
|
pageSize: parseInt(route.queryParams.pageSize, 10),
|
||||||
|
currentPage: parseInt(route.queryParams.page, 10)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1 @@
|
|||||||
|
<ds-publication-claim [source]="'openaire'"></ds-publication-claim>
|
@@ -0,0 +1,38 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AdminNotificationsPublicationClaimPageComponent } from './admin-notifications-publication-claim-page.component';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
describe('AdminNotificationsPublicationClaimPageComponent', () => {
|
||||||
|
let component: AdminNotificationsPublicationClaimPageComponent;
|
||||||
|
let fixture: ComponentFixture<AdminNotificationsPublicationClaimPageComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
AdminNotificationsPublicationClaimPageComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
AdminNotificationsPublicationClaimPageComponent
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AdminNotificationsPublicationClaimPageComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,10 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-admin-notifications-publication-claim-page',
|
||||||
|
templateUrl: './admin-notifications-publication-claim-page.component.html',
|
||||||
|
styleUrls: ['./admin-notifications-publication-claim-page.component.scss']
|
||||||
|
})
|
||||||
|
export class AdminNotificationsPublicationClaimPageComponent {
|
||||||
|
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
export const QUALITY_ASSURANCE_EDIT_PATH = 'quality-assurance';
|
export const QUALITY_ASSURANCE_EDIT_PATH = 'quality-assurance';
|
||||||
|
export const PUBLICATION_CLAIMS_PATH = 'publication-claim';
|
||||||
|
|
||||||
export function getQualityAssuranceEditRoute() {
|
export function getQualityAssuranceEditRoute() {
|
||||||
return `/${QUALITY_ASSURANCE_EDIT_PATH}`;
|
return `/${QUALITY_ASSURANCE_EDIT_PATH}`;
|
||||||
|
@@ -4,6 +4,9 @@ import { RouterModule } from '@angular/router';
|
|||||||
import { AuthenticatedGuard } from '../../core/auth/authenticated.guard';
|
import { AuthenticatedGuard } from '../../core/auth/authenticated.guard';
|
||||||
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||||
import { I18nBreadcrumbsService } from '../../core/breadcrumbs/i18n-breadcrumbs.service';
|
import { I18nBreadcrumbsService } from '../../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||||
|
import { PUBLICATION_CLAIMS_PATH } from './admin-notifications-routing-paths';
|
||||||
|
import { AdminNotificationsPublicationClaimPageComponent } from './admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component';
|
||||||
|
import { AdminNotificationsPublicationClaimPageResolver } from './admin-notifications-publication-claim-page/admin-notifications-publication-claim-page-resolver.service';
|
||||||
import { QUALITY_ASSURANCE_EDIT_PATH } from './admin-notifications-routing-paths';
|
import { QUALITY_ASSURANCE_EDIT_PATH } from './admin-notifications-routing-paths';
|
||||||
import { AdminQualityAssuranceTopicsPageComponent } from './admin-quality-assurance-topics-page/admin-quality-assurance-topics-page.component';
|
import { AdminQualityAssuranceTopicsPageComponent } from './admin-quality-assurance-topics-page/admin-quality-assurance-topics-page.component';
|
||||||
import { AdminQualityAssuranceEventsPageComponent } from './admin-quality-assurance-events-page/admin-quality-assurance-events-page.component';
|
import { AdminQualityAssuranceEventsPageComponent } from './admin-quality-assurance-events-page/admin-quality-assurance-events-page.component';
|
||||||
@@ -23,6 +26,21 @@ import {
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forChild([
|
RouterModule.forChild([
|
||||||
|
{
|
||||||
|
canActivate: [ AuthenticatedGuard ],
|
||||||
|
path: `${PUBLICATION_CLAIMS_PATH}`,
|
||||||
|
component: AdminNotificationsPublicationClaimPageComponent,
|
||||||
|
pathMatch: 'full',
|
||||||
|
resolve: {
|
||||||
|
breadcrumb: I18nBreadcrumbResolver,
|
||||||
|
suggestionTargetParams: AdminNotificationsPublicationClaimPageResolver
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: 'admin.notifications.publicationclaim.page.title',
|
||||||
|
breadcrumbKey: 'admin.notifications.publicationclaim',
|
||||||
|
showBreadcrumbsFluid: false
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
canActivate: [ AuthenticatedGuard ],
|
canActivate: [ AuthenticatedGuard ],
|
||||||
path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId`,
|
path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId`,
|
||||||
@@ -74,7 +92,9 @@ import {
|
|||||||
providers: [
|
providers: [
|
||||||
I18nBreadcrumbResolver,
|
I18nBreadcrumbResolver,
|
||||||
I18nBreadcrumbsService,
|
I18nBreadcrumbsService,
|
||||||
|
AdminNotificationsPublicationClaimPageResolver,
|
||||||
SourceDataResolver,
|
SourceDataResolver,
|
||||||
|
AdminQualityAssuranceSourcePageResolver,
|
||||||
AdminQualityAssuranceTopicsPageResolver,
|
AdminQualityAssuranceTopicsPageResolver,
|
||||||
AdminQualityAssuranceEventsPageResolver,
|
AdminQualityAssuranceEventsPageResolver,
|
||||||
AdminQualityAssuranceSourcePageResolver,
|
AdminQualityAssuranceSourcePageResolver,
|
||||||
|
@@ -3,10 +3,11 @@ import { NgModule } from '@angular/core';
|
|||||||
import { CoreModule } from '../../core/core.module';
|
import { CoreModule } from '../../core/core.module';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
import { AdminNotificationsRoutingModule } from './admin-notifications-routing.module';
|
import { AdminNotificationsRoutingModule } from './admin-notifications-routing.module';
|
||||||
|
import { AdminNotificationsPublicationClaimPageComponent } from './admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component';
|
||||||
import { AdminQualityAssuranceTopicsPageComponent } from './admin-quality-assurance-topics-page/admin-quality-assurance-topics-page.component';
|
import { AdminQualityAssuranceTopicsPageComponent } from './admin-quality-assurance-topics-page/admin-quality-assurance-topics-page.component';
|
||||||
import { AdminQualityAssuranceEventsPageComponent } from './admin-quality-assurance-events-page/admin-quality-assurance-events-page.component';
|
import { AdminQualityAssuranceEventsPageComponent } from './admin-quality-assurance-events-page/admin-quality-assurance-events-page.component';
|
||||||
import { AdminQualityAssuranceSourcePageComponent } from './admin-quality-assurance-source-page-component/admin-quality-assurance-source-page.component';
|
import { AdminQualityAssuranceSourcePageComponent } from './admin-quality-assurance-source-page-component/admin-quality-assurance-source-page.component';
|
||||||
import {NotificationsModule} from '../../notifications/notifications.module';
|
import { NotificationsModule } from '../../notifications/notifications.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -17,6 +18,7 @@ import {NotificationsModule} from '../../notifications/notifications.module';
|
|||||||
NotificationsModule
|
NotificationsModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
AdminNotificationsPublicationClaimPageComponent,
|
||||||
AdminQualityAssuranceTopicsPageComponent,
|
AdminQualityAssuranceTopicsPageComponent,
|
||||||
AdminQualityAssuranceEventsPageComponent,
|
AdminQualityAssuranceEventsPageComponent,
|
||||||
AdminQualityAssuranceSourcePageComponent
|
AdminQualityAssuranceSourcePageComponent
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, NoPreloading } from '@angular/router';
|
import { NoPreloading, RouterModule } from '@angular/router';
|
||||||
import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
|
import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
|
||||||
|
|
||||||
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
|
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
|
||||||
@@ -35,9 +35,11 @@ import {
|
|||||||
ThemedPageInternalServerErrorComponent
|
ThemedPageInternalServerErrorComponent
|
||||||
} from './page-internal-server-error/themed-page-internal-server-error.component';
|
} from './page-internal-server-error/themed-page-internal-server-error.component';
|
||||||
import { ServerCheckGuard } from './core/server-check/server-check.guard';
|
import { ServerCheckGuard } from './core/server-check/server-check.guard';
|
||||||
|
import { SUGGESTION_MODULE_PATH } from './suggestions-page/suggestions-page-routing-paths';
|
||||||
import { MenuResolver } from './menu.resolver';
|
import { MenuResolver } from './menu.resolver';
|
||||||
import { ThemedPageErrorComponent } from './page-error/themed-page-error.component';
|
import { ThemedPageErrorComponent } from './page-error/themed-page-error.component';
|
||||||
import { NOTIFICATIONS_MODULE_PATH } from './admin/admin-routing-paths';
|
import { NOTIFICATIONS_MODULE_PATH } from './admin/admin-routing-paths';
|
||||||
|
import { ForgotPasswordCheckGuard } from './core/rest-property/forgot-password-check-guard.guard';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -92,7 +94,10 @@ import { NOTIFICATIONS_MODULE_PATH } from './admin/admin-routing-paths';
|
|||||||
path: FORGOT_PASSWORD_PATH,
|
path: FORGOT_PASSWORD_PATH,
|
||||||
loadChildren: () => import('./forgot-password/forgot-password.module')
|
loadChildren: () => import('./forgot-password/forgot-password.module')
|
||||||
.then((m) => m.ForgotPasswordModule),
|
.then((m) => m.ForgotPasswordModule),
|
||||||
canActivate: [EndUserAgreementCurrentUserGuard]
|
canActivate: [
|
||||||
|
ForgotPasswordCheckGuard,
|
||||||
|
EndUserAgreementCurrentUserGuard
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: COMMUNITY_MODULE_PATH,
|
path: COMMUNITY_MODULE_PATH,
|
||||||
@@ -206,6 +211,11 @@ import { NOTIFICATIONS_MODULE_PATH } from './admin/admin-routing-paths';
|
|||||||
.then((m) => m.ProcessPageModule),
|
.then((m) => m.ProcessPageModule),
|
||||||
canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard]
|
canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard]
|
||||||
},
|
},
|
||||||
|
{ path: SUGGESTION_MODULE_PATH,
|
||||||
|
loadChildren: () => import('./suggestions-page/suggestions-page.module')
|
||||||
|
.then((m) => m.SuggestionsPageModule),
|
||||||
|
canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: INFO_MODULE_PATH,
|
path: INFO_MODULE_PATH,
|
||||||
loadChildren: () => import('./info/info.module').then((m) => m.InfoModule)
|
loadChildren: () => import('./info/info.module').then((m) => m.InfoModule)
|
||||||
|
@@ -1,12 +0,0 @@
|
|||||||
:host {
|
|
||||||
::ng-deep {
|
|
||||||
.switch {
|
|
||||||
position: absolute;
|
|
||||||
top: calc(var(--bs-spacer) * 2.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:host ::ng-deep ds-dynamic-form-control-container > div > label {
|
|
||||||
margin-top: 1.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@@ -2,14 +2,35 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnIni
|
|||||||
import { Bitstream } from '../../core/shared/bitstream.model';
|
import { Bitstream } from '../../core/shared/bitstream.model';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { filter, map, switchMap, tap } from 'rxjs/operators';
|
import { filter, map, switchMap, tap } from 'rxjs/operators';
|
||||||
import { combineLatest, combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
|
import {
|
||||||
import { DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, DynamicFormService, DynamicInputModel, DynamicSelectModel } from '@ng-dynamic-forms/core';
|
combineLatest,
|
||||||
|
combineLatest as observableCombineLatest,
|
||||||
|
Observable,
|
||||||
|
of as observableOf,
|
||||||
|
Subscription
|
||||||
|
} from 'rxjs';
|
||||||
|
import {
|
||||||
|
DynamicFormControlModel,
|
||||||
|
DynamicFormGroupModel,
|
||||||
|
DynamicFormLayout,
|
||||||
|
DynamicFormService,
|
||||||
|
DynamicInputModel,
|
||||||
|
DynamicSelectModel
|
||||||
|
} from '@ng-dynamic-forms/core';
|
||||||
import { UntypedFormGroup } from '@angular/forms';
|
import { UntypedFormGroup } from '@angular/forms';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { DynamicCustomSwitchModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
|
import {
|
||||||
|
DynamicCustomSwitchModel
|
||||||
|
} from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
||||||
import { getAllSucceededRemoteDataPayload, getFirstCompletedRemoteData, getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload, getRemoteDataPayload } from '../../core/shared/operators';
|
import {
|
||||||
|
getAllSucceededRemoteDataPayload,
|
||||||
|
getFirstCompletedRemoteData,
|
||||||
|
getFirstSucceededRemoteData,
|
||||||
|
getFirstSucceededRemoteDataPayload,
|
||||||
|
getRemoteDataPayload
|
||||||
|
} from '../../core/shared/operators';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { BitstreamFormatDataService } from '../../core/data/bitstream-format-data.service';
|
import { BitstreamFormatDataService } from '../../core/data/bitstream-format-data.service';
|
||||||
import { BitstreamFormat } from '../../core/shared/bitstream-format.model';
|
import { BitstreamFormat } from '../../core/shared/bitstream-format.model';
|
||||||
@@ -245,7 +266,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* All input models in a simple array for easier iterations
|
* All input models in a simple array for easier iterations
|
||||||
*/
|
*/
|
||||||
inputModels = [this.fileNameModel, this.primaryBitstreamModel, this.descriptionModel, this.selectedFormatModel,
|
inputModels = [this.primaryBitstreamModel, this.fileNameModel, this.descriptionModel, this.selectedFormatModel,
|
||||||
this.newFormatModel];
|
this.newFormatModel];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -256,8 +277,8 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
new DynamicFormGroupModel({
|
new DynamicFormGroupModel({
|
||||||
id: 'fileNamePrimaryContainer',
|
id: 'fileNamePrimaryContainer',
|
||||||
group: [
|
group: [
|
||||||
this.fileNameModel,
|
this.primaryBitstreamModel,
|
||||||
this.primaryBitstreamModel
|
this.fileNameModel
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
grid: {
|
grid: {
|
||||||
@@ -295,7 +316,10 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
},
|
},
|
||||||
primaryBitstream: {
|
primaryBitstream: {
|
||||||
grid: {
|
grid: {
|
||||||
host: 'col col-sm-4 d-inline-block switch border-0'
|
container: 'col-12'
|
||||||
|
},
|
||||||
|
element: {
|
||||||
|
container: 'text-right'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { BrowseByDatePageComponent } from './browse-by-date-page.component';
|
import { BrowseByDateComponent } from './browse-by-date.component';
|
||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
@@ -15,7 +15,7 @@ import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
|
|||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
|
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
|
||||||
import { toRemoteData } from '../browse-by-metadata-page/browse-by-metadata-page.component.spec';
|
import { toRemoteData } from '../browse-by-metadata/browse-by-metadata.component.spec';
|
||||||
import { VarDirective } from '../../shared/utils/var.directive';
|
import { VarDirective } from '../../shared/utils/var.directive';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
@@ -23,10 +23,11 @@ import { PaginationServiceStub } from '../../shared/testing/pagination-service.s
|
|||||||
import { APP_CONFIG } from '../../../config/app-config.interface';
|
import { APP_CONFIG } from '../../../config/app-config.interface';
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { SortDirection } from '../../core/cache/models/sort-options.model';
|
import { SortDirection } from '../../core/cache/models/sort-options.model';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
|
||||||
describe('BrowseByDatePageComponent', () => {
|
describe('BrowseByDateComponent', () => {
|
||||||
let comp: BrowseByDatePageComponent;
|
let comp: BrowseByDateComponent;
|
||||||
let fixture: ComponentFixture<BrowseByDatePageComponent>;
|
let fixture: ComponentFixture<BrowseByDateComponent>;
|
||||||
let route: ActivatedRoute;
|
let route: ActivatedRoute;
|
||||||
let paginationService;
|
let paginationService;
|
||||||
|
|
||||||
@@ -86,7 +87,7 @@ describe('BrowseByDatePageComponent', () => {
|
|||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
||||||
declarations: [BrowseByDatePageComponent, EnumKeysPipe, VarDirective],
|
declarations: [BrowseByDateComponent, EnumKeysPipe, VarDirective],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||||
{ provide: BrowseService, useValue: mockBrowseService },
|
{ provide: BrowseService, useValue: mockBrowseService },
|
||||||
@@ -101,7 +102,7 @@ describe('BrowseByDatePageComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(BrowseByDatePageComponent);
|
fixture = TestBed.createComponent(BrowseByDateComponent);
|
||||||
const browseService = fixture.debugElement.injector.get(BrowseService);
|
const browseService = fixture.debugElement.injector.get(BrowseService);
|
||||||
spyOn(browseService, 'getFirstItemFor')
|
spyOn(browseService, 'getFirstItemFor')
|
||||||
// ok to expect the default browse as first param since we just need the mock items obtained via sort direction.
|
// ok to expect the default browse as first param since we just need the mock items obtained via sort direction.
|
||||||
@@ -112,9 +113,13 @@ describe('BrowseByDatePageComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should initialize the list of items', () => {
|
it('should initialize the list of items', (done: DoneFn) => {
|
||||||
|
expect(comp.loading$).toBeObservable(cold('(a|)', {
|
||||||
|
a: false,
|
||||||
|
}));
|
||||||
comp.items$.subscribe((result) => {
|
comp.items$.subscribe((result) => {
|
||||||
expect(result.payload.page).toEqual([firstItem]);
|
expect(result.payload.page).toEqual([firstItem]);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@@ -1,10 +1,6 @@
|
|||||||
import { ChangeDetectorRef, Component, Inject, OnInit } from '@angular/core';
|
import { ChangeDetectorRef, Component, Inject, OnInit } from '@angular/core';
|
||||||
import {
|
import { BrowseByMetadataComponent, browseParamsToOptions, getBrowseSearchOptions } from '../browse-by-metadata/browse-by-metadata.component';
|
||||||
BrowseByMetadataPageComponent,
|
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||||
browseParamsToOptions,
|
|
||||||
getBrowseSearchOptions
|
|
||||||
} from '../browse-by-metadata-page/browse-by-metadata-page.component';
|
|
||||||
import { combineLatest as observableCombineLatest } from 'rxjs';
|
|
||||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||||
import { BrowseService } from '../../core/browse/browse.service';
|
import { BrowseService } from '../../core/browse/browse.service';
|
||||||
@@ -23,9 +19,9 @@ import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
|||||||
import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type';
|
import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-browse-by-date-page',
|
selector: 'ds-browse-by-date',
|
||||||
styleUrls: ['../browse-by-metadata-page/browse-by-metadata-page.component.scss'],
|
styleUrls: ['../browse-by-metadata/browse-by-metadata.component.scss'],
|
||||||
templateUrl: '../browse-by-metadata-page/browse-by-metadata-page.component.html'
|
templateUrl: '../browse-by-metadata/browse-by-metadata.component.html',
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* Component for browsing items by metadata definition of type 'date'
|
* Component for browsing items by metadata definition of type 'date'
|
||||||
@@ -33,21 +29,22 @@ import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type';
|
|||||||
* An example would be 'dateissued' for 'dc.date.issued'
|
* An example would be 'dateissued' for 'dc.date.issued'
|
||||||
*/
|
*/
|
||||||
@rendersBrowseBy(BrowseByDataType.Date)
|
@rendersBrowseBy(BrowseByDataType.Date)
|
||||||
export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent implements OnInit {
|
export class BrowseByDateComponent extends BrowseByMetadataComponent implements OnInit {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default metadata keys to use for determining the lower limit of the StartsWith dropdown options
|
* The default metadata keys to use for determining the lower limit of the StartsWith dropdown options
|
||||||
*/
|
*/
|
||||||
defaultMetadataKeys = ['dc.date.issued'];
|
defaultMetadataKeys = ['dc.date.issued'];
|
||||||
|
|
||||||
public constructor(protected route: ActivatedRoute,
|
public constructor(
|
||||||
protected browseService: BrowseService,
|
protected route: ActivatedRoute,
|
||||||
protected dsoService: DSpaceObjectDataService,
|
protected browseService: BrowseService,
|
||||||
protected router: Router,
|
protected dsoService: DSpaceObjectDataService,
|
||||||
protected paginationService: PaginationService,
|
protected paginationService: PaginationService,
|
||||||
protected cdRef: ChangeDetectorRef,
|
protected router: Router,
|
||||||
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
||||||
public dsoNameService: DSONameService,
|
public dsoNameService: DSONameService,
|
||||||
|
protected cdRef: ChangeDetectorRef,
|
||||||
) {
|
) {
|
||||||
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService);
|
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService);
|
||||||
}
|
}
|
||||||
@@ -60,19 +57,17 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent imp
|
|||||||
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
||||||
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
|
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
observableCombineLatest([this.route.params, this.route.queryParams, this.route.data,
|
observableCombineLatest([this.route.params, this.route.queryParams, this.scope$, this.route.data,
|
||||||
this.currentPagination$, this.currentSort$]).pipe(
|
this.currentPagination$, this.currentSort$]).pipe(
|
||||||
map(([routeParams, queryParams, data, currentPage, currentSort]) => {
|
map(([routeParams, queryParams, scope, data, currentPage, currentSort]) => {
|
||||||
return [Object.assign({}, routeParams, queryParams, data), currentPage, currentSort];
|
return [Object.assign({}, routeParams, queryParams, data), scope, currentPage, currentSort];
|
||||||
})
|
})
|
||||||
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
).subscribe(([params, scope, currentPage, currentSort]: [Params, string, PaginationComponentOptions, SortOptions]) => {
|
||||||
const metadataKeys = params.browseDefinition ? params.browseDefinition.metadataKeys : this.defaultMetadataKeys;
|
const metadataKeys = params.browseDefinition ? params.browseDefinition.metadataKeys : this.defaultMetadataKeys;
|
||||||
this.browseId = params.id || this.defaultBrowseId;
|
this.browseId = params.id || this.defaultBrowseId;
|
||||||
this.startsWith = +params.startsWith || params.startsWith;
|
this.startsWith = +params.startsWith || params.startsWith;
|
||||||
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId, this.fetchThumbnails);
|
const searchOptions = browseParamsToOptions(params, scope, currentPage, currentSort, this.browseId, this.fetchThumbnails);
|
||||||
this.updatePageWithItems(searchOptions, this.value, undefined);
|
this.updatePageWithItems(searchOptions, this.value, undefined);
|
||||||
this.updateParent(params.scope);
|
|
||||||
this.updateLogo();
|
|
||||||
this.updateStartsWithOptions(this.browseId, metadataKeys, params.scope);
|
this.updateStartsWithOptions(this.browseId, metadataKeys, params.scope);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -88,12 +83,21 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent imp
|
|||||||
* @param scope The scope under which to fetch the earliest item for
|
* @param scope The scope under which to fetch the earliest item for
|
||||||
*/
|
*/
|
||||||
updateStartsWithOptions(definition: string, metadataKeys: string[], scope?: string) {
|
updateStartsWithOptions(definition: string, metadataKeys: string[], scope?: string) {
|
||||||
const firstItemRD = this.browseService.getFirstItemFor(definition, scope, SortDirection.ASC);
|
const firstItemRD$: Observable<RemoteData<Item>> = this.browseService.getFirstItemFor(definition, scope, SortDirection.ASC);
|
||||||
const lastItemRD = this.browseService.getFirstItemFor(definition, scope, SortDirection.DESC);
|
const lastItemRD$: Observable<RemoteData<Item>> = this.browseService.getFirstItemFor(definition, scope, SortDirection.DESC);
|
||||||
|
this.loading$ = observableCombineLatest([
|
||||||
|
firstItemRD$,
|
||||||
|
lastItemRD$,
|
||||||
|
]).pipe(
|
||||||
|
map(([firstItemRD, lastItemRD]: [RemoteData<Item>, RemoteData<Item>]) => firstItemRD.isLoading || lastItemRD.isLoading)
|
||||||
|
);
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
observableCombineLatest([firstItemRD, lastItemRD]).subscribe(([firstItem, lastItem]) => {
|
observableCombineLatest([
|
||||||
let lowerLimit: number = this.getLimit(firstItem, metadataKeys, this.appConfig.browseBy.defaultLowerLimit);
|
firstItemRD$,
|
||||||
let upperLimit: number = this.getLimit(lastItem, metadataKeys, new Date().getUTCFullYear());
|
lastItemRD$,
|
||||||
|
]).subscribe(([firstItemRD, lastItemRD]: [RemoteData<Item>, RemoteData<Item>]) => {
|
||||||
|
let lowerLimit: number = this.getLimit(firstItemRD, metadataKeys, this.appConfig.browseBy.defaultLowerLimit);
|
||||||
|
let upperLimit: number = this.getLimit(lastItemRD, metadataKeys, new Date().getUTCFullYear());
|
||||||
const options: number[] = [];
|
const options: number[] = [];
|
||||||
const oneYearBreak: number = Math.floor((upperLimit - this.appConfig.browseBy.oneYearLimit) / 5) * 5;
|
const oneYearBreak: number = Math.floor((upperLimit - this.appConfig.browseBy.oneYearLimit) / 5) * 5;
|
||||||
const fiveYearBreak: number = Math.floor((upperLimit - this.appConfig.browseBy.fiveYearLimit) / 10) * 10;
|
const fiveYearBreak: number = Math.floor((upperLimit - this.appConfig.browseBy.fiveYearLimit) / 10) * 10;
|
@@ -1,17 +1,13 @@
|
|||||||
import { first } from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
import { BrowseByGuard } from './browse-by-guard';
|
import { BrowseByGuard } from './browse-by-guard';
|
||||||
import { of as observableOf } from 'rxjs';
|
|
||||||
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||||
import { BrowseByDataType } from './browse-by-switcher/browse-by-data-type';
|
import { BrowseByDataType } from './browse-by-switcher/browse-by-data-type';
|
||||||
import { ValueListBrowseDefinition } from '../core/shared/value-list-browse-definition.model';
|
import { ValueListBrowseDefinition } from '../core/shared/value-list-browse-definition.model';
|
||||||
import { DSONameServiceMock } from '../shared/mocks/dso-name.service.mock';
|
|
||||||
import { DSONameService } from '../core/breadcrumbs/dso-name.service';
|
|
||||||
import { RouterStub } from '../shared/testing/router.stub';
|
import { RouterStub } from '../shared/testing/router.stub';
|
||||||
|
|
||||||
describe('BrowseByGuard', () => {
|
describe('BrowseByGuard', () => {
|
||||||
describe('canActivate', () => {
|
describe('canActivate', () => {
|
||||||
let guard: BrowseByGuard;
|
let guard: BrowseByGuard;
|
||||||
let dsoService: any;
|
|
||||||
let translateService: any;
|
let translateService: any;
|
||||||
let browseDefinitionService: any;
|
let browseDefinitionService: any;
|
||||||
let router: any;
|
let router: any;
|
||||||
@@ -25,10 +21,6 @@ describe('BrowseByGuard', () => {
|
|||||||
const browseDefinition = Object.assign(new ValueListBrowseDefinition(), { type: BrowseByDataType.Metadata, metadataKeys: ['dc.contributor'] });
|
const browseDefinition = Object.assign(new ValueListBrowseDefinition(), { type: BrowseByDataType.Metadata, metadataKeys: ['dc.contributor'] });
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
dsoService = {
|
|
||||||
findById: (dsoId: string) => observableOf({ payload: { name: name }, hasSucceeded: true })
|
|
||||||
};
|
|
||||||
|
|
||||||
translateService = {
|
translateService = {
|
||||||
instant: () => field
|
instant: () => field
|
||||||
};
|
};
|
||||||
@@ -39,7 +31,7 @@ describe('BrowseByGuard', () => {
|
|||||||
|
|
||||||
router = new RouterStub() as any;
|
router = new RouterStub() as any;
|
||||||
|
|
||||||
guard = new BrowseByGuard(dsoService, translateService, browseDefinitionService, new DSONameServiceMock() as DSONameService, router);
|
guard = new BrowseByGuard(translateService, browseDefinitionService, router);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true, and sets up the data correctly, with a scope and value', () => {
|
it('should return true, and sets up the data correctly, with a scope and value', () => {
|
||||||
@@ -48,6 +40,7 @@ describe('BrowseByGuard', () => {
|
|||||||
title: field,
|
title: field,
|
||||||
browseDefinition,
|
browseDefinition,
|
||||||
},
|
},
|
||||||
|
parent: null,
|
||||||
params: {
|
params: {
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
@@ -64,7 +57,7 @@ describe('BrowseByGuard', () => {
|
|||||||
title,
|
title,
|
||||||
id,
|
id,
|
||||||
browseDefinition,
|
browseDefinition,
|
||||||
collection: name,
|
scope,
|
||||||
field,
|
field,
|
||||||
value: '"' + value + '"'
|
value: '"' + value + '"'
|
||||||
};
|
};
|
||||||
@@ -97,7 +90,7 @@ describe('BrowseByGuard', () => {
|
|||||||
title,
|
title,
|
||||||
id,
|
id,
|
||||||
browseDefinition,
|
browseDefinition,
|
||||||
collection: name,
|
scope,
|
||||||
field,
|
field,
|
||||||
value: ''
|
value: ''
|
||||||
};
|
};
|
||||||
@@ -108,12 +101,48 @@ describe('BrowseByGuard', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return true, and sets up the data correctly using the community/collection page id, with a scope and without value', () => {
|
||||||
|
const scopedNoValueRoute = {
|
||||||
|
data: {
|
||||||
|
title: field,
|
||||||
|
browseDefinition,
|
||||||
|
},
|
||||||
|
parent: {
|
||||||
|
params: {
|
||||||
|
id: scope,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
queryParams: {
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
guard.canActivate(scopedNoValueRoute as any, undefined).pipe(
|
||||||
|
first(),
|
||||||
|
).subscribe((canActivate) => {
|
||||||
|
const result = {
|
||||||
|
title,
|
||||||
|
id,
|
||||||
|
browseDefinition,
|
||||||
|
scope,
|
||||||
|
field,
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
expect(scopedNoValueRoute.data).toEqual(result);
|
||||||
|
expect(router.navigate).not.toHaveBeenCalled();
|
||||||
|
expect(canActivate).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should return true, and sets up the data correctly, without a scope and with a value', () => {
|
it('should return true, and sets up the data correctly, without a scope and with a value', () => {
|
||||||
const route = {
|
const route = {
|
||||||
data: {
|
data: {
|
||||||
title: field,
|
title: field,
|
||||||
browseDefinition,
|
browseDefinition,
|
||||||
},
|
},
|
||||||
|
parent: null,
|
||||||
params: {
|
params: {
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
@@ -129,7 +158,7 @@ describe('BrowseByGuard', () => {
|
|||||||
title,
|
title,
|
||||||
id,
|
id,
|
||||||
browseDefinition,
|
browseDefinition,
|
||||||
collection: '',
|
scope: undefined,
|
||||||
field,
|
field,
|
||||||
value: '"' + value + '"'
|
value: '"' + value + '"'
|
||||||
};
|
};
|
||||||
@@ -147,6 +176,7 @@ describe('BrowseByGuard', () => {
|
|||||||
data: {
|
data: {
|
||||||
title: field,
|
title: field,
|
||||||
},
|
},
|
||||||
|
parent: null,
|
||||||
params: {
|
params: {
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
|
@@ -1,15 +1,12 @@
|
|||||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, Data } from '@angular/router';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { DSpaceObjectDataService } from '../core/data/dspace-object-data.service';
|
|
||||||
import { hasNoValue, hasValue } from '../shared/empty.util';
|
import { hasNoValue, hasValue } from '../shared/empty.util';
|
||||||
import { map, switchMap } from 'rxjs/operators';
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Observable, of as observableOf } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { BrowseDefinitionDataService } from '../core/browse/browse-definition-data.service';
|
import { BrowseDefinitionDataService } from '../core/browse/browse-definition-data.service';
|
||||||
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
||||||
import { DSONameService } from '../core/breadcrumbs/dso-name.service';
|
|
||||||
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { PAGE_NOT_FOUND_PATH } from '../app-routing-paths';
|
import { PAGE_NOT_FOUND_PATH } from '../app-routing-paths';
|
||||||
|
|
||||||
@@ -19,11 +16,10 @@ import { PAGE_NOT_FOUND_PATH } from '../app-routing-paths';
|
|||||||
*/
|
*/
|
||||||
export class BrowseByGuard implements CanActivate {
|
export class BrowseByGuard implements CanActivate {
|
||||||
|
|
||||||
constructor(protected dsoService: DSpaceObjectDataService,
|
constructor(
|
||||||
protected translate: TranslateService,
|
protected translate: TranslateService,
|
||||||
protected browseDefinitionService: BrowseDefinitionDataService,
|
protected browseDefinitionService: BrowseDefinitionDataService,
|
||||||
protected dsoNameService: DSONameService,
|
protected router: Router,
|
||||||
protected router: Router,
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,25 +35,14 @@ export class BrowseByGuard implements CanActivate {
|
|||||||
} else {
|
} else {
|
||||||
browseDefinition$ = observableOf(route.data.browseDefinition);
|
browseDefinition$ = observableOf(route.data.browseDefinition);
|
||||||
}
|
}
|
||||||
const scope = route.queryParams.scope;
|
const scope = route.queryParams.scope ?? route.parent?.params.id;
|
||||||
const value = route.queryParams.value;
|
const value = route.queryParams.value;
|
||||||
const metadataTranslated = this.translate.instant(`browse.metadata.${id}`);
|
const metadataTranslated = this.translate.instant(`browse.metadata.${id}`);
|
||||||
return browseDefinition$.pipe(
|
return browseDefinition$.pipe(
|
||||||
switchMap((browseDefinition: BrowseDefinition | undefined) => {
|
switchMap((browseDefinition: BrowseDefinition | undefined) => {
|
||||||
if (hasValue(browseDefinition)) {
|
if (hasValue(browseDefinition)) {
|
||||||
if (hasValue(scope)) {
|
route.data = this.createData(title, id, browseDefinition, metadataTranslated, value, route, scope);
|
||||||
const dso$: Observable<DSpaceObject> = this.dsoService.findById(scope).pipe(getFirstSucceededRemoteDataPayload());
|
return observableOf(true);
|
||||||
return dso$.pipe(
|
|
||||||
map((dso: DSpaceObject) => {
|
|
||||||
const name = this.dsoNameService.getName(dso);
|
|
||||||
route.data = this.createData(title, id, browseDefinition, name, metadataTranslated, value, route);
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
route.data = this.createData(title, id, browseDefinition, '', metadataTranslated, value, route);
|
|
||||||
return observableOf(true);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
void this.router.navigate([PAGE_NOT_FOUND_PATH]);
|
void this.router.navigate([PAGE_NOT_FOUND_PATH]);
|
||||||
return observableOf(false);
|
return observableOf(false);
|
||||||
@@ -66,14 +51,14 @@ export class BrowseByGuard implements CanActivate {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createData(title, id, browseDefinition, collection, field, value, route) {
|
private createData(title: string, id: string, browseDefinition: BrowseDefinition, field: string, value: string, route: ActivatedRouteSnapshot, scope: string): Data {
|
||||||
return Object.assign({}, route.data, {
|
return Object.assign({}, route.data, {
|
||||||
title: title,
|
title: title,
|
||||||
id: id,
|
id: id,
|
||||||
browseDefinition: browseDefinition,
|
browseDefinition: browseDefinition,
|
||||||
collection: collection,
|
|
||||||
field: field,
|
field: field,
|
||||||
value: hasValue(value) ? `"${value}"` : ''
|
value: hasValue(value) ? `"${value}"` : '',
|
||||||
|
scope: scope,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,67 +0,0 @@
|
|||||||
<div class="container">
|
|
||||||
<ng-container *ngVar="(parent$ | async) as parent">
|
|
||||||
<ng-container *ngIf="parent?.payload as parentContext">
|
|
||||||
<div class="d-flex flex-row border-bottom mb-4 pb-4">
|
|
||||||
|
|
||||||
<header class="comcol-header mr-auto">
|
|
||||||
<!-- Parent Name -->
|
|
||||||
<ds-comcol-page-header [name]="dsoNameService.getName(parentContext)">
|
|
||||||
</ds-comcol-page-header>
|
|
||||||
<!-- Collection logo -->
|
|
||||||
<ds-comcol-page-logo *ngIf="logo$"
|
|
||||||
[logo]="(logo$ | async)?.payload"
|
|
||||||
[alternateText]="'Community or Collection Logo'">
|
|
||||||
</ds-comcol-page-logo>
|
|
||||||
<!-- Handle -->
|
|
||||||
<ds-themed-comcol-page-handle
|
|
||||||
[content]="parentContext.handle"
|
|
||||||
[title]="parentContext.type+'.page.handle'" >
|
|
||||||
</ds-themed-comcol-page-handle>
|
|
||||||
<!-- Introductory text -->
|
|
||||||
<ds-comcol-page-content [content]="parentContext.introductoryText" [hasInnerHtml]="true">
|
|
||||||
</ds-comcol-page-content>
|
|
||||||
<!-- News -->
|
|
||||||
<ds-comcol-page-content [content]="parentContext.sidebarText" [hasInnerHtml]="true" [title]="'community.page.news'">
|
|
||||||
</ds-comcol-page-content>
|
|
||||||
</header>
|
|
||||||
<ds-dso-edit-menu></ds-dso-edit-menu>
|
|
||||||
</div>
|
|
||||||
<!-- Browse-By Links -->
|
|
||||||
<ds-themed-comcol-page-browse-by [id]="parentContext.id" [contentType]="parentContext.type"></ds-themed-comcol-page-browse-by>
|
|
||||||
</ng-container></ng-container>
|
|
||||||
|
|
||||||
<section class="comcol-page-browse-section">
|
|
||||||
<div class="browse-by-metadata w-100">
|
|
||||||
<ds-themed-browse-by *ngIf="startsWithOptions" class="col-xs-12 w-100"
|
|
||||||
title="{{'browse.title' | translate:
|
|
||||||
{
|
|
||||||
collection: dsoNameService.getName((parent$ | async)?.payload),
|
|
||||||
field: 'browse.metadata.' + browseId | translate,
|
|
||||||
startsWith: (startsWith)? ('browse.startsWith' | translate: { startsWith: '"' + startsWith + '"' }) : '',
|
|
||||||
value: (value)? '"' + value + '"': ''
|
|
||||||
} }}"
|
|
||||||
parentname="{{dsoNameService.getName((parent$ | async)?.payload)}}"
|
|
||||||
[objects$]="(items$ !== undefined)? items$ : browseEntries$"
|
|
||||||
[paginationConfig]="(currentPagination$ |async)"
|
|
||||||
[sortConfig]="(currentSort$ |async)"
|
|
||||||
[type]="startsWithType"
|
|
||||||
[startsWithOptions]="startsWithOptions"
|
|
||||||
(prev)="goPrev()"
|
|
||||||
(next)="goNext()">
|
|
||||||
</ds-themed-browse-by>
|
|
||||||
<ds-themed-loading *ngIf="!startsWithOptions" message="{{'loading.browse-by-page' | translate}}"></ds-themed-loading>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<ng-container *ngVar="(parent$ | async) as parent">
|
|
||||||
<ng-container *ngIf="parent?.payload as parentContext">
|
|
||||||
<footer *ngIf="parentContext.copyrightText" class="border-top my-5 pt-4">
|
|
||||||
<div >
|
|
||||||
|
|
||||||
<!-- Copyright -->
|
|
||||||
<ds-comcol-page-content [content]="parentContext.copyrightText" [hasInnerHtml]="true">
|
|
||||||
</ds-comcol-page-content>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
@@ -0,0 +1,21 @@
|
|||||||
|
<section class="comcol-page-browse-section">
|
||||||
|
<div class="browse-by-metadata w-100">
|
||||||
|
<ds-themed-browse-by *ngIf="!(loading$ | async)" class="col-xs-12 w-100"
|
||||||
|
title="{{'browse.title' | translate:{
|
||||||
|
field: 'browse.metadata.' + browseId | translate,
|
||||||
|
startsWith: (startsWith)? ('browse.startsWith' | translate: { startsWith: '"' + startsWith + '"' }) : '',
|
||||||
|
value: (value)? '"' + value + '"': ''
|
||||||
|
} }}"
|
||||||
|
[displayTitle]="displayTitle"
|
||||||
|
[objects$]="(items$ !== undefined)? items$ : browseEntries$"
|
||||||
|
[paginationConfig]="(currentPagination$ |async)"
|
||||||
|
[sortConfig]="(currentSort$ |async)"
|
||||||
|
[type]="startsWithType"
|
||||||
|
[startsWithOptions]="startsWithOptions"
|
||||||
|
(prev)="goPrev()"
|
||||||
|
(next)="goNext()">
|
||||||
|
</ds-themed-browse-by>
|
||||||
|
<ds-themed-loading *ngIf="loading$ | async"
|
||||||
|
message="{{'loading.browse-by-page' | translate}}"></ds-themed-loading>
|
||||||
|
</div>
|
||||||
|
</section>
|
@@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
BrowseByMetadataPageComponent,
|
BrowseByMetadataComponent,
|
||||||
browseParamsToOptions,
|
browseParamsToOptions,
|
||||||
getBrowseSearchOptions
|
getBrowseSearchOptions
|
||||||
} from './browse-by-metadata-page.component';
|
} from './browse-by-metadata.component';
|
||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { BrowseService } from '../../core/browse/browse.service';
|
import { BrowseService } from '../../core/browse/browse.service';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
@@ -30,10 +30,11 @@ import { PaginationService } from '../../core/pagination/pagination.service';
|
|||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
||||||
import { APP_CONFIG } from '../../../config/app-config.interface';
|
import { APP_CONFIG } from '../../../config/app-config.interface';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
|
||||||
describe('BrowseByMetadataPageComponent', () => {
|
describe('BrowseByMetadataComponent', () => {
|
||||||
let comp: BrowseByMetadataPageComponent;
|
let comp: BrowseByMetadataComponent;
|
||||||
let fixture: ComponentFixture<BrowseByMetadataPageComponent>;
|
let fixture: ComponentFixture<BrowseByMetadataComponent>;
|
||||||
let browseService: BrowseService;
|
let browseService: BrowseService;
|
||||||
let route: ActivatedRoute;
|
let route: ActivatedRoute;
|
||||||
let paginationService;
|
let paginationService;
|
||||||
@@ -103,7 +104,7 @@ describe('BrowseByMetadataPageComponent', () => {
|
|||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
||||||
declarations: [BrowseByMetadataPageComponent, EnumKeysPipe, VarDirective],
|
declarations: [BrowseByMetadataComponent, EnumKeysPipe, VarDirective],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||||
{ provide: BrowseService, useValue: mockBrowseService },
|
{ provide: BrowseService, useValue: mockBrowseService },
|
||||||
@@ -117,7 +118,7 @@ describe('BrowseByMetadataPageComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(BrowseByMetadataPageComponent);
|
fixture = TestBed.createComponent(BrowseByMetadataComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
browseService = (comp as any).browseService;
|
browseService = (comp as any).browseService;
|
||||||
@@ -144,20 +145,18 @@ describe('BrowseByMetadataPageComponent', () => {
|
|||||||
|
|
||||||
route.params = observableOf(paramsWithValue);
|
route.params = observableOf(paramsWithValue);
|
||||||
comp.ngOnInit();
|
comp.ngOnInit();
|
||||||
comp.updateParent('fake-scope');
|
|
||||||
comp.updateLogo();
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fetch items', () => {
|
it('should fetch items', (done: DoneFn) => {
|
||||||
|
expect(comp.loading$).toBeObservable(cold('(a|)', {
|
||||||
|
a: false,
|
||||||
|
}));
|
||||||
comp.items$.subscribe((result) => {
|
comp.items$.subscribe((result) => {
|
||||||
expect(result.payload.page).toEqual(mockItems);
|
expect(result.payload.page).toEqual(mockItems);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fetch the logo', () => {
|
|
||||||
expect(comp.logo$).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when calling browseParamsToOptions', () => {
|
describe('when calling browseParamsToOptions', () => {
|
||||||
@@ -176,7 +175,7 @@ describe('BrowseByMetadataPageComponent', () => {
|
|||||||
field: 'fake-field',
|
field: 'fake-field',
|
||||||
};
|
};
|
||||||
|
|
||||||
result = browseParamsToOptions(paramsScope, paginationOptions, sortOptions, 'author', comp.fetchThumbnails);
|
result = browseParamsToOptions(paramsScope, 'fake-scope', paginationOptions, sortOptions, 'author', comp.fetchThumbnails);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return BrowseEntrySearchOptions with the correct properties', () => {
|
it('should return BrowseEntrySearchOptions with the correct properties', () => {
|
@@ -1,5 +1,5 @@
|
|||||||
import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs';
|
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subscription, of as observableOf } from 'rxjs';
|
||||||
import { Component, Inject, OnInit, OnDestroy, Input } from '@angular/core';
|
import { Component, Inject, OnInit, OnDestroy, Input, OnChanges } from '@angular/core';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
@@ -12,14 +12,9 @@ import { Item } from '../../core/shared/item.model';
|
|||||||
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
|
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
|
||||||
import { getFirstSucceededRemoteData } from '../../core/shared/operators';
|
import { getFirstSucceededRemoteData } from '../../core/shared/operators';
|
||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
|
||||||
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { filter, map, mergeMap } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
|
||||||
import { Bitstream } from '../../core/shared/bitstream.model';
|
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
|
||||||
import { Community } from '../../core/shared/community.model';
|
|
||||||
import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
|
import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
|
||||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||||
@@ -29,9 +24,9 @@ import { Context } from '../../core/shared/context.model';
|
|||||||
export const BBM_PAGINATION_ID = 'bbm';
|
export const BBM_PAGINATION_ID = 'bbm';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-browse-by-metadata-page',
|
selector: 'ds-browse-by-metadata',
|
||||||
styleUrls: ['./browse-by-metadata-page.component.scss'],
|
styleUrls: ['./browse-by-metadata.component.scss'],
|
||||||
templateUrl: './browse-by-metadata-page.component.html'
|
templateUrl: './browse-by-metadata.component.html',
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* Component for browsing (items) by metadata definition.
|
* Component for browsing (items) by metadata definition.
|
||||||
@@ -40,7 +35,7 @@ export const BBM_PAGINATION_ID = 'bbm';
|
|||||||
* 'dc.contributor.*'
|
* 'dc.contributor.*'
|
||||||
*/
|
*/
|
||||||
@rendersBrowseBy(BrowseByDataType.Metadata)
|
@rendersBrowseBy(BrowseByDataType.Metadata)
|
||||||
export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
export class BrowseByMetadataComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The optional context
|
* The optional context
|
||||||
@@ -52,6 +47,18 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
@Input() browseByType: BrowseByDataType;
|
@Input() browseByType: BrowseByDataType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the {@link Community} or {@link Collection} of the scope to display
|
||||||
|
*/
|
||||||
|
@Input() scope: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the h1 title in the section
|
||||||
|
*/
|
||||||
|
@Input() displayTitle = true;
|
||||||
|
|
||||||
|
scope$: BehaviorSubject<string> = new BehaviorSubject(undefined);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of browse-entries to display
|
* The list of browse-entries to display
|
||||||
*/
|
*/
|
||||||
@@ -62,16 +69,6 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
items$: Observable<RemoteData<PaginatedList<Item>>>;
|
items$: Observable<RemoteData<PaginatedList<Item>>>;
|
||||||
|
|
||||||
/**
|
|
||||||
* The current Community or Collection we're browsing metadata/items in
|
|
||||||
*/
|
|
||||||
parent$: Observable<RemoteData<DSpaceObject>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The logo of the current Community or Collection
|
|
||||||
*/
|
|
||||||
logo$: Observable<RemoteData<Bitstream>>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The pagination config used to display the values
|
* The pagination config used to display the values
|
||||||
*/
|
*/
|
||||||
@@ -112,7 +109,7 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
* The list of StartsWith options
|
* The list of StartsWith options
|
||||||
* Should be defined after ngOnInit is called!
|
* Should be defined after ngOnInit is called!
|
||||||
*/
|
*/
|
||||||
startsWithOptions;
|
startsWithOptions: (string | number)[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The value we're browsing items for
|
* The value we're browsing items for
|
||||||
@@ -136,6 +133,11 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
fetchThumbnails: boolean;
|
fetchThumbnails: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable determining if the loading animation needs to be shown
|
||||||
|
*/
|
||||||
|
loading$ = observableOf(true);
|
||||||
|
|
||||||
public constructor(protected route: ActivatedRoute,
|
public constructor(protected route: ActivatedRoute,
|
||||||
protected browseService: BrowseService,
|
protected browseService: BrowseService,
|
||||||
protected dsoService: DSpaceObjectDataService,
|
protected dsoService: DSpaceObjectDataService,
|
||||||
@@ -160,12 +162,12 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
||||||
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
|
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
observableCombineLatest([this.route.params, this.route.queryParams, this.currentPagination$, this.currentSort$]).pipe(
|
observableCombineLatest([this.route.params, this.route.queryParams, this.scope$, this.currentPagination$, this.currentSort$]).pipe(
|
||||||
map(([routeParams, queryParams, currentPage, currentSort]) => {
|
map(([routeParams, queryParams, scope, currentPage, currentSort]) => {
|
||||||
return [Object.assign({}, routeParams, queryParams),currentPage,currentSort];
|
return [Object.assign({}, routeParams, queryParams), scope, currentPage, currentSort];
|
||||||
})
|
})
|
||||||
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
).subscribe(([params, scope, currentPage, currentSort]: [Params, string, PaginationComponentOptions, SortOptions]) => {
|
||||||
this.browseId = params.id || this.defaultBrowseId;
|
this.browseId = params.id || this.defaultBrowseId;
|
||||||
this.authority = params.authority;
|
this.authority = params.authority;
|
||||||
|
|
||||||
if (typeof params.value === 'string'){
|
if (typeof params.value === 'string'){
|
||||||
@@ -183,18 +185,19 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isNotEmpty(this.value)) {
|
if (isNotEmpty(this.value)) {
|
||||||
this.updatePageWithItems(
|
this.updatePageWithItems(browseParamsToOptions(params, scope, currentPage, currentSort, this.browseId, this.fetchThumbnails), this.value, this.authority);
|
||||||
browseParamsToOptions(params, currentPage, currentSort, this.browseId, this.fetchThumbnails), this.value, this.authority);
|
|
||||||
} else {
|
} else {
|
||||||
this.updatePage(browseParamsToOptions(params, currentPage, currentSort, this.browseId, false));
|
this.updatePage(browseParamsToOptions(params, scope, currentPage, currentSort, this.browseId, false));
|
||||||
}
|
}
|
||||||
this.updateParent(params.scope);
|
|
||||||
this.updateLogo();
|
|
||||||
}));
|
}));
|
||||||
this.updateStartsWithTextOptions();
|
this.updateStartsWithTextOptions();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnChanges(): void {
|
||||||
|
this.scope$.next(this.scope);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the StartsWith options with text values
|
* Update the StartsWith options with text values
|
||||||
* It adds the value "0-9" as well as all letters from A to Z
|
* It adds the value "0-9" as well as all letters from A to Z
|
||||||
@@ -213,6 +216,9 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
updatePage(searchOptions: BrowseEntrySearchOptions) {
|
updatePage(searchOptions: BrowseEntrySearchOptions) {
|
||||||
this.browseEntries$ = this.browseService.getBrowseEntriesFor(searchOptions);
|
this.browseEntries$ = this.browseService.getBrowseEntriesFor(searchOptions);
|
||||||
|
this.loading$ = this.browseEntries$.pipe(
|
||||||
|
map((browseEntriesRD: RemoteData<PaginatedList<BrowseEntry>>) => browseEntriesRD.isLoading),
|
||||||
|
);
|
||||||
this.items$ = undefined;
|
this.items$ = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,37 +233,9 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
updatePageWithItems(searchOptions: BrowseEntrySearchOptions, value: string, authority: string) {
|
updatePageWithItems(searchOptions: BrowseEntrySearchOptions, value: string, authority: string) {
|
||||||
this.items$ = this.browseService.getBrowseItemsFor(value, authority, searchOptions);
|
this.items$ = this.browseService.getBrowseItemsFor(value, authority, searchOptions);
|
||||||
}
|
this.loading$ = this.items$.pipe(
|
||||||
|
map((itemsRD: RemoteData<PaginatedList<Item>>) => itemsRD.isLoading),
|
||||||
/**
|
);
|
||||||
* Update the parent Community or Collection using their scope
|
|
||||||
* @param scope The UUID of the Community or Collection to fetch
|
|
||||||
*/
|
|
||||||
updateParent(scope: string) {
|
|
||||||
if (hasValue(scope)) {
|
|
||||||
const linksToFollow = () => {
|
|
||||||
return [followLink('logo')];
|
|
||||||
};
|
|
||||||
this.parent$ = this.dsoService.findById(scope,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
...linksToFollow() as FollowLinkConfig<DSpaceObject>[]).pipe(
|
|
||||||
getFirstSucceededRemoteData()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the parent Community or Collection logo
|
|
||||||
*/
|
|
||||||
updateLogo() {
|
|
||||||
if (hasValue(this.parent$)) {
|
|
||||||
this.logo$ = this.parent$.pipe(
|
|
||||||
map((rd: RemoteData<Collection | Community>) => rd.payload),
|
|
||||||
filter((collectionOrCommunity: Collection | Community) => hasValue(collectionOrCommunity.logo)),
|
|
||||||
mergeMap((collectionOrCommunity: Collection | Community) => collectionOrCommunity.logo)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -320,12 +298,14 @@ export function getBrowseSearchOptions(defaultBrowseId: string,
|
|||||||
/**
|
/**
|
||||||
* Function to transform query and url parameters into searchOptions used to fetch browse entries or items
|
* Function to transform query and url parameters into searchOptions used to fetch browse entries or items
|
||||||
* @param params URL and query parameters
|
* @param params URL and query parameters
|
||||||
|
* @param scope The scope to show the results
|
||||||
* @param paginationConfig Pagination configuration
|
* @param paginationConfig Pagination configuration
|
||||||
* @param sortConfig Sorting configuration
|
* @param sortConfig Sorting configuration
|
||||||
* @param metadata Optional metadata definition to fetch browse entries/items for
|
* @param metadata Optional metadata definition to fetch browse entries/items for
|
||||||
* @param fetchThumbnail Optional parameter for requesting thumbnail images
|
* @param fetchThumbnail Optional parameter for requesting thumbnail images
|
||||||
*/
|
*/
|
||||||
export function browseParamsToOptions(params: any,
|
export function browseParamsToOptions(params: any,
|
||||||
|
scope: string,
|
||||||
paginationConfig: PaginationComponentOptions,
|
paginationConfig: PaginationComponentOptions,
|
||||||
sortConfig: SortOptions,
|
sortConfig: SortOptions,
|
||||||
metadata?: string,
|
metadata?: string,
|
||||||
@@ -335,7 +315,7 @@ export function browseParamsToOptions(params: any,
|
|||||||
paginationConfig,
|
paginationConfig,
|
||||||
sortConfig,
|
sortConfig,
|
||||||
params.startsWith,
|
params.startsWith,
|
||||||
params.scope,
|
scope,
|
||||||
fetchThumbnail
|
fetchThumbnail
|
||||||
);
|
);
|
||||||
}
|
}
|
@@ -1,2 +1,4 @@
|
|||||||
<ds-browse-by-switcher [browseByType]="browseByType$ | async">
|
<div class="container">
|
||||||
</ds-browse-by-switcher>
|
<ds-browse-by-switcher [browseByType]="browseByType$ | async">
|
||||||
|
</ds-browse-by-switcher>
|
||||||
|
</div>
|
||||||
|
@@ -15,6 +15,10 @@ export class BrowseBySwitcherComponent extends AbstractComponentLoaderComponent<
|
|||||||
|
|
||||||
@Input() browseByType: BrowseByDataType;
|
@Input() browseByType: BrowseByDataType;
|
||||||
|
|
||||||
|
@Input() displayTitle: boolean;
|
||||||
|
|
||||||
|
@Input() scope: string;
|
||||||
|
|
||||||
protected inputNamesDependentForComponent: (keyof this & string)[] = [
|
protected inputNamesDependentForComponent: (keyof this & string)[] = [
|
||||||
'context',
|
'context',
|
||||||
'browseByType',
|
'browseByType',
|
||||||
@@ -23,6 +27,8 @@ export class BrowseBySwitcherComponent extends AbstractComponentLoaderComponent<
|
|||||||
protected inputNames: (keyof this & string)[] = [
|
protected inputNames: (keyof this & string)[] = [
|
||||||
'context',
|
'context',
|
||||||
'browseByType',
|
'browseByType',
|
||||||
|
'displayTitle',
|
||||||
|
'scope',
|
||||||
];
|
];
|
||||||
|
|
||||||
public getComponent(): GenericConstructor<Component> {
|
public getComponent(): GenericConstructor<Component> {
|
||||||
|
@@ -0,0 +1,16 @@
|
|||||||
|
import { Directive, ViewContainerRef } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directive used as a hook to know where to inject the dynamic loaded component
|
||||||
|
*/
|
||||||
|
@Directive({
|
||||||
|
selector: '[dsDynamicComponentLoader]'
|
||||||
|
})
|
||||||
|
export class DynamicComponentLoaderDirective {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public viewContainerRef: ViewContainerRef,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,5 +1,11 @@
|
|||||||
<div class="container">
|
<section>
|
||||||
<h1>{{ ('browse.taxonomy_' + vocabularyName + '.title') | translate }}</h1>
|
<h1 *ngIf="displayTitle">
|
||||||
|
{{ ('browse.title') | translate:{
|
||||||
|
field: 'browse.metadata.' + vocabularyName | translate,
|
||||||
|
startsWith: '',
|
||||||
|
value: '',
|
||||||
|
} }}
|
||||||
|
</h1>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<ds-vocabulary-treeview [vocabularyOptions]=vocabularyOptions
|
<ds-vocabulary-treeview [vocabularyOptions]=vocabularyOptions
|
||||||
[multiSelect]="true"
|
[multiSelect]="true"
|
||||||
@@ -12,4 +18,4 @@
|
|||||||
[queryParams]="queryParams"
|
[queryParams]="queryParams"
|
||||||
[queryParamsHandling]="'merge'">
|
[queryParamsHandling]="'merge'">
|
||||||
{{ 'browse.taxonomy.button' | translate }}</a>
|
{{ 'browse.taxonomy.button' | translate }}</a>
|
||||||
</div>
|
</section>
|
@@ -1,6 +1,5 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { BrowseByTaxonomyComponent } from './browse-by-taxonomy.component';
|
||||||
import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page.component';
|
|
||||||
import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
@@ -10,9 +9,9 @@ import { createDataWithBrowseDefinition } from '../browse-by-switcher/browse-by-
|
|||||||
import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model';
|
import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model';
|
||||||
import { ThemeService } from '../../shared/theme-support/theme.service';
|
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||||
|
|
||||||
describe('BrowseByTaxonomyPageComponent', () => {
|
describe('BrowseByTaxonomyComponent', () => {
|
||||||
let component: BrowseByTaxonomyPageComponent;
|
let component: BrowseByTaxonomyComponent;
|
||||||
let fixture: ComponentFixture<BrowseByTaxonomyPageComponent>;
|
let fixture: ComponentFixture<BrowseByTaxonomyComponent>;
|
||||||
let themeService: ThemeService;
|
let themeService: ThemeService;
|
||||||
let detail1: VocabularyEntryDetail;
|
let detail1: VocabularyEntryDetail;
|
||||||
let detail2: VocabularyEntryDetail;
|
let detail2: VocabularyEntryDetail;
|
||||||
@@ -29,7 +28,9 @@ describe('BrowseByTaxonomyPageComponent', () => {
|
|||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [ TranslateModule.forRoot() ],
|
imports: [ TranslateModule.forRoot() ],
|
||||||
declarations: [ BrowseByTaxonomyPageComponent ],
|
declarations: [
|
||||||
|
BrowseByTaxonomyComponent,
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||||
{ provide: ThemeService, useValue: themeService },
|
{ provide: ThemeService, useValue: themeService },
|
||||||
@@ -40,8 +41,9 @@ describe('BrowseByTaxonomyPageComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(BrowseByTaxonomyPageComponent);
|
fixture = TestBed.createComponent(BrowseByTaxonomyComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
spyOn(component, 'updateQueryParams').and.callThrough();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
detail1 = new VocabularyEntryDetail();
|
detail1 = new VocabularyEntryDetail();
|
||||||
detail2 = new VocabularyEntryDetail();
|
detail2 = new VocabularyEntryDetail();
|
||||||
@@ -61,6 +63,7 @@ describe('BrowseByTaxonomyPageComponent', () => {
|
|||||||
expect(component.selectedItems).toContain(detail1);
|
expect(component.selectedItems).toContain(detail1);
|
||||||
expect(component.selectedItems.length).toBe(1);
|
expect(component.selectedItems.length).toBe(1);
|
||||||
expect(component.filterValues).toEqual(['HUMANITIES and RELIGION,equals'] );
|
expect(component.filterValues).toEqual(['HUMANITIES and RELIGION,equals'] );
|
||||||
|
expect(component.updateQueryParams).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle select event with multiple selected items', () => {
|
it('should handle select event with multiple selected items', () => {
|
||||||
@@ -70,6 +73,7 @@ describe('BrowseByTaxonomyPageComponent', () => {
|
|||||||
expect(component.selectedItems).toContain(detail1, detail2);
|
expect(component.selectedItems).toContain(detail1, detail2);
|
||||||
expect(component.selectedItems.length).toBe(2);
|
expect(component.selectedItems.length).toBe(2);
|
||||||
expect(component.filterValues).toEqual(['HUMANITIES and RELIGION,equals', 'TECHNOLOGY,equals'] );
|
expect(component.filterValues).toEqual(['HUMANITIES and RELIGION,equals', 'TECHNOLOGY,equals'] );
|
||||||
|
expect(component.updateQueryParams).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle deselect event', () => {
|
it('should handle deselect event', () => {
|
||||||
@@ -82,6 +86,33 @@ describe('BrowseByTaxonomyPageComponent', () => {
|
|||||||
expect(component.selectedItems).toContain(detail2);
|
expect(component.selectedItems).toContain(detail2);
|
||||||
expect(component.selectedItems.length).toBe(1);
|
expect(component.selectedItems.length).toBe(1);
|
||||||
expect(component.filterValues).toEqual(['TECHNOLOGY,equals'] );
|
expect(component.filterValues).toEqual(['TECHNOLOGY,equals'] );
|
||||||
|
expect(component.updateQueryParams).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateQueryParams', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
component.facetType = 'subject';
|
||||||
|
component.filterValues = ['HUMANITIES and RELIGION,equals', 'TECHNOLOGY,equals'];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the queryParams with the selected filterValues', () => {
|
||||||
|
component.updateQueryParams();
|
||||||
|
|
||||||
|
expect(component.queryParams).toEqual({
|
||||||
|
'f.subject': ['HUMANITIES and RELIGION,equals', 'TECHNOLOGY,equals'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include the scope if present', () => {
|
||||||
|
component.scope = '67f849f1-2499-4872-8c61-9e2b47d71068';
|
||||||
|
|
||||||
|
component.updateQueryParams();
|
||||||
|
|
||||||
|
expect(component.queryParams).toEqual({
|
||||||
|
'f.subject': ['HUMANITIES and RELIGION,equals', 'TECHNOLOGY,equals'],
|
||||||
|
'scope': '67f849f1-2499-4872-8c61-9e2b47d71068',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
@@ -1,25 +1,26 @@
|
|||||||
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
|
import { Component, OnInit, OnChanges, OnDestroy, Input } from '@angular/core';
|
||||||
import { VocabularyOptions } from '../../core/submission/vocabularies/models/vocabulary-options.model';
|
import { VocabularyOptions } from '../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||||
import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute, Params } from '@angular/router';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||||
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
||||||
import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model';
|
import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model';
|
||||||
import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type';
|
import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type';
|
||||||
import { Context } from '../../core/shared/context.model';
|
import { Context } from '../../core/shared/context.model';
|
||||||
|
import { hasValue } from '../../shared/empty.util';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-browse-by-taxonomy-page',
|
selector: 'ds-browse-by-taxonomy',
|
||||||
templateUrl: './browse-by-taxonomy-page.component.html',
|
templateUrl: './browse-by-taxonomy.component.html',
|
||||||
styleUrls: ['./browse-by-taxonomy-page.component.scss']
|
styleUrls: ['./browse-by-taxonomy.component.scss'],
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* Component for browsing items by metadata in a hierarchical controlled vocabulary
|
* Component for browsing items by metadata in a hierarchical controlled vocabulary
|
||||||
*/
|
*/
|
||||||
@rendersBrowseBy(BrowseByDataType.Hierarchy)
|
@rendersBrowseBy(BrowseByDataType.Hierarchy)
|
||||||
export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy {
|
export class BrowseByTaxonomyComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The optional context
|
* The optional context
|
||||||
@@ -31,6 +32,18 @@ export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
@Input() browseByType: BrowseByDataType;
|
@Input() browseByType: BrowseByDataType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the {@link Community} or {@link Collection} of the scope to display
|
||||||
|
*/
|
||||||
|
@Input() scope: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the h1 title in the section
|
||||||
|
*/
|
||||||
|
@Input() displayTitle = true;
|
||||||
|
|
||||||
|
scope$: BehaviorSubject<string> = new BehaviorSubject(undefined);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link VocabularyOptions} object
|
* The {@link VocabularyOptions} object
|
||||||
*/
|
*/
|
||||||
@@ -59,7 +72,7 @@ export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* The parameters used in the URL
|
* The parameters used in the URL
|
||||||
*/
|
*/
|
||||||
queryParams: any;
|
queryParams: Params;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolved browse-by definition
|
* Resolved browse-by definition
|
||||||
@@ -87,6 +100,13 @@ export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy {
|
|||||||
this.vocabularyName = browseDefinition.vocabulary;
|
this.vocabularyName = browseDefinition.vocabulary;
|
||||||
this.vocabularyOptions = { name: this.vocabularyName, closed: true };
|
this.vocabularyOptions = { name: this.vocabularyName, closed: true };
|
||||||
}));
|
}));
|
||||||
|
this.subs.push(this.scope$.subscribe(() => {
|
||||||
|
this.updateQueryParams();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(): void {
|
||||||
|
this.scope$.next(this.scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,9 +116,9 @@ export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy {
|
|||||||
* @param detail VocabularyEntryDetail to be added
|
* @param detail VocabularyEntryDetail to be added
|
||||||
*/
|
*/
|
||||||
onSelect(detail: VocabularyEntryDetail): void {
|
onSelect(detail: VocabularyEntryDetail): void {
|
||||||
this.selectedItems.push(detail);
|
this.selectedItems.push(detail);
|
||||||
this.filterValues = this.selectedItems
|
this.filterValues = this.selectedItems
|
||||||
.map((item: VocabularyEntryDetail) => `${item.value},equals`);
|
.map((item: VocabularyEntryDetail) => `${item.value},equals`);
|
||||||
this.updateQueryParams();
|
this.updateQueryParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,18 +128,25 @@ export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy {
|
|||||||
* @param detail VocabularyEntryDetail to be removed
|
* @param detail VocabularyEntryDetail to be removed
|
||||||
*/
|
*/
|
||||||
onDeselect(detail: VocabularyEntryDetail): void {
|
onDeselect(detail: VocabularyEntryDetail): void {
|
||||||
this.selectedItems = this.selectedItems.filter((entry: VocabularyEntryDetail) => { return entry.id !== detail.id; });
|
this.selectedItems = this.selectedItems.filter((entry: VocabularyEntryDetail) => {
|
||||||
this.filterValues = this.filterValues.filter((value: string) => { return value !== `${detail.value},equals`; });
|
return entry.id !== detail.id;
|
||||||
|
});
|
||||||
|
this.filterValues = this.filterValues.filter((value: string) => {
|
||||||
|
return value !== `${detail.value},equals`;
|
||||||
|
});
|
||||||
this.updateQueryParams();
|
this.updateQueryParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates queryParams based on the current facetType and filterValues.
|
* Updates queryParams based on the current facetType and filterValues.
|
||||||
*/
|
*/
|
||||||
private updateQueryParams(): void {
|
updateQueryParams(): void {
|
||||||
this.queryParams = {
|
this.queryParams = {
|
||||||
['f.' + this.facetType]: this.filterValues
|
['f.' + this.facetType]: this.filterValues
|
||||||
};
|
};
|
||||||
|
if (hasValue(this.scope)) {
|
||||||
|
this.queryParams.scope = this.scope;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
@@ -1,62 +0,0 @@
|
|||||||
import { combineLatest as observableCombineLatest } from 'rxjs';
|
|
||||||
import { Component, Inject, OnInit } from '@angular/core';
|
|
||||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
|
||||||
import {
|
|
||||||
BrowseByMetadataPageComponent,
|
|
||||||
browseParamsToOptions, getBrowseSearchOptions
|
|
||||||
} from '../browse-by-metadata-page/browse-by-metadata-page.component';
|
|
||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
|
||||||
import { BrowseService } from '../../core/browse/browse.service';
|
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
|
||||||
import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface';
|
|
||||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
|
||||||
import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
|
||||||
import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-browse-by-title-page',
|
|
||||||
styleUrls: ['../browse-by-metadata-page/browse-by-metadata-page.component.scss'],
|
|
||||||
templateUrl: '../browse-by-metadata-page/browse-by-metadata-page.component.html'
|
|
||||||
})
|
|
||||||
/**
|
|
||||||
* Component for browsing items by title (dc.title)
|
|
||||||
*/
|
|
||||||
@rendersBrowseBy(BrowseByDataType.Title)
|
|
||||||
export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent implements OnInit {
|
|
||||||
|
|
||||||
public constructor(protected route: ActivatedRoute,
|
|
||||||
protected browseService: BrowseService,
|
|
||||||
protected dsoService: DSpaceObjectDataService,
|
|
||||||
protected paginationService: PaginationService,
|
|
||||||
protected router: Router,
|
|
||||||
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
|
||||||
public dsoNameService: DSONameService,
|
|
||||||
) {
|
|
||||||
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
const sortConfig = new SortOptions('dc.title', SortDirection.ASC);
|
|
||||||
// include the thumbnail configuration in browse search options
|
|
||||||
this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig, this.fetchThumbnails));
|
|
||||||
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
|
||||||
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
|
|
||||||
this.subs.push(
|
|
||||||
observableCombineLatest([this.route.params, this.route.queryParams, this.currentPagination$, this.currentSort$]).pipe(
|
|
||||||
map(([routeParams, queryParams, currentPage, currentSort]) => {
|
|
||||||
return [Object.assign({}, routeParams, queryParams),currentPage,currentSort];
|
|
||||||
})
|
|
||||||
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
|
||||||
this.startsWith = +params.startsWith || params.startsWith;
|
|
||||||
this.browseId = params.id || this.defaultBrowseId;
|
|
||||||
this.updatePageWithItems(browseParamsToOptions(params, currentPage, currentSort, this.browseId, this.fetchThumbnails), undefined, undefined);
|
|
||||||
this.updateParent(params.scope);
|
|
||||||
this.updateLogo();
|
|
||||||
}));
|
|
||||||
this.updateStartsWithTextOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -9,8 +9,8 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe';
|
import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { toRemoteData } from '../browse-by-metadata-page/browse-by-metadata-page.component.spec';
|
import { toRemoteData } from '../browse-by-metadata/browse-by-metadata.component.spec';
|
||||||
import { BrowseByTitlePageComponent } from './browse-by-title-page.component';
|
import { BrowseByTitleComponent } from './browse-by-title.component';
|
||||||
import { ItemDataService } from '../../core/data/item-data.service';
|
import { ItemDataService } from '../../core/data/item-data.service';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
@@ -24,9 +24,9 @@ import { APP_CONFIG } from '../../../config/app-config.interface';
|
|||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
|
|
||||||
|
|
||||||
describe('BrowseByTitlePageComponent', () => {
|
describe('BrowseByTitleComponent', () => {
|
||||||
let comp: BrowseByTitlePageComponent;
|
let comp: BrowseByTitleComponent;
|
||||||
let fixture: ComponentFixture<BrowseByTitlePageComponent>;
|
let fixture: ComponentFixture<BrowseByTitleComponent>;
|
||||||
let itemDataService: ItemDataService;
|
let itemDataService: ItemDataService;
|
||||||
let route: ActivatedRoute;
|
let route: ActivatedRoute;
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ describe('BrowseByTitlePageComponent', () => {
|
|||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
||||||
declarations: [BrowseByTitlePageComponent, EnumKeysPipe, VarDirective],
|
declarations: [BrowseByTitleComponent, EnumKeysPipe, VarDirective],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||||
{ provide: BrowseService, useValue: mockBrowseService },
|
{ provide: BrowseService, useValue: mockBrowseService },
|
||||||
@@ -85,7 +85,7 @@ describe('BrowseByTitlePageComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(BrowseByTitlePageComponent);
|
fixture = TestBed.createComponent(BrowseByTitleComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
itemDataService = (comp as any).itemDataService;
|
itemDataService = (comp as any).itemDataService;
|
@@ -0,0 +1,44 @@
|
|||||||
|
import { combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Params } from '@angular/router';
|
||||||
|
import {
|
||||||
|
BrowseByMetadataComponent,
|
||||||
|
browseParamsToOptions, getBrowseSearchOptions
|
||||||
|
} from '../browse-by-metadata/browse-by-metadata.component';
|
||||||
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
|
import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||||
|
import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-browse-by-title',
|
||||||
|
styleUrls: ['../browse-by-metadata/browse-by-metadata.component.scss'],
|
||||||
|
templateUrl: '../browse-by-metadata/browse-by-metadata.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component for browsing items by title (dc.title)
|
||||||
|
*/
|
||||||
|
@rendersBrowseBy(BrowseByDataType.Title)
|
||||||
|
export class BrowseByTitleComponent extends BrowseByMetadataComponent implements OnInit {
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
const sortConfig = new SortOptions('dc.title', SortDirection.ASC);
|
||||||
|
// include the thumbnail configuration in browse search options
|
||||||
|
this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig, this.fetchThumbnails));
|
||||||
|
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
||||||
|
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
|
||||||
|
this.subs.push(
|
||||||
|
observableCombineLatest([this.route.params, this.route.queryParams, this.scope$, this.currentPagination$, this.currentSort$]).pipe(
|
||||||
|
map(([routeParams, queryParams, scope, currentPage, currentSort]) => {
|
||||||
|
return [Object.assign({}, routeParams, queryParams), scope, currentPage, currentSort];
|
||||||
|
})
|
||||||
|
).subscribe(([params, scope, currentPage, currentSort]: [Params, string, PaginationComponentOptions, SortOptions]) => {
|
||||||
|
this.startsWith = +params.startsWith || params.startsWith;
|
||||||
|
this.browseId = params.id || this.defaultBrowseId;
|
||||||
|
this.updatePageWithItems(browseParamsToOptions(params, scope, currentPage, currentSort, this.browseId, this.fetchThumbnails), undefined, undefined);
|
||||||
|
}));
|
||||||
|
this.updateStartsWithTextOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,11 +1,10 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { BrowseByTitlePageComponent } from './browse-by-title-page/browse-by-title-page.component';
|
import { BrowseByTitleComponent } from './browse-by-title/browse-by-title.component';
|
||||||
import { BrowseByMetadataPageComponent } from './browse-by-metadata-page/browse-by-metadata-page.component';
|
import { BrowseByMetadataComponent } from './browse-by-metadata/browse-by-metadata.component';
|
||||||
import { BrowseByDatePageComponent } from './browse-by-date-page/browse-by-date-page.component';
|
import { BrowseByDateComponent } from './browse-by-date/browse-by-date.component';
|
||||||
import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switcher.component';
|
import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switcher.component';
|
||||||
import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page/browse-by-taxonomy-page.component';
|
import { BrowseByTaxonomyComponent } from './browse-by-taxonomy/browse-by-taxonomy.component';
|
||||||
import { ComcolModule } from '../shared/comcol/comcol.module';
|
|
||||||
import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.module';
|
import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.module';
|
||||||
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
||||||
import { FormModule } from '../shared/form/form.module';
|
import { FormModule } from '../shared/form/form.module';
|
||||||
@@ -17,17 +16,16 @@ const DECLARATIONS = [
|
|||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
BrowseByTitlePageComponent,
|
BrowseByTitleComponent,
|
||||||
BrowseByMetadataPageComponent,
|
BrowseByMetadataComponent,
|
||||||
BrowseByDatePageComponent,
|
BrowseByDateComponent,
|
||||||
BrowseByTaxonomyPageComponent,
|
BrowseByTaxonomyComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
SharedBrowseByModule,
|
SharedBrowseByModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
ComcolModule,
|
|
||||||
DsoPageModule,
|
DsoPageModule,
|
||||||
FormModule,
|
FormModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
|
@@ -22,6 +22,10 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
|||||||
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
|
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
|
||||||
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
||||||
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||||
|
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
|
||||||
|
import { BrowseByGuard } from '../browse-by/browse-by-guard';
|
||||||
|
import { BrowseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
|
||||||
|
import { CollectionRecentlyAddedComponent } from './sections/recently-added/collection-recently-added.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -65,7 +69,23 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
|||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: ThemedCollectionPageComponent,
|
component: ThemedCollectionPageComponent,
|
||||||
pathMatch: 'full',
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
pathMatch: 'full',
|
||||||
|
component: CollectionRecentlyAddedComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'browse/:id',
|
||||||
|
pathMatch: 'full',
|
||||||
|
component: ComcolBrowseByComponent,
|
||||||
|
canActivate: [BrowseByGuard],
|
||||||
|
resolve: {
|
||||||
|
breadcrumb: BrowseByI18nBreadcrumbResolver,
|
||||||
|
},
|
||||||
|
data: { breadcrumbKey: 'browse.metadata' },
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
data: {
|
data: {
|
||||||
|
@@ -1,78 +1,61 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="collection-page"
|
<div class="collection-page"
|
||||||
*ngVar="(collectionRD$ | async) as collectionRD">
|
*ngVar="(collectionRD$ | async) as collectionRD">
|
||||||
<div *ngIf="collectionRD?.hasSucceeded" @fadeInOut>
|
<div *ngIf="collectionRD?.hasSucceeded" @fadeInOut>
|
||||||
<div *ngIf="collectionRD?.payload as collection">
|
<div *ngIf="collectionRD?.payload as collection">
|
||||||
<ds-view-tracker [object]="collection"></ds-view-tracker>
|
<ds-view-tracker [object]="collection"></ds-view-tracker>
|
||||||
<div class="d-flex flex-row border-bottom mb-4 pb-4">
|
<div class="d-flex flex-row border-bottom mb-4 pb-4">
|
||||||
<header class="comcol-header mr-auto">
|
<header class="comcol-header mr-auto">
|
||||||
<!-- Collection Name -->
|
<!-- Collection Name -->
|
||||||
<ds-comcol-page-header
|
<ds-comcol-page-header
|
||||||
[name]="dsoNameService.getName(collection)">
|
[name]="dsoNameService.getName(collection)">
|
||||||
</ds-comcol-page-header>
|
</ds-comcol-page-header>
|
||||||
<!-- Collection logo -->
|
<!-- Collection logo -->
|
||||||
<ds-comcol-page-logo *ngIf="logoRD$"
|
<ds-comcol-page-logo *ngIf="logoRD$"
|
||||||
[logo]="(logoRD$ | async)?.payload"
|
[logo]="(logoRD$ | async)?.payload"
|
||||||
[alternateText]="'collection.logo' | translate">
|
[alternateText]="'collection.logo' | translate">
|
||||||
</ds-comcol-page-logo>
|
</ds-comcol-page-logo>
|
||||||
|
|
||||||
<!-- Handle -->
|
<!-- Handle -->
|
||||||
<ds-themed-comcol-page-handle
|
<ds-themed-comcol-page-handle
|
||||||
[content]="collection.handle"
|
[content]="collection.handle"
|
||||||
[title]="'collection.page.handle'" >
|
[title]="'collection.page.handle'">
|
||||||
</ds-themed-comcol-page-handle>
|
</ds-themed-comcol-page-handle>
|
||||||
<!-- Introductory text -->
|
<!-- Introductory text -->
|
||||||
<ds-comcol-page-content
|
<ds-comcol-page-content
|
||||||
[content]="collection.introductoryText"
|
[content]="collection.introductoryText"
|
||||||
[hasInnerHtml]="true">
|
[hasInnerHtml]="true">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
<!-- News -->
|
<!-- News -->
|
||||||
<ds-comcol-page-content
|
<ds-comcol-page-content
|
||||||
[content]="collection.sidebarText"
|
[content]="collection.sidebarText"
|
||||||
[hasInnerHtml]="true"
|
[hasInnerHtml]="true"
|
||||||
[title]="'collection.page.news'">
|
[title]="'collection.page.news'">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
</header>
|
</header>
|
||||||
<ds-dso-edit-menu></ds-dso-edit-menu>
|
<ds-dso-edit-menu></ds-dso-edit-menu>
|
||||||
</div>
|
</div>
|
||||||
<section class="comcol-page-browse-section">
|
<section class="comcol-page-browse-section">
|
||||||
<!-- Browse-By Links -->
|
<!-- Browse-By Links -->
|
||||||
<ds-themed-comcol-page-browse-by
|
<ds-themed-comcol-page-browse-by
|
||||||
[id]="collection.id"
|
[id]="collection.id"
|
||||||
[contentType]="collection.type">
|
[contentType]="collection.type">
|
||||||
</ds-themed-comcol-page-browse-by>
|
</ds-themed-comcol-page-browse-by>
|
||||||
|
|
||||||
<ng-container *ngVar="(itemRD$ | async) as itemRD">
|
<router-outlet></router-outlet>
|
||||||
<div class="mt-4" *ngIf="itemRD?.hasSucceeded" @fadeIn>
|
</section>
|
||||||
<h3 class="sr-only">{{'collection.page.browse.recent.head' | translate}}</h3>
|
<footer *ngIf="collection.copyrightText" class="border-top my-5 pt-4">
|
||||||
<ds-viewable-collection
|
|
||||||
[config]="paginationConfig"
|
|
||||||
[sortConfig]="sortConfig"
|
|
||||||
[objects]="itemRD"
|
|
||||||
[hideGear]="true">
|
|
||||||
</ds-viewable-collection>
|
|
||||||
</div>
|
|
||||||
<ds-error *ngIf="itemRD?.hasFailed"
|
|
||||||
message="{{'error.recent-submissions' | translate}}"></ds-error>
|
|
||||||
<ds-themed-loading *ngIf="!itemRD || itemRD.isLoading"
|
|
||||||
message="{{'loading.recent-submissions' | translate}}"></ds-themed-loading>
|
|
||||||
<div *ngIf="!itemRD?.isLoading && itemRD?.payload?.page.length === 0" class="alert alert-info w-100" role="alert">
|
|
||||||
{{'collection.page.browse.recent.empty' | translate}}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</section>
|
|
||||||
<footer *ngIf="collection.copyrightText" class="border-top my-5 pt-4">
|
|
||||||
<!-- Copyright -->
|
<!-- Copyright -->
|
||||||
<ds-comcol-page-content
|
<ds-comcol-page-content
|
||||||
[content]="collection.copyrightText"
|
[content]="collection.copyrightText"
|
||||||
[hasInnerHtml]="true">
|
[hasInnerHtml]="true">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ds-error *ngIf="collectionRD?.hasFailed"
|
<ds-error *ngIf="collectionRD?.hasFailed"
|
||||||
message="{{'error.collection' | translate}}"></ds-error>
|
message="{{'error.collection' | translate}}"></ds-error>
|
||||||
<ds-themed-loading *ngIf="collectionRD?.isLoading"
|
<ds-themed-loading *ngIf="collectionRD?.isLoading"
|
||||||
message="{{'loading.collection' | translate}}"></ds-themed-loading>
|
message="{{'loading.collection' | translate}}"></ds-themed-loading>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,36 +1,21 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit, Inject } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subject } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { filter, map, mergeMap, startWith, switchMap, take } from 'rxjs/operators';
|
import { filter, map, mergeMap, take } from 'rxjs/operators';
|
||||||
import { PaginatedSearchOptions } from '../shared/search/models/paginated-search-options.model';
|
import { SortOptions } from '../core/cache/models/sort-options.model';
|
||||||
import { SearchService } from '../core/shared/search/search.service';
|
|
||||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
|
||||||
import { CollectionDataService } from '../core/data/collection-data.service';
|
|
||||||
import { PaginatedList } from '../core/data/paginated-list.model';
|
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { Bitstream } from '../core/shared/bitstream.model';
|
import { Bitstream } from '../core/shared/bitstream.model';
|
||||||
|
|
||||||
import { Collection } from '../core/shared/collection.model';
|
import { Collection } from '../core/shared/collection.model';
|
||||||
import { DSpaceObjectType } from '../core/shared/dspace-object-type.model';
|
import { getAllSucceededRemoteDataPayload } from '../core/shared/operators';
|
||||||
import { Item } from '../core/shared/item.model';
|
|
||||||
import {
|
|
||||||
getAllSucceededRemoteDataPayload,
|
|
||||||
getFirstSucceededRemoteData,
|
|
||||||
toDSpaceObjectListRD
|
|
||||||
} from '../core/shared/operators';
|
|
||||||
|
|
||||||
import { fadeIn, fadeInOut } from '../shared/animations/fade';
|
import { fadeIn, fadeInOut } from '../shared/animations/fade';
|
||||||
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
||||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||||
import { AuthService } from '../core/auth/auth.service';
|
import { AuthService } from '../core/auth/auth.service';
|
||||||
import { PaginationService } from '../core/pagination/pagination.service';
|
|
||||||
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
|
||||||
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
||||||
import { getCollectionPageRoute } from './collection-page-routing-paths';
|
import { getCollectionPageRoute } from './collection-page-routing-paths';
|
||||||
import { redirectOn4xx } from '../core/shared/authorized.operators';
|
import { redirectOn4xx } from '../core/shared/authorized.operators';
|
||||||
import { BROWSE_LINKS_TO_FOLLOW } from '../core/browse/browse.service';
|
|
||||||
import { DSONameService } from '../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../core/breadcrumbs/dso-name.service';
|
||||||
import { APP_CONFIG, AppConfig } from '../../../src/config/app-config.interface';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-collection-page',
|
selector: 'ds-collection-page',
|
||||||
@@ -44,14 +29,9 @@ import { APP_CONFIG, AppConfig } from '../../../src/config/app-config.interface'
|
|||||||
})
|
})
|
||||||
export class CollectionPageComponent implements OnInit {
|
export class CollectionPageComponent implements OnInit {
|
||||||
collectionRD$: Observable<RemoteData<Collection>>;
|
collectionRD$: Observable<RemoteData<Collection>>;
|
||||||
itemRD$: Observable<RemoteData<PaginatedList<Item>>>;
|
|
||||||
logoRD$: Observable<RemoteData<Bitstream>>;
|
logoRD$: Observable<RemoteData<Bitstream>>;
|
||||||
paginationConfig: PaginationComponentOptions;
|
paginationConfig: PaginationComponentOptions;
|
||||||
sortConfig: SortOptions;
|
sortConfig: SortOptions;
|
||||||
private paginationChanges$: Subject<{
|
|
||||||
paginationConfig: PaginationComponentOptions,
|
|
||||||
sortConfig: SortOptions
|
|
||||||
}>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the current user is a Community admin
|
* Whether the current user is a Community admin
|
||||||
@@ -64,23 +44,12 @@ export class CollectionPageComponent implements OnInit {
|
|||||||
collectionPageRoute$: Observable<string>;
|
collectionPageRoute$: Observable<string>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private collectionDataService: CollectionDataService,
|
protected route: ActivatedRoute,
|
||||||
private searchService: SearchService,
|
protected router: Router,
|
||||||
private route: ActivatedRoute,
|
protected authService: AuthService,
|
||||||
private router: Router,
|
protected authorizationDataService: AuthorizationDataService,
|
||||||
private authService: AuthService,
|
|
||||||
private paginationService: PaginationService,
|
|
||||||
private authorizationDataService: AuthorizationDataService,
|
|
||||||
public dsoNameService: DSONameService,
|
public dsoNameService: DSONameService,
|
||||||
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
|
||||||
) {
|
) {
|
||||||
this.paginationConfig = Object.assign(new PaginationComponentOptions(), {
|
|
||||||
id: 'cp',
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: this.appConfig.browseBy.pageSize,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.sortConfig = new SortOptions('dc.date.accessioned', SortDirection.DESC);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@@ -96,33 +65,6 @@ export class CollectionPageComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
this.isCollectionAdmin$ = this.authorizationDataService.isAuthorized(FeatureID.IsCollectionAdmin);
|
this.isCollectionAdmin$ = this.authorizationDataService.isAuthorized(FeatureID.IsCollectionAdmin);
|
||||||
|
|
||||||
this.paginationChanges$ = new BehaviorSubject({
|
|
||||||
paginationConfig: this.paginationConfig,
|
|
||||||
sortConfig: this.sortConfig
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
|
||||||
const currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig);
|
|
||||||
|
|
||||||
this.itemRD$ = observableCombineLatest([currentPagination$, currentSort$]).pipe(
|
|
||||||
switchMap(([currentPagination, currentSort]) => this.collectionRD$.pipe(
|
|
||||||
getFirstSucceededRemoteData(),
|
|
||||||
map((rd) => rd.payload.id),
|
|
||||||
switchMap((id: string) => {
|
|
||||||
return this.searchService.search<Item>(
|
|
||||||
new PaginatedSearchOptions({
|
|
||||||
scope: id,
|
|
||||||
pagination: currentPagination,
|
|
||||||
sort: currentSort,
|
|
||||||
dsoTypes: [DSpaceObjectType.ITEM]
|
|
||||||
}), null, true, true, ...BROWSE_LINKS_TO_FOLLOW)
|
|
||||||
.pipe(toDSpaceObjectListRD()) as Observable<RemoteData<PaginatedList<Item>>>;
|
|
||||||
}),
|
|
||||||
startWith(undefined) // Make sure switching pages shows loading component
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.collectionPageRoute$ = this.collectionRD$.pipe(
|
this.collectionPageRoute$ = this.collectionRD$.pipe(
|
||||||
getAllSucceededRemoteDataPayload(),
|
getAllSucceededRemoteDataPayload(),
|
||||||
map((collection) => getCollectionPageRoute(collection.id))
|
map((collection) => getCollectionPageRoute(collection.id))
|
||||||
@@ -133,9 +75,5 @@ export class CollectionPageComponent implements OnInit {
|
|||||||
return isNotEmpty(object);
|
return isNotEmpty(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.paginationService.clearPagination(this.paginationConfig.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,19 @@ import { ThemedCollectionPageComponent } from './themed-collection-page.componen
|
|||||||
import { ComcolModule } from '../shared/comcol/comcol.module';
|
import { ComcolModule } from '../shared/comcol/comcol.module';
|
||||||
import { DsoSharedModule } from '../dso-shared/dso-shared.module';
|
import { DsoSharedModule } from '../dso-shared/dso-shared.module';
|
||||||
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
||||||
|
import { BrowseByPageModule } from '../browse-by/browse-by-page.module';
|
||||||
|
import { CollectionRecentlyAddedComponent } from './sections/recently-added/collection-recently-added.component';
|
||||||
|
|
||||||
|
const DECLARATIONS = [
|
||||||
|
CollectionPageComponent,
|
||||||
|
ThemedCollectionPageComponent,
|
||||||
|
CreateCollectionPageComponent,
|
||||||
|
DeleteCollectionPageComponent,
|
||||||
|
EditItemTemplatePageComponent,
|
||||||
|
ThemedEditItemTemplatePageComponent,
|
||||||
|
CollectionItemMapperComponent,
|
||||||
|
CollectionRecentlyAddedComponent,
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -30,15 +43,10 @@ import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
|||||||
ComcolModule,
|
ComcolModule,
|
||||||
DsoSharedModule,
|
DsoSharedModule,
|
||||||
DsoPageModule,
|
DsoPageModule,
|
||||||
|
BrowseByPageModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
CollectionPageComponent,
|
...DECLARATIONS,
|
||||||
ThemedCollectionPageComponent,
|
|
||||||
CreateCollectionPageComponent,
|
|
||||||
DeleteCollectionPageComponent,
|
|
||||||
EditItemTemplatePageComponent,
|
|
||||||
ThemedEditItemTemplatePageComponent,
|
|
||||||
CollectionItemMapperComponent
|
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SearchService,
|
SearchService,
|
||||||
|
@@ -0,0 +1,18 @@
|
|||||||
|
<ng-container *ngVar="(itemRD$ | async) as itemRD">
|
||||||
|
<div class="mt-4" *ngIf="itemRD?.hasSucceeded" @fadeIn>
|
||||||
|
<h3 class="sr-only">{{'collection.page.browse.recent.head' | translate}}</h3>
|
||||||
|
<ds-viewable-collection
|
||||||
|
[config]="paginationConfig"
|
||||||
|
[sortConfig]="sortConfig"
|
||||||
|
[objects]="itemRD"
|
||||||
|
[hideGear]="true">
|
||||||
|
</ds-viewable-collection>
|
||||||
|
</div>
|
||||||
|
<ds-error *ngIf="itemRD?.hasFailed"
|
||||||
|
message="{{'error.recent-submissions' | translate}}"></ds-error>
|
||||||
|
<ds-themed-loading *ngIf="!itemRD || itemRD.isLoading"
|
||||||
|
message="{{'loading.recent-submissions' | translate}}"></ds-themed-loading>
|
||||||
|
<div *ngIf="!itemRD?.isLoading && itemRD?.payload?.page.length === 0" class="alert alert-info w-100" role="alert">
|
||||||
|
{{'collection.page.browse.recent.empty' | translate}}
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
@@ -0,0 +1,53 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { CollectionRecentlyAddedComponent } from './collection-recently-added.component';
|
||||||
|
import { APP_CONFIG } from '../../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../../environments/environment.test';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||||
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
|
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||||
|
import { SearchServiceStub } from '../../../shared/testing/search-service.stub';
|
||||||
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
|
import { VarDirective } from '../../../shared/utils/var.directive';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
|
|
||||||
|
describe('CollectionRecentlyAddedComponent', () => {
|
||||||
|
let component: CollectionRecentlyAddedComponent;
|
||||||
|
let fixture: ComponentFixture<CollectionRecentlyAddedComponent>;
|
||||||
|
|
||||||
|
let activatedRoute: ActivatedRouteStub;
|
||||||
|
let paginationService: PaginationServiceStub;
|
||||||
|
let searchService: SearchServiceStub;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
activatedRoute = new ActivatedRouteStub();
|
||||||
|
paginationService = new PaginationServiceStub();
|
||||||
|
searchService = new SearchServiceStub();
|
||||||
|
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [
|
||||||
|
CollectionRecentlyAddedComponent,
|
||||||
|
VarDirective,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: ActivatedRoute, useValue: activatedRoute },
|
||||||
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
|
{ provide: PaginationService, useValue: paginationService },
|
||||||
|
{ provide: SearchService, useValue: SearchServiceStub },
|
||||||
|
],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(CollectionRecentlyAddedComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,82 @@
|
|||||||
|
import { Component, OnInit, Inject, OnDestroy } from '@angular/core';
|
||||||
|
import { Observable, combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
import { switchMap, map, startWith, take } from 'rxjs/operators';
|
||||||
|
import { getFirstSucceededRemoteData, toDSpaceObjectListRD } from '../../../core/shared/operators';
|
||||||
|
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||||
|
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
||||||
|
import { BROWSE_LINKS_TO_FOLLOW } from '../../../core/browse/browse.service';
|
||||||
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
|
import { SortOptions, SortDirection } from '../../../core/cache/models/sort-options.model';
|
||||||
|
import { APP_CONFIG, AppConfig } from '../../../../config/app-config.interface';
|
||||||
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
|
import { ActivatedRoute, Data } from '@angular/router';
|
||||||
|
import { fadeIn } from '../../../shared/animations/fade';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-collection-recently-added',
|
||||||
|
templateUrl: './collection-recently-added.component.html',
|
||||||
|
styleUrls: ['./collection-recently-added.component.scss'],
|
||||||
|
animations: [fadeIn],
|
||||||
|
})
|
||||||
|
export class CollectionRecentlyAddedComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
paginationConfig: PaginationComponentOptions;
|
||||||
|
|
||||||
|
sortConfig: SortOptions;
|
||||||
|
|
||||||
|
collectionRD$: Observable<RemoteData<Collection>>;
|
||||||
|
|
||||||
|
itemRD$: Observable<RemoteData<PaginatedList<Item>>>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
|
protected paginationService: PaginationService,
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
protected searchService: SearchService,
|
||||||
|
) {
|
||||||
|
this.paginationConfig = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: 'cp',
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: this.appConfig.browseBy.pageSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.sortConfig = new SortOptions('dc.date.accessioned', SortDirection.DESC);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.collectionRD$ = this.route.data.pipe(
|
||||||
|
map((data: Data) => data.dso as RemoteData<Collection>),
|
||||||
|
take(1),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.itemRD$ = observableCombineLatest([
|
||||||
|
this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig),
|
||||||
|
this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig),
|
||||||
|
]).pipe(
|
||||||
|
switchMap(([currentPagination, currentSort]: [PaginationComponentOptions, SortOptions]) => this.collectionRD$.pipe(
|
||||||
|
getFirstSucceededRemoteData(),
|
||||||
|
map((rd: RemoteData<Collection>) => rd.payload.id),
|
||||||
|
switchMap((id: string) => this.searchService.search<Item>(
|
||||||
|
new PaginatedSearchOptions({
|
||||||
|
scope: id,
|
||||||
|
pagination: currentPagination,
|
||||||
|
sort: currentSort,
|
||||||
|
dsoTypes: [DSpaceObjectType.ITEM]
|
||||||
|
}), null, true, true, ...BROWSE_LINKS_TO_FOLLOW).pipe(
|
||||||
|
toDSpaceObjectListRD()
|
||||||
|
) as Observable<RemoteData<PaginatedList<Item>>>),
|
||||||
|
startWith(undefined), // Make sure switching pages shows loading component
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.paginationService.clearPagination(this.paginationConfig.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -15,6 +15,10 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
|||||||
import { ThemedCommunityPageComponent } from './themed-community-page.component';
|
import { ThemedCommunityPageComponent } from './themed-community-page.component';
|
||||||
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
||||||
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||||
|
import { SubComColSectionComponent } from './sections/sub-com-col-section/sub-com-col-section.component';
|
||||||
|
import { BrowseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
|
||||||
|
import { BrowseByGuard } from '../browse-by/browse-by-guard';
|
||||||
|
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -48,7 +52,23 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
|||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: ThemedCommunityPageComponent,
|
component: ThemedCommunityPageComponent,
|
||||||
pathMatch: 'full',
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
pathMatch: 'full',
|
||||||
|
component: SubComColSectionComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'browse/:id',
|
||||||
|
pathMatch: 'full',
|
||||||
|
component: ComcolBrowseByComponent,
|
||||||
|
canActivate: [BrowseByGuard],
|
||||||
|
resolve: {
|
||||||
|
breadcrumb: BrowseByI18nBreadcrumbResolver,
|
||||||
|
},
|
||||||
|
data: { breadcrumbKey: 'browse.metadata' },
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
data: {
|
data: {
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
<!-- News -->
|
<!-- News -->
|
||||||
<ds-comcol-page-content [content]="communityPayload.sidebarText" [hasInnerHtml]="true"
|
<ds-comcol-page-content [content]="communityPayload.sidebarText" [hasInnerHtml]="true"
|
||||||
[title]="'community.page.news'">
|
[title]="'community.page.news'">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
</header>
|
</header>
|
||||||
<ds-dso-edit-menu></ds-dso-edit-menu>
|
<ds-dso-edit-menu></ds-dso-edit-menu>
|
||||||
@@ -28,10 +28,9 @@
|
|||||||
<ds-themed-comcol-page-browse-by [id]="communityPayload.id" [contentType]="communityPayload.type">
|
<ds-themed-comcol-page-browse-by [id]="communityPayload.id" [contentType]="communityPayload.type">
|
||||||
</ds-themed-comcol-page-browse-by>
|
</ds-themed-comcol-page-browse-by>
|
||||||
|
|
||||||
<ds-themed-community-page-sub-community-list [community]="communityPayload"></ds-themed-community-page-sub-community-list>
|
<router-outlet></router-outlet>
|
||||||
<ds-themed-community-page-sub-collection-list [community]="communityPayload"></ds-themed-community-page-sub-collection-list>
|
|
||||||
</section>
|
</section>
|
||||||
<footer *ngIf="communityPayload.copyrightText" class="border-top my-5 pt-4">
|
<footer *ngIf="communityPayload.copyrightText" class="border-top my-5 pt-4">
|
||||||
<!-- Copyright -->
|
<!-- Copyright -->
|
||||||
<ds-comcol-page-content [content]="communityPayload.copyrightText" [hasInnerHtml]="true">
|
<ds-comcol-page-content [content]="communityPayload.copyrightText" [hasInnerHtml]="true">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
|
@@ -1,16 +1,10 @@
|
|||||||
import { mergeMap, filter, map } from 'rxjs/operators';
|
import { mergeMap, filter, map } from 'rxjs/operators';
|
||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { CommunityDataService } from '../core/data/community-data.service';
|
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { Bitstream } from '../core/shared/bitstream.model';
|
import { Bitstream } from '../core/shared/bitstream.model';
|
||||||
|
|
||||||
import { Community } from '../core/shared/community.model';
|
import { Community } from '../core/shared/community.model';
|
||||||
|
|
||||||
import { MetadataService } from '../core/metadata/metadata.service';
|
|
||||||
|
|
||||||
import { fadeInOut } from '../shared/animations/fade';
|
import { fadeInOut } from '../shared/animations/fade';
|
||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
import { getAllSucceededRemoteDataPayload} from '../core/shared/operators';
|
import { getAllSucceededRemoteDataPayload} from '../core/shared/operators';
|
||||||
@@ -53,8 +47,6 @@ export class CommunityPageComponent implements OnInit {
|
|||||||
communityPageRoute$: Observable<string>;
|
communityPageRoute$: Observable<string>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private communityDataService: CommunityDataService,
|
|
||||||
private metadata: MetadataService,
|
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
|
@@ -4,9 +4,9 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
|
||||||
import { CommunityPageComponent } from './community-page.component';
|
import { CommunityPageComponent } from './community-page.component';
|
||||||
import { CommunityPageSubCollectionListComponent } from './sub-collection-list/community-page-sub-collection-list.component';
|
import { CommunityPageSubCollectionListComponent } from './sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component';
|
||||||
import { CommunityPageRoutingModule } from './community-page-routing.module';
|
import { CommunityPageRoutingModule } from './community-page-routing.module';
|
||||||
import { CommunityPageSubCommunityListComponent } from './sub-community-list/community-page-sub-community-list.component';
|
import { CommunityPageSubCommunityListComponent } from './sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component';
|
||||||
import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component';
|
import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component';
|
||||||
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
|
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
|
||||||
import { StatisticsModule } from '../statistics/statistics.module';
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
@@ -15,20 +15,25 @@ import { ThemedCommunityPageComponent } from './themed-community-page.component'
|
|||||||
import { ComcolModule } from '../shared/comcol/comcol.module';
|
import { ComcolModule } from '../shared/comcol/comcol.module';
|
||||||
import {
|
import {
|
||||||
ThemedCommunityPageSubCommunityListComponent
|
ThemedCommunityPageSubCommunityListComponent
|
||||||
} from './sub-community-list/themed-community-page-sub-community-list.component';
|
} from './sections/sub-com-col-section/sub-community-list/themed-community-page-sub-community-list.component';
|
||||||
import {
|
import {
|
||||||
ThemedCollectionPageSubCollectionListComponent
|
ThemedCollectionPageSubCollectionListComponent
|
||||||
} from './sub-collection-list/themed-community-page-sub-collection-list.component';
|
} from './sections/sub-com-col-section/sub-collection-list/themed-community-page-sub-collection-list.component';
|
||||||
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
||||||
|
import { SubComColSectionComponent } from './sections/sub-com-col-section/sub-com-col-section.component';
|
||||||
|
import { BrowseByPageModule } from '../browse-by/browse-by-page.module';
|
||||||
|
|
||||||
const DECLARATIONS = [CommunityPageComponent,
|
const DECLARATIONS = [
|
||||||
|
CommunityPageComponent,
|
||||||
ThemedCommunityPageComponent,
|
ThemedCommunityPageComponent,
|
||||||
ThemedCommunityPageSubCommunityListComponent,
|
ThemedCommunityPageSubCommunityListComponent,
|
||||||
CommunityPageSubCollectionListComponent,
|
CommunityPageSubCollectionListComponent,
|
||||||
ThemedCollectionPageSubCollectionListComponent,
|
ThemedCollectionPageSubCollectionListComponent,
|
||||||
CommunityPageSubCommunityListComponent,
|
CommunityPageSubCommunityListComponent,
|
||||||
CreateCommunityPageComponent,
|
CreateCommunityPageComponent,
|
||||||
DeleteCommunityPageComponent];
|
DeleteCommunityPageComponent,
|
||||||
|
SubComColSectionComponent,
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -39,6 +44,7 @@ const DECLARATIONS = [CommunityPageComponent,
|
|||||||
CommunityFormModule,
|
CommunityFormModule,
|
||||||
ComcolModule,
|
ComcolModule,
|
||||||
DsoPageModule,
|
DsoPageModule,
|
||||||
|
BrowseByPageModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
...DECLARATIONS
|
...DECLARATIONS
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<ng-container *ngVar="(subCollectionsRDObs | async) as subCollectionsRD">
|
<ng-container *ngVar="(subCollectionsRDObs | async) as subCollectionsRD">
|
||||||
<div *ngIf="subCollectionsRD?.hasSucceeded && subCollectionsRD?.payload.totalElements > 0" @fadeIn>
|
<div *ngIf="subCollectionsRD?.hasSucceeded && subCollectionsRD?.payload.totalElements > 0" @fadeIn>
|
||||||
<h2>{{'community.sub-collection-list.head' | translate}}</h2>
|
<h3>{{'community.sub-collection-list.head' | translate}}</h3>
|
||||||
<ds-viewable-collection
|
<ds-viewable-collection
|
||||||
[config]="config"
|
[config]="config"
|
||||||
[sortConfig]="sortConfig"
|
[sortConfig]="sortConfig"
|
@@ -8,27 +8,27 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
|||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
import { CommunityPageSubCollectionListComponent } from './community-page-sub-collection-list.component';
|
import { CommunityPageSubCollectionListComponent } from './community-page-sub-collection-list.component';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../../../shared/shared.module';
|
||||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../../../core/data/collection-data.service';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
|
||||||
import { buildPaginatedList } from '../../core/data/paginated-list.model';
|
import { buildPaginatedList } from '../../../../core/data/paginated-list.model';
|
||||||
import { PageInfo } from '../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
import { HostWindowService } from '../../shared/host-window.service';
|
import { HostWindowService } from '../../../../shared/host-window.service';
|
||||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
|
import { HostWindowServiceStub } from '../../../../shared/testing/host-window-service.stub';
|
||||||
import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service';
|
import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||||
import { getMockThemeService } from '../../shared/mocks/theme-service.mock';
|
import { getMockThemeService } from '../../../../shared/mocks/theme-service.mock';
|
||||||
import { ThemeService } from '../../shared/theme-support/theme.service';
|
import { ThemeService } from '../../../../shared/theme-support/theme.service';
|
||||||
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub';
|
||||||
import { FindListOptions } from '../../core/data/find-list-options.model';
|
import { FindListOptions } from '../../../../core/data/find-list-options.model';
|
||||||
import { GroupDataService } from '../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
import { LinkHeadService } from '../../core/services/link-head.service';
|
import { LinkHeadService } from '../../../../core/services/link-head.service';
|
||||||
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
|
import { ConfigurationDataService } from '../../../../core/data/configuration-data.service';
|
||||||
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service';
|
||||||
import { ConfigurationProperty } from '../../core/shared/configuration-property.model';
|
import { ConfigurationProperty } from '../../../../core/shared/configuration-property.model';
|
||||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../../../shared/testing/utils.test';
|
||||||
import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-service.stub';
|
import { SearchConfigurationServiceStub } from '../../../../shared/testing/search-configuration-service.stub';
|
||||||
|
|
||||||
describe('CommunityPageSubCollectionList Component', () => {
|
describe('CommunityPageSubCollectionList Component', () => {
|
||||||
let comp: CommunityPageSubCollectionListComponent;
|
let comp: CommunityPageSubCollectionListComponent;
|
@@ -1,19 +1,17 @@
|
|||||||
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription } from 'rxjs';
|
import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription } from 'rxjs';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { fadeIn } from '../../../../shared/animations/fade';
|
||||||
import { fadeIn } from '../../shared/animations/fade';
|
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
||||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model';
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { CollectionDataService } from '../../../../core/data/collection-data.service';
|
||||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../../../shared/empty.util';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-community-page-sub-collection-list',
|
selector: 'ds-community-page-sub-collection-list',
|
@@ -1,12 +1,12 @@
|
|||||||
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
import { ThemedComponent } from '../../../../shared/theme-support/themed.component';
|
||||||
import { CommunityPageSubCollectionListComponent } from './community-page-sub-collection-list.component';
|
import { CommunityPageSubCollectionListComponent } from './community-page-sub-collection-list.component';
|
||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-themed-community-page-sub-collection-list',
|
selector: 'ds-themed-community-page-sub-collection-list',
|
||||||
styleUrls: [],
|
styleUrls: [],
|
||||||
templateUrl: '../../shared/theme-support/themed.component.html',
|
templateUrl: '../../../../shared/theme-support/themed.component.html',
|
||||||
})
|
})
|
||||||
export class ThemedCollectionPageSubCollectionListComponent extends ThemedComponent<CommunityPageSubCollectionListComponent> {
|
export class ThemedCollectionPageSubCollectionListComponent extends ThemedComponent<CommunityPageSubCollectionListComponent> {
|
||||||
@Input() community: Community;
|
@Input() community: Community;
|
||||||
@@ -18,7 +18,7 @@ export class ThemedCollectionPageSubCollectionListComponent extends ThemedCompon
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected importThemedComponent(themeName: string): Promise<any> {
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
return import(`../../../themes/${themeName}/app/community-page/sub-collection-list/community-page-sub-collection-list.component`);
|
return import(`../../../../../themes/${themeName}/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component`);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected importUnthemedComponent(): Promise<any> {
|
protected importUnthemedComponent(): Promise<any> {
|
@@ -0,0 +1,8 @@
|
|||||||
|
<ng-container *ngIf="(community$ | async) as community">
|
||||||
|
<ds-themed-community-page-sub-community-list
|
||||||
|
[community]="community">
|
||||||
|
</ds-themed-community-page-sub-community-list>
|
||||||
|
<ds-themed-community-page-sub-collection-list
|
||||||
|
[community]="community">
|
||||||
|
</ds-themed-community-page-sub-collection-list>
|
||||||
|
</ng-container>
|
@@ -0,0 +1,32 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { SubComColSectionComponent } from './sub-com-col-section.component';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||||
|
|
||||||
|
describe('SubComColSectionComponent', () => {
|
||||||
|
let component: SubComColSectionComponent;
|
||||||
|
let fixture: ComponentFixture<SubComColSectionComponent>;
|
||||||
|
|
||||||
|
let activatedRoute: ActivatedRouteStub;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
activatedRoute = new ActivatedRouteStub();
|
||||||
|
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [
|
||||||
|
SubComColSectionComponent,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: ActivatedRoute, useValue: activatedRoute },
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(SubComColSectionComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,28 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { Community } from '../../../core/shared/community.model';
|
||||||
|
import { ActivatedRoute, Data } from '@angular/router';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-sub-com-col-section',
|
||||||
|
templateUrl: './sub-com-col-section.component.html',
|
||||||
|
styleUrls: ['./sub-com-col-section.component.scss'],
|
||||||
|
})
|
||||||
|
export class SubComColSectionComponent implements OnInit {
|
||||||
|
|
||||||
|
community$: Observable<Community>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.community$ = this.route.data.pipe(
|
||||||
|
map((data: Data) => (data.dso as RemoteData<Community>).payload),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
<ng-container *ngVar="(subCommunitiesRDObs | async) as subCommunitiesRD">
|
<ng-container *ngVar="(subCommunitiesRDObs | async) as subCommunitiesRD">
|
||||||
<div *ngIf="subCommunitiesRD?.hasSucceeded && subCommunitiesRD?.payload.totalElements > 0" @fadeIn>
|
<div *ngIf="subCommunitiesRD?.hasSucceeded && subCommunitiesRD?.payload.totalElements > 0" @fadeIn>
|
||||||
<h2>{{'community.sub-community-list.head' | translate}}</h2>
|
<h3>{{'community.sub-community-list.head' | translate}}</h3>
|
||||||
<ds-viewable-collection
|
<ds-viewable-collection
|
||||||
[config]="config"
|
[config]="config"
|
||||||
[sortConfig]="sortConfig"
|
[sortConfig]="sortConfig"
|
@@ -8,27 +8,27 @@ import { By } from '@angular/platform-browser';
|
|||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
import { CommunityPageSubCommunityListComponent } from './community-page-sub-community-list.component';
|
import { CommunityPageSubCommunityListComponent } from './community-page-sub-community-list.component';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
import { buildPaginatedList } from '../../core/data/paginated-list.model';
|
import { buildPaginatedList } from '../../../../core/data/paginated-list.model';
|
||||||
import { PageInfo } from '../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../../../shared/shared.module';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
|
||||||
import { HostWindowService } from '../../shared/host-window.service';
|
import { HostWindowService } from '../../../../shared/host-window.service';
|
||||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
|
import { HostWindowServiceStub } from '../../../../shared/testing/host-window-service.stub';
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../../../core/data/community-data.service';
|
||||||
import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service';
|
import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||||
import { getMockThemeService } from '../../shared/mocks/theme-service.mock';
|
import { getMockThemeService } from '../../../../shared/mocks/theme-service.mock';
|
||||||
import { ThemeService } from '../../shared/theme-support/theme.service';
|
import { ThemeService } from '../../../../shared/theme-support/theme.service';
|
||||||
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub';
|
||||||
import { FindListOptions } from '../../core/data/find-list-options.model';
|
import { FindListOptions } from '../../../../core/data/find-list-options.model';
|
||||||
import { GroupDataService } from '../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
import { LinkHeadService } from '../../core/services/link-head.service';
|
import { LinkHeadService } from '../../../../core/services/link-head.service';
|
||||||
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
|
import { ConfigurationDataService } from '../../../../core/data/configuration-data.service';
|
||||||
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service';
|
||||||
import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-service.stub';
|
import { SearchConfigurationServiceStub } from '../../../../shared/testing/search-configuration-service.stub';
|
||||||
import { ConfigurationProperty } from '../../core/shared/configuration-property.model';
|
import { ConfigurationProperty } from '../../../../core/shared/configuration-property.model';
|
||||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../../../shared/testing/utils.test';
|
||||||
|
|
||||||
describe('CommunityPageSubCommunityListComponent Component', () => {
|
describe('CommunityPageSubCommunityListComponent Component', () => {
|
||||||
let comp: CommunityPageSubCommunityListComponent;
|
let comp: CommunityPageSubCommunityListComponent;
|
@@ -3,16 +3,16 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
|
|
||||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription } from 'rxjs';
|
import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription } from 'rxjs';
|
||||||
|
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
import { fadeIn } from '../../shared/animations/fade';
|
import { fadeIn } from '../../../../shared/animations/fade';
|
||||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model';
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../../../core/data/community-data.service';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../../../shared/empty.util';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-community-page-sub-community-list',
|
selector: 'ds-community-page-sub-community-list',
|
@@ -1,12 +1,12 @@
|
|||||||
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
import { ThemedComponent } from '../../../../shared/theme-support/themed.component';
|
||||||
import { CommunityPageSubCommunityListComponent } from './community-page-sub-community-list.component';
|
import { CommunityPageSubCommunityListComponent } from './community-page-sub-community-list.component';
|
||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-themed-community-page-sub-community-list',
|
selector: 'ds-themed-community-page-sub-community-list',
|
||||||
styleUrls: [],
|
styleUrls: [],
|
||||||
templateUrl: '../../shared/theme-support/themed.component.html',
|
templateUrl: '../../../../shared/theme-support/themed.component.html',
|
||||||
})
|
})
|
||||||
export class ThemedCommunityPageSubCommunityListComponent extends ThemedComponent<CommunityPageSubCommunityListComponent> {
|
export class ThemedCommunityPageSubCommunityListComponent extends ThemedComponent<CommunityPageSubCommunityListComponent> {
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ export class ThemedCommunityPageSubCommunityListComponent extends ThemedComponen
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected importThemedComponent(themeName: string): Promise<any> {
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
return import(`../../../themes/${themeName}/app/community-page/sub-community-list/community-page-sub-community-list.component`);
|
return import(`../../../../../themes/${themeName}/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component`);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected importUnthemedComponent(): Promise<any> {
|
protected importUnthemedComponent(): Promise<any> {
|
@@ -0,0 +1,31 @@
|
|||||||
|
import { PublicationClaimBreadcrumbResolver } from './publication-claim-breadcrumb.resolver';
|
||||||
|
|
||||||
|
describe('PublicationClaimBreadcrumbResolver', () => {
|
||||||
|
describe('resolve', () => {
|
||||||
|
let resolver: PublicationClaimBreadcrumbResolver;
|
||||||
|
let publicationClaimBreadcrumbService: any;
|
||||||
|
const fullPath = '/test/publication-claim/openaire:6bee076d-4f2a-4555-a475-04a267769b2a';
|
||||||
|
const expectedKey = '6bee076d-4f2a-4555-a475-04a267769b2a';
|
||||||
|
const expectedId = 'openaire:6bee076d-4f2a-4555-a475-04a267769b2a';
|
||||||
|
let route;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
route = {
|
||||||
|
paramMap: {
|
||||||
|
get: function (param) {
|
||||||
|
return this[param];
|
||||||
|
},
|
||||||
|
targetId: expectedId,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
publicationClaimBreadcrumbService = {};
|
||||||
|
resolver = new PublicationClaimBreadcrumbResolver(publicationClaimBreadcrumbService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve the breadcrumb config', () => {
|
||||||
|
const resolvedConfig = resolver.resolve(route as any, {url: fullPath } as any);
|
||||||
|
const expectedConfig = { provider: publicationClaimBreadcrumbService, key: expectedKey };
|
||||||
|
expect(resolvedConfig).toEqual(expectedConfig);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,24 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||||
|
import {BreadcrumbConfig} from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||||
|
import { PublicationClaimBreadcrumbService } from './publication-claim-breadcrumb.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class PublicationClaimBreadcrumbResolver implements Resolve<BreadcrumbConfig<string>> {
|
||||||
|
constructor(protected breadcrumbService: PublicationClaimBreadcrumbService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that resolve Publication Claim item into a breadcrumb
|
||||||
|
* The parameter are retrieved by the url since part of the Publication Claim route config
|
||||||
|
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||||
|
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||||
|
* @returns BreadcrumbConfig object
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): BreadcrumbConfig<string> {
|
||||||
|
const targetId = route.paramMap.get('targetId').split(':')[1];
|
||||||
|
return { provider: this.breadcrumbService, key: targetId };
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,51 @@
|
|||||||
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model';
|
||||||
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
|
import { PublicationClaimBreadcrumbService } from './publication-claim-breadcrumb.service';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
|
describe('PublicationClaimBreadcrumbService', () => {
|
||||||
|
let service: PublicationClaimBreadcrumbService;
|
||||||
|
let dsoNameService: any = {
|
||||||
|
getName: (str) => str
|
||||||
|
};
|
||||||
|
let translateService: any = {
|
||||||
|
instant: (str) => str,
|
||||||
|
};
|
||||||
|
|
||||||
|
let dataService: any = {
|
||||||
|
findById: (str) => createSuccessfulRemoteDataObject$(str),
|
||||||
|
};
|
||||||
|
|
||||||
|
let authorizationService: any = {
|
||||||
|
isAuthorized: (str) => of(true),
|
||||||
|
};
|
||||||
|
|
||||||
|
let exampleKey;
|
||||||
|
|
||||||
|
const ADMIN_PUBLICATION_CLAIMS_PATH = 'admin/notifications/publication-claim';
|
||||||
|
const ADMIN_PUBLICATION_CLAIMS_BREADCRUMB_KEY = 'admin.notifications.publicationclaim.page.title';
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
exampleKey = 'suggestion.suggestionFor.breadcrumb';
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
init();
|
||||||
|
TestBed.configureTestingModule({}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
service = new PublicationClaimBreadcrumbService(dataService,dsoNameService,translateService, authorizationService);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getBreadcrumbs', () => {
|
||||||
|
it('should return a breadcrumb based on a string', () => {
|
||||||
|
const breadcrumbs = service.getBreadcrumbs(exampleKey);
|
||||||
|
getTestScheduler().expectObservable(breadcrumbs).toBe('(a|)', { a: [new Breadcrumb(ADMIN_PUBLICATION_CLAIMS_BREADCRUMB_KEY, ADMIN_PUBLICATION_CLAIMS_PATH),
|
||||||
|
new Breadcrumb(exampleKey, undefined)]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,46 @@
|
|||||||
|
import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model';
|
||||||
|
import { BreadcrumbsProviderService } from './breadcrumbsProviderService';
|
||||||
|
import { combineLatest, Observable } from 'rxjs';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ItemDataService } from '../data/item-data.service';
|
||||||
|
import { getFirstCompletedRemoteData } from '../shared/operators';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { DSONameService } from './dso-name.service';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { AuthorizationDataService } from '../data/feature-authorization/authorization-data.service';
|
||||||
|
import { FeatureID } from '../data/feature-authorization/feature-id';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to calculate Publication claims breadcrumbs
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class PublicationClaimBreadcrumbService implements BreadcrumbsProviderService<string> {
|
||||||
|
private ADMIN_PUBLICATION_CLAIMS_PATH = 'admin/notifications/publication-claim';
|
||||||
|
private ADMIN_PUBLICATION_CLAIMS_BREADCRUMB_KEY = 'admin.notifications.publicationclaim.page.title';
|
||||||
|
|
||||||
|
constructor(private dataService: ItemDataService,
|
||||||
|
private dsoNameService: DSONameService,
|
||||||
|
private tranlsateService: TranslateService,
|
||||||
|
protected authorizationService: AuthorizationDataService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to calculate the breadcrumbs
|
||||||
|
* @param key The key used to resolve the breadcrumb
|
||||||
|
*/
|
||||||
|
getBreadcrumbs(key: string): Observable<Breadcrumb[]> {
|
||||||
|
return combineLatest([this.dataService.findById(key).pipe(getFirstCompletedRemoteData()),this.authorizationService.isAuthorized(FeatureID.AdministratorOf)]).pipe(
|
||||||
|
map(([item, isAdmin]) => {
|
||||||
|
const itemName = this.dsoNameService.getName(item.payload);
|
||||||
|
return isAdmin ? [new Breadcrumb(this.tranlsateService.instant(this.ADMIN_PUBLICATION_CLAIMS_BREADCRUMB_KEY), this.ADMIN_PUBLICATION_CLAIMS_PATH),
|
||||||
|
new Breadcrumb(this.tranlsateService.instant('suggestion.suggestionFor.breadcrumb', {name: itemName}), undefined)] :
|
||||||
|
[new Breadcrumb(this.tranlsateService.instant('suggestion.suggestionFor.breadcrumb', {name: itemName}), undefined)];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
import { HALLink } from '../../shared/hal-link.model';
|
import { HALLink } from '../../shared/hal-link.model';
|
||||||
import { HALResource } from '../../shared/hal-resource.model';
|
import { HALResource } from '../../shared/hal-resource.model';
|
||||||
import { ResourceType } from '../../shared/resource-type';
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
import { getLinkDefinition, link } from './build-decorators';
|
import { dataService, getDataServiceFor, getLinkDefinition, link } from './build-decorators';
|
||||||
|
|
||||||
class TestHALResource implements HALResource {
|
class TestHALResource implements HALResource {
|
||||||
_links: {
|
_links: {
|
||||||
@@ -46,5 +46,17 @@ describe('build decorators', () => {
|
|||||||
expect(result).toBeUndefined();
|
expect(result).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe(`set data service`, () => {
|
||||||
|
it(`should throw error`, () => {
|
||||||
|
expect(dataService(null)).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should set properly data service for type`, () => {
|
||||||
|
const target = new TestHALResource();
|
||||||
|
dataService(testType)(target);
|
||||||
|
expect(getDataServiceFor(testType)).toEqual(target);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
47
src/app/core/cache/builders/build-decorators.ts
vendored
47
src/app/core/cache/builders/build-decorators.ts
vendored
@@ -3,13 +3,20 @@ import { hasNoValue, hasValue } from '../../../shared/empty.util';
|
|||||||
import { GenericConstructor } from '../../shared/generic-constructor';
|
import { GenericConstructor } from '../../shared/generic-constructor';
|
||||||
import { HALResource } from '../../shared/hal-resource.model';
|
import { HALResource } from '../../shared/hal-resource.model';
|
||||||
import { ResourceType } from '../../shared/resource-type';
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
import { getResourceTypeValueFor } from '../object-cache.reducer';
|
import {
|
||||||
|
getResourceTypeValueFor
|
||||||
|
} from '../object-cache.reducer';
|
||||||
import { InjectionToken } from '@angular/core';
|
import { InjectionToken } from '@angular/core';
|
||||||
|
import { CacheableObject } from '../cacheable-object.model';
|
||||||
import { TypedObject } from '../typed-object.model';
|
import { TypedObject } from '../typed-object.model';
|
||||||
|
|
||||||
|
export const DATA_SERVICE_FACTORY = new InjectionToken<(resourceType: ResourceType) => GenericConstructor<any>>('getDataServiceFor', {
|
||||||
|
providedIn: 'root',
|
||||||
|
factory: () => getDataServiceFor
|
||||||
|
});
|
||||||
export const LINK_DEFINITION_FACTORY = new InjectionToken<<T extends HALResource>(source: GenericConstructor<T>, linkName: keyof T['_links']) => LinkDefinition<T>>('getLinkDefinition', {
|
export const LINK_DEFINITION_FACTORY = new InjectionToken<<T extends HALResource>(source: GenericConstructor<T>, linkName: keyof T['_links']) => LinkDefinition<T>>('getLinkDefinition', {
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
factory: () => getLinkDefinition,
|
factory: () => getLinkDefinition
|
||||||
});
|
});
|
||||||
export const LINK_DEFINITION_MAP_FACTORY = new InjectionToken<<T extends HALResource>(source: GenericConstructor<T>) => Map<keyof T['_links'], LinkDefinition<T>>>('getLinkDefinitions', {
|
export const LINK_DEFINITION_MAP_FACTORY = new InjectionToken<<T extends HALResource>(source: GenericConstructor<T>) => Map<keyof T['_links'], LinkDefinition<T>>>('getLinkDefinitions', {
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@@ -20,6 +27,7 @@ const resolvedLinkKey = Symbol('resolvedLink');
|
|||||||
|
|
||||||
const resolvedLinkMap = new Map();
|
const resolvedLinkMap = new Map();
|
||||||
const typeMap = new Map();
|
const typeMap = new Map();
|
||||||
|
const dataServiceMap = new Map();
|
||||||
const linkMap = new Map();
|
const linkMap = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,6 +46,39 @@ export function getClassForType(type: string | ResourceType) {
|
|||||||
return typeMap.get(getResourceTypeValueFor(type));
|
return typeMap.get(getResourceTypeValueFor(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class decorator to indicate that this class is a dataservice
|
||||||
|
* for a given resource type.
|
||||||
|
*
|
||||||
|
* "dataservice" in this context means that it has findByHref and
|
||||||
|
* findAllByHref methods.
|
||||||
|
*
|
||||||
|
* @param resourceType the resource type the class is a dataservice for
|
||||||
|
*/
|
||||||
|
export function dataService(resourceType: ResourceType): any {
|
||||||
|
return (target: any) => {
|
||||||
|
if (hasNoValue(resourceType)) {
|
||||||
|
throw new Error(`Invalid @dataService annotation on ${target}, resourceType needs to be defined`);
|
||||||
|
}
|
||||||
|
const existingDataservice = dataServiceMap.get(resourceType.value);
|
||||||
|
|
||||||
|
if (hasValue(existingDataservice)) {
|
||||||
|
throw new Error(`Multiple dataservices for ${resourceType.value}: ${existingDataservice} and ${target}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
dataServiceMap.set(resourceType.value, target);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the dataservice matching the given resource type
|
||||||
|
*
|
||||||
|
* @param resourceType the resource type you want the matching dataservice for
|
||||||
|
*/
|
||||||
|
export function getDataServiceFor<T extends CacheableObject>(resourceType: ResourceType) {
|
||||||
|
return dataServiceMap.get(resourceType.value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class to represent the data that can be set by the @link decorator
|
* A class to represent the data that can be set by the @link decorator
|
||||||
*/
|
*/
|
||||||
@@ -65,7 +106,7 @@ export const link = <T extends HALResource>(
|
|||||||
resourceType: ResourceType,
|
resourceType: ResourceType,
|
||||||
isList = false,
|
isList = false,
|
||||||
linkName?: keyof T['_links'],
|
linkName?: keyof T['_links'],
|
||||||
) => {
|
) => {
|
||||||
return (target: T, propertyName: string) => {
|
return (target: T, propertyName: string) => {
|
||||||
let targetMap = linkMap.get(target.constructor);
|
let targetMap = linkMap.get(target.constructor);
|
||||||
|
|
||||||
|
@@ -161,6 +161,7 @@ export class RemoteDataBuildService {
|
|||||||
} else {
|
} else {
|
||||||
// in case the elements of the paginated list were already filled in, because they're UnCacheableObjects
|
// in case the elements of the paginated list were already filled in, because they're UnCacheableObjects
|
||||||
paginatedList.page = paginatedList.page
|
paginatedList.page = paginatedList.page
|
||||||
|
.filter((obj: any) => obj != null)
|
||||||
.map((obj: any) => this.plainObjectToInstance<T>(obj))
|
.map((obj: any) => this.plainObjectToInstance<T>(obj))
|
||||||
.map((obj: any) =>
|
.map((obj: any) =>
|
||||||
this.linkService.resolveLinks(obj, ...pageLink.linksToFollow)
|
this.linkService.resolveLinks(obj, ...pageLink.linksToFollow)
|
||||||
|
@@ -186,6 +186,8 @@ import { ValueListBrowseDefinition } from './shared/value-list-browse-definition
|
|||||||
import { NonHierarchicalBrowseDefinition } from './shared/non-hierarchical-browse-definition';
|
import { NonHierarchicalBrowseDefinition } from './shared/non-hierarchical-browse-definition';
|
||||||
import { BulkAccessConditionOptions } from './config/models/bulk-access-condition-options.model';
|
import { BulkAccessConditionOptions } from './config/models/bulk-access-condition-options.model';
|
||||||
import { CorrectionTypeDataService } from './submission/correctiontype-data.service';
|
import { CorrectionTypeDataService } from './submission/correctiontype-data.service';
|
||||||
|
import { SuggestionTarget } from './suggestion-notifications/models/suggestion-target.model';
|
||||||
|
import { SuggestionSource } from './suggestion-notifications/models/suggestion-source.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When not in production, endpoint responses can be mocked for testing purposes
|
* When not in production, endpoint responses can be mocked for testing purposes
|
||||||
@@ -388,7 +390,9 @@ export const models =
|
|||||||
IdentifierData,
|
IdentifierData,
|
||||||
Subscription,
|
Subscription,
|
||||||
ItemRequest,
|
ItemRequest,
|
||||||
BulkAccessConditionOptions
|
BulkAccessConditionOptions,
|
||||||
|
SuggestionTarget,
|
||||||
|
SuggestionSource
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -29,11 +29,11 @@ export const EMBED_SEPARATOR = '%2F';
|
|||||||
/**
|
/**
|
||||||
* Common functionality for data services.
|
* Common functionality for data services.
|
||||||
* Specific functionality that not all services would need
|
* Specific functionality that not all services would need
|
||||||
* is implemented in "DataService feature" classes (e.g. {@link CreateData}
|
* is implemented in "UpdateDataServiceImpl feature" classes (e.g. {@link CreateData}
|
||||||
*
|
*
|
||||||
* All DataService (or DataService feature) classes must
|
* All UpdateDataServiceImpl (or UpdateDataServiceImpl feature) classes must
|
||||||
* - extend this class (or {@link IdentifiableDataService})
|
* - extend this class (or {@link IdentifiableDataService})
|
||||||
* - implement any DataService features it requires in order to forward calls to it
|
* - implement any UpdateDataServiceImpl features it requires in order to forward calls to it
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* export class SomeDataService extends BaseDataService<Something> implements CreateData<Something>, SearchData<Something> {
|
* export class SomeDataService extends BaseDataService<Something> implements CreateData<Something>, SearchData<Something> {
|
||||||
@@ -385,7 +385,7 @@ export class BaseDataService<T extends CacheableObject> implements HALDataServic
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the links to traverse from the root of the api to the
|
* Return the links to traverse from the root of the api to the
|
||||||
* endpoint this DataService represents
|
* endpoint this UpdateDataServiceImpl represents
|
||||||
*
|
*
|
||||||
* e.g. if the api root links to 'foo', and the endpoint at 'foo'
|
* e.g. if the api root links to 'foo', and the endpoint at 'foo'
|
||||||
* links to 'bar' the linkPath for the BarDataService would be
|
* links to 'bar' the linkPath for the BarDataService would be
|
||||||
|
@@ -37,7 +37,7 @@ export interface CreateData<T extends CacheableObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A DataService feature to create objects.
|
* A UpdateDataServiceImpl feature to create objects.
|
||||||
*
|
*
|
||||||
* Concrete data services can use this feature by implementing {@link CreateData}
|
* Concrete data services can use this feature by implementing {@link CreateData}
|
||||||
* and delegating its method to an inner instance of this class.
|
* and delegating its method to an inner instance of this class.
|
||||||
|
@@ -42,7 +42,7 @@ export interface FindAllData<T extends CacheableObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A DataService feature to list all objects.
|
* A UpdateDataServiceImpl feature to list all objects.
|
||||||
*
|
*
|
||||||
* Concrete data services can use this feature by implementing {@link FindAllData}
|
* Concrete data services can use this feature by implementing {@link FindAllData}
|
||||||
* and delegating its method to an inner instance of this class.
|
* and delegating its method to an inner instance of this class.
|
||||||
|
@@ -54,7 +54,7 @@ export interface PatchData<T extends CacheableObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A DataService feature to patch and update objects.
|
* A UpdateDataServiceImpl feature to patch and update objects.
|
||||||
*
|
*
|
||||||
* Concrete data services can use this feature by implementing {@link PatchData}
|
* Concrete data services can use this feature by implementing {@link PatchData}
|
||||||
* and delegating its method to an inner instance of this class.
|
* and delegating its method to an inner instance of this class.
|
||||||
|
@@ -31,7 +31,7 @@ export interface PutData<T extends CacheableObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A DataService feature to send PUT requests.
|
* A UpdateDataServiceImpl feature to send PUT requests.
|
||||||
*
|
*
|
||||||
* Concrete data services can use this feature by implementing {@link PutData}
|
* Concrete data services can use this feature by implementing {@link PutData}
|
||||||
* and delegating its method to an inner instance of this class.
|
* and delegating its method to an inner instance of this class.
|
||||||
|
@@ -51,7 +51,7 @@ export interface SearchData<T extends CacheableObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A DataService feature to search for objects.
|
* A UpdateDataServiceImpl feature to search for objects.
|
||||||
*
|
*
|
||||||
* Concrete data services can use this feature by implementing {@link SearchData}
|
* Concrete data services can use this feature by implementing {@link SearchData}
|
||||||
* and delegating its method to an inner instance of this class.
|
* and delegating its method to an inner instance of this class.
|
||||||
|
@@ -21,6 +21,11 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
|
|||||||
import objectContaining = jasmine.objectContaining;
|
import objectContaining = jasmine.objectContaining;
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
|
import { BundleDataService } from './bundle-data.service';
|
||||||
|
import { ItemMock } from 'src/app/shared/mocks/item.mock';
|
||||||
|
import { createFailedRemoteDataObject, createSuccessfulRemoteDataObject } from 'src/app/shared/remote-data.utils';
|
||||||
|
import { Bundle } from '../shared/bundle.model';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
|
||||||
describe('BitstreamDataService', () => {
|
describe('BitstreamDataService', () => {
|
||||||
let service: BitstreamDataService;
|
let service: BitstreamDataService;
|
||||||
@@ -29,6 +34,7 @@ describe('BitstreamDataService', () => {
|
|||||||
let halService: HALEndpointService;
|
let halService: HALEndpointService;
|
||||||
let bitstreamFormatService: BitstreamFormatDataService;
|
let bitstreamFormatService: BitstreamFormatDataService;
|
||||||
let rdbService: RemoteDataBuildService;
|
let rdbService: RemoteDataBuildService;
|
||||||
|
let bundleDataService: BundleDataService;
|
||||||
const bitstreamFormatHref = 'rest-api/bitstreamformats';
|
const bitstreamFormatHref = 'rest-api/bitstreamformats';
|
||||||
|
|
||||||
const bitstream1 = Object.assign(new Bitstream(), {
|
const bitstream1 = Object.assign(new Bitstream(), {
|
||||||
@@ -62,6 +68,7 @@ describe('BitstreamDataService', () => {
|
|||||||
bitstreamFormatService = jasmine.createSpyObj('bistreamFormatService', {
|
bitstreamFormatService = jasmine.createSpyObj('bistreamFormatService', {
|
||||||
getBrowseEndpoint: observableOf(bitstreamFormatHref)
|
getBrowseEndpoint: observableOf(bitstreamFormatHref)
|
||||||
});
|
});
|
||||||
|
|
||||||
rdbService = getMockRemoteDataBuildService();
|
rdbService = getMockRemoteDataBuildService();
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -76,6 +83,7 @@ describe('BitstreamDataService', () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
service = TestBed.inject(BitstreamDataService);
|
service = TestBed.inject(BitstreamDataService);
|
||||||
|
bundleDataService = TestBed.inject(BundleDataService);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('composition', () => {
|
describe('composition', () => {
|
||||||
@@ -118,6 +126,32 @@ describe('BitstreamDataService', () => {
|
|||||||
expect(service.invalidateByHref).toHaveBeenCalledWith('fake-bitstream1-self');
|
expect(service.invalidateByHref).toHaveBeenCalledWith('fake-bitstream1-self');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('findPrimaryBitstreamByItemAndName', () => {
|
||||||
|
it('should return primary bitstream', () => {
|
||||||
|
const exprected$ = cold('(a|)', { a: bitstream1} );
|
||||||
|
const bundle = Object.assign(new Bundle(), {
|
||||||
|
primaryBitstream: observableOf(createSuccessfulRemoteDataObject(bitstream1)),
|
||||||
|
});
|
||||||
|
spyOn(bundleDataService, 'findByItemAndName').and.returnValue(observableOf(createSuccessfulRemoteDataObject(bundle)));
|
||||||
|
expect(service.findPrimaryBitstreamByItemAndName(ItemMock, 'ORIGINAL')).toBeObservable(exprected$);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null if primary bitstream has not be succeeded ', () => {
|
||||||
|
const exprected$ = cold('(a|)', { a: null} );
|
||||||
|
const bundle = Object.assign(new Bundle(), {
|
||||||
|
primaryBitstream: observableOf(createFailedRemoteDataObject()),
|
||||||
|
});
|
||||||
|
spyOn(bundleDataService, 'findByItemAndName').and.returnValue(observableOf(createSuccessfulRemoteDataObject(bundle)));
|
||||||
|
expect(service.findPrimaryBitstreamByItemAndName(ItemMock, 'ORIGINAL')).toBeObservable(exprected$);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return EMPTY if nothing where found', () => {
|
||||||
|
const exprected$ = cold('(|)', {} );
|
||||||
|
spyOn(bundleDataService, 'findByItemAndName').and.returnValue(observableOf(createFailedRemoteDataObject<Bundle>()));
|
||||||
|
expect(service.findPrimaryBitstreamByItemAndName(ItemMock, 'ORIGINAL')).toBeObservable(exprected$);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should be able to delete multiple bitstreams', () => {
|
it('should be able to delete multiple bitstreams', () => {
|
||||||
service.removeMultiple([bitstream1, bitstream2]);
|
service.removeMultiple([bitstream1, bitstream2]);
|
||||||
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { HttpHeaders } from '@angular/common/http';
|
import { HttpHeaders } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
import { combineLatest as observableCombineLatest, Observable, EMPTY } from 'rxjs';
|
||||||
import { find, map, switchMap, take } from 'rxjs/operators';
|
import { find, map, switchMap, take } from 'rxjs/operators';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig, followLink } from '../../shared/utils/follow-link-config.model';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { Bitstream } from '../shared/bitstream.model';
|
import { Bitstream } from '../shared/bitstream.model';
|
||||||
@@ -34,6 +34,7 @@ import { NoContent } from '../shared/NoContent.model';
|
|||||||
import { IdentifiableDataService } from './base/identifiable-data.service';
|
import { IdentifiableDataService } from './base/identifiable-data.service';
|
||||||
import { dataService } from './base/data-service.decorator';
|
import { dataService } from './base/data-service.decorator';
|
||||||
import { Operation, RemoveOperation } from 'fast-json-patch';
|
import { Operation, RemoveOperation } from 'fast-json-patch';
|
||||||
|
import { getFirstCompletedRemoteData } from '../shared/operators';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A service to retrieve {@link Bitstream}s from the REST API
|
* A service to retrieve {@link Bitstream}s from the REST API
|
||||||
@@ -201,6 +202,37 @@ export class BitstreamDataService extends IdentifiableDataService<Bitstream> imp
|
|||||||
return this.searchData.getSearchByHref(searchMethod, options, ...linksToFollow);
|
return this.searchData.getSearchByHref(searchMethod, options, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Make a request to get primary bitstream
|
||||||
|
* in all current use cases, and having it simplifies this method
|
||||||
|
*
|
||||||
|
* @param item the {@link Item} the {@link Bundle} is a part of
|
||||||
|
* @param bundleName the name of the {@link Bundle} we want to find
|
||||||
|
* {@link Bitstream}s for
|
||||||
|
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||||
|
* no valid cached version. Defaults to true
|
||||||
|
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||||
|
* requested after the response becomes stale
|
||||||
|
* @return {Observable<Bitstream | null>}
|
||||||
|
* Return an observable that constains primary bitstream information or null
|
||||||
|
*/
|
||||||
|
public findPrimaryBitstreamByItemAndName(item: Item, bundleName: string, useCachedVersionIfAvailable = true, reRequestOnStale = true): Observable<Bitstream | null> {
|
||||||
|
return this.bundleService.findByItemAndName(item, bundleName, useCachedVersionIfAvailable, reRequestOnStale, followLink('primaryBitstream')).pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
switchMap((rd: RemoteData<Bundle>) => {
|
||||||
|
if (!rd.hasSucceeded) {
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
return rd.payload.primaryBitstream.pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
map((rdb: RemoteData<Bitstream>) => rdb.hasSucceeded ? rdb.payload : null)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a new FindListRequest with given search method
|
* Make a new FindListRequest with given search method
|
||||||
*
|
*
|
||||||
|
@@ -34,5 +34,6 @@ export enum FeatureID {
|
|||||||
CanEditItem = 'canEditItem',
|
CanEditItem = 'canEditItem',
|
||||||
CanRegisterDOI = 'canRegisterDOI',
|
CanRegisterDOI = 'canRegisterDOI',
|
||||||
CanSubscribe = 'canSubscribeDso',
|
CanSubscribe = 'canSubscribeDso',
|
||||||
CanSeeQA = 'canSeeQA'
|
EPersonForgotPassword = 'epersonForgotPassword',
|
||||||
|
CanSeeQA = 'canSeeQA',
|
||||||
}
|
}
|
||||||
|
@@ -17,8 +17,8 @@ import { HALDataService } from './base/hal-data-service.interface';
|
|||||||
import { dataService } from './base/data-service.decorator';
|
import { dataService } from './base/data-service.decorator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A DataService with only findByHref methods. Its purpose is to be used for resources that don't
|
* A UpdateDataServiceImpl with only findByHref methods. Its purpose is to be used for resources that don't
|
||||||
* need to be retrieved by ID, or have any way to update them, but require a DataService in order
|
* need to be retrieved by ID, or have any way to update them, but require a UpdateDataServiceImpl in order
|
||||||
* for their links to be resolved by the LinkService.
|
* for their links to be resolved by the LinkService.
|
||||||
*
|
*
|
||||||
* an @dataService annotation can be added for any number of these resource types
|
* an @dataService annotation can be added for any number of these resource types
|
||||||
|
144
src/app/core/data/update-data.service.spec.ts
Normal file
144
src/app/core/data/update-data.service.spec.ts
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { RequestService } from './request.service';
|
||||||
|
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
|
import { HrefOnlyDataService } from './href-only-data.service';
|
||||||
|
import { getMockHrefOnlyDataService } from '../../shared/mocks/href-only-data.service.mock';
|
||||||
|
import { RestResponse } from '../cache/response.models';
|
||||||
|
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
||||||
|
import { Item } from '../shared/item.model';
|
||||||
|
import { Version } from '../shared/version.model';
|
||||||
|
import { VersionHistory } from '../shared/version-history.model';
|
||||||
|
import { RequestEntry } from './request-entry.model';
|
||||||
|
import { testPatchDataImplementation } from './base/patch-data.spec';
|
||||||
|
import { UpdateDataServiceImpl } from './update-data.service';
|
||||||
|
import { testSearchDataImplementation } from './base/search-data.spec';
|
||||||
|
import { testDeleteDataImplementation } from './base/delete-data.spec';
|
||||||
|
import { testCreateDataImplementation } from './base/create-data.spec';
|
||||||
|
import { testFindAllDataImplementation } from './base/find-all-data.spec';
|
||||||
|
import { testPutDataImplementation } from './base/put-data.spec';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
|
||||||
|
describe('VersionDataService test', () => {
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
let service: UpdateDataServiceImpl<any>;
|
||||||
|
let requestService: RequestService;
|
||||||
|
let rdbService: RemoteDataBuildService;
|
||||||
|
let objectCache: ObjectCacheService;
|
||||||
|
let halService: HALEndpointService;
|
||||||
|
let hrefOnlyDataService: HrefOnlyDataService;
|
||||||
|
let responseCacheEntry: RequestEntry;
|
||||||
|
|
||||||
|
const notificationsService = {} as NotificationsService;
|
||||||
|
|
||||||
|
const item = Object.assign(new Item(), {
|
||||||
|
id: '1234-1234',
|
||||||
|
uuid: '1234-1234',
|
||||||
|
bundles: observableOf({}),
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'This is just another title'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'dc.type': [
|
||||||
|
{
|
||||||
|
language: null,
|
||||||
|
value: 'Article'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'dc.contributor.author': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'Smith, Donald'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'dc.date.issued': [
|
||||||
|
{
|
||||||
|
language: null,
|
||||||
|
value: '2015-06-26'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const versionHistory = Object.assign(new VersionHistory(), {
|
||||||
|
id: '1',
|
||||||
|
draftVersion: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockVersion: Version = Object.assign(new Version(), {
|
||||||
|
item: createSuccessfulRemoteDataObject$(item),
|
||||||
|
versionhistory: createSuccessfulRemoteDataObject$(versionHistory),
|
||||||
|
version: 1,
|
||||||
|
});
|
||||||
|
const mockVersionRD = createSuccessfulRemoteDataObject(mockVersion);
|
||||||
|
|
||||||
|
const endpointURL = `https://rest.api/rest/api/versioning/versions`;
|
||||||
|
const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a';
|
||||||
|
|
||||||
|
objectCache = {} as ObjectCacheService;
|
||||||
|
const comparatorEntry = {} as any;
|
||||||
|
function initTestService() {
|
||||||
|
hrefOnlyDataService = getMockHrefOnlyDataService();
|
||||||
|
return new UpdateDataServiceImpl(
|
||||||
|
'testLinkPath',
|
||||||
|
requestService,
|
||||||
|
rdbService,
|
||||||
|
objectCache,
|
||||||
|
halService,
|
||||||
|
notificationsService,
|
||||||
|
comparatorEntry,
|
||||||
|
10 * 1000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
|
||||||
|
halService = jasmine.createSpyObj('halService', {
|
||||||
|
getEndpoint: cold('a', { a: endpointURL })
|
||||||
|
});
|
||||||
|
responseCacheEntry = new RequestEntry();
|
||||||
|
responseCacheEntry.request = { href: 'https://rest.api/' } as any;
|
||||||
|
responseCacheEntry.response = new RestResponse(true, 200, 'Success');
|
||||||
|
|
||||||
|
requestService = jasmine.createSpyObj('requestService', {
|
||||||
|
generateRequestId: requestUUID,
|
||||||
|
send: true,
|
||||||
|
removeByHrefSubstring: {},
|
||||||
|
getByHref: observableOf(responseCacheEntry),
|
||||||
|
getByUUID: observableOf(responseCacheEntry),
|
||||||
|
});
|
||||||
|
rdbService = jasmine.createSpyObj('rdbService', {
|
||||||
|
buildSingle: hot('(a|)', {
|
||||||
|
a: mockVersionRD
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
service = initTestService();
|
||||||
|
|
||||||
|
spyOn((service as any), 'findById').and.callThrough();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
service = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('composition', () => {
|
||||||
|
const initService = () => new UpdateDataServiceImpl(null, null, null, null, null, null, null, null);
|
||||||
|
|
||||||
|
testPatchDataImplementation(initService);
|
||||||
|
testSearchDataImplementation(initService);
|
||||||
|
testDeleteDataImplementation(initService);
|
||||||
|
testCreateDataImplementation(initService);
|
||||||
|
testFindAllDataImplementation(initService);
|
||||||
|
testPutDataImplementation(initService);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -1,13 +1,317 @@
|
|||||||
import { Observable } from 'rxjs';
|
|
||||||
import { RemoteData } from './remote-data';
|
|
||||||
import { RestRequestMethod } from './rest-request-method';
|
|
||||||
import { Operation } from 'fast-json-patch';
|
import { Operation } from 'fast-json-patch';
|
||||||
|
import { AsyncSubject, from as observableFrom, Observable } from 'rxjs';
|
||||||
|
import {
|
||||||
|
find,
|
||||||
|
map,
|
||||||
|
mergeMap,
|
||||||
|
switchMap,
|
||||||
|
take,
|
||||||
|
toArray
|
||||||
|
} from 'rxjs/operators';
|
||||||
|
import { hasValue } from '../../shared/empty.util';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
|
import { ObjectCacheEntry } from '../cache/object-cache.reducer';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { ChangeAnalyzer } from './change-analyzer';
|
||||||
|
import { PaginatedList } from './paginated-list.model';
|
||||||
|
import { RemoteData } from './remote-data';
|
||||||
|
import {
|
||||||
|
DeleteByIDRequest,
|
||||||
|
PostRequest
|
||||||
|
} from './request.models';
|
||||||
|
import { RequestService } from './request.service';
|
||||||
|
import { RestRequestMethod } from './rest-request-method';
|
||||||
|
import { NoContent } from '../shared/NoContent.model';
|
||||||
|
import { CacheableObject } from '../cache/cacheable-object.model';
|
||||||
|
import { FindListOptions } from './find-list-options.model';
|
||||||
|
import { FindAllData, FindAllDataImpl } from './base/find-all-data';
|
||||||
|
import { SearchData, SearchDataImpl } from './base/search-data';
|
||||||
|
import { CreateData, CreateDataImpl } from './base/create-data';
|
||||||
|
import { PatchData, PatchDataImpl } from './base/patch-data';
|
||||||
|
import { IdentifiableDataService } from './base/identifiable-data.service';
|
||||||
|
import { PutData, PutDataImpl } from './base/put-data';
|
||||||
|
import { DeleteData, DeleteDataImpl } from './base/delete-data';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a data service to update a given object
|
* Interface to list the methods used by the injected service in components
|
||||||
*/
|
*/
|
||||||
export interface UpdateDataService<T> {
|
export interface UpdateDataService<T> {
|
||||||
patch(dso: T, operations: Operation[]): Observable<RemoteData<T>>;
|
patch(dso: T, operations: Operation[]): Observable<RemoteData<T>>;
|
||||||
update(object: T): Observable<RemoteData<T>>;
|
update(object: T): Observable<RemoteData<T>>;
|
||||||
commitUpdates(method?: RestRequestMethod);
|
commitUpdates(method?: RestRequestMethod): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specific functionalities that not all services would need.
|
||||||
|
* Goal of the class is to update remote objects, handling custom methods that don't belong to BaseDataService
|
||||||
|
* The class implements also the following common interfaces
|
||||||
|
*
|
||||||
|
* findAllData: FindAllData<T>;
|
||||||
|
* searchData: SearchData<T>;
|
||||||
|
* createData: CreateData<T>;
|
||||||
|
* patchData: PatchData<T>;
|
||||||
|
* putData: PutData<T>;
|
||||||
|
* deleteData: DeleteData<T>;
|
||||||
|
*
|
||||||
|
* Custom methods are:
|
||||||
|
*
|
||||||
|
* deleteOnRelated - delete all related objects to the given one
|
||||||
|
* postOnRelated - post all the related objects to the given one
|
||||||
|
* invalidate - invalidate the DSpaceObject making all requests as stale
|
||||||
|
* invalidateByHref - invalidate the href making all requests as stale
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class UpdateDataServiceImpl<T extends CacheableObject> extends IdentifiableDataService<T> implements FindAllData<T>, SearchData<T>, CreateData<T>, PatchData<T>, PutData<T>, DeleteData<T> {
|
||||||
|
private findAllData: FindAllDataImpl<T>;
|
||||||
|
private searchData: SearchDataImpl<T>;
|
||||||
|
private createData: CreateDataImpl<T>;
|
||||||
|
private patchData: PatchDataImpl<T>;
|
||||||
|
private putData: PutDataImpl<T>;
|
||||||
|
private deleteData: DeleteDataImpl<T>;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected linkPath: string,
|
||||||
|
protected requestService: RequestService,
|
||||||
|
protected rdbService: RemoteDataBuildService,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
protected halService: HALEndpointService,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected comparator: ChangeAnalyzer<T>,
|
||||||
|
protected responseMsToLive: number,
|
||||||
|
) {
|
||||||
|
super(linkPath, requestService, rdbService, objectCache, halService, responseMsToLive);
|
||||||
|
this.findAllData = new FindAllDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||||
|
this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||||
|
this.createData = new CreateDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService ,this.responseMsToLive);
|
||||||
|
this.patchData = new PatchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, comparator ,this.responseMsToLive, this.constructIdEndpoint);
|
||||||
|
this.putData = new PutDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||||
|
this.deleteData = new DeleteDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService ,this.responseMsToLive, this.constructIdEndpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the HREF with given options object
|
||||||
|
*
|
||||||
|
* @param options The [[FindListOptions]] object
|
||||||
|
* @param linkPath The link path for the object
|
||||||
|
* @return {Observable<string>}
|
||||||
|
* Return an observable that emits created HREF
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||||
|
*/
|
||||||
|
public getFindAllHref(options: FindListOptions = {}, linkPath?: string, ...linksToFollow: FollowLinkConfig<T>[]): Observable<string> {
|
||||||
|
return this.findAllData.getFindAllHref(options, linkPath, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the HREF for a specific object's search method with given options object
|
||||||
|
*
|
||||||
|
* @param searchMethod The search method for the object
|
||||||
|
* @param options The [[FindListOptions]] object
|
||||||
|
* @return {Observable<string>}
|
||||||
|
* Return an observable that emits created HREF
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||||
|
*/
|
||||||
|
public getSearchByHref(searchMethod: string, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<T>[]): Observable<string> {
|
||||||
|
return this.searchData.getSearchByHref(searchMethod, options, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@link RemoteData} of all object with a list of {@link FollowLinkConfig}, to indicate which embedded
|
||||||
|
* info should be added to the objects
|
||||||
|
*
|
||||||
|
* @param options Find list options object
|
||||||
|
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||||
|
* no valid cached version. Defaults to true
|
||||||
|
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||||
|
* requested after the response becomes stale
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||||
|
* {@link HALLink}s should be automatically resolved
|
||||||
|
* @return {Observable<RemoteData<PaginatedList<T>>>}
|
||||||
|
* Return an observable that emits object list
|
||||||
|
*/
|
||||||
|
findAll(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
|
||||||
|
return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a new FindListRequest with given search method
|
||||||
|
*
|
||||||
|
* @param searchMethod The search method for the object
|
||||||
|
* @param options The [[FindListOptions]] object
|
||||||
|
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||||
|
* no valid cached version. Defaults to true
|
||||||
|
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||||
|
* requested after the response becomes stale
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||||
|
* {@link HALLink}s should be automatically resolved
|
||||||
|
* @return {Observable<RemoteData<PaginatedList<T>>}
|
||||||
|
* Return an observable that emits response from the server
|
||||||
|
*/
|
||||||
|
searchBy(searchMethod: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
|
||||||
|
return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a patch request for a specified object
|
||||||
|
* @param {T} object The object to send a patch request for
|
||||||
|
* @param {Operation[]} operations The patch operations to be performed
|
||||||
|
*/
|
||||||
|
patch(object: T, operations: Operation[]): Observable<RemoteData<T>> {
|
||||||
|
return this.patchData.patch(object, operations);
|
||||||
|
}
|
||||||
|
|
||||||
|
createPatchFromCache(object: T): Observable<Operation[]> {
|
||||||
|
return this.patchData.createPatchFromCache(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a PUT request for the specified object
|
||||||
|
*
|
||||||
|
* @param object The object to send a put request for.
|
||||||
|
*/
|
||||||
|
put(object: T): Observable<RemoteData<T>> {
|
||||||
|
return this.putData.put(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new patch to the object cache
|
||||||
|
* The patch is derived from the differences between the given object and its version in the object cache
|
||||||
|
* @param {DSpaceObject} object The given object
|
||||||
|
*/
|
||||||
|
update(object: T): Observable<RemoteData<T>> {
|
||||||
|
return this.patchData.update(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new DSpaceObject on the server, and store the response
|
||||||
|
* in the object cache
|
||||||
|
*
|
||||||
|
* @param {CacheableObject} object
|
||||||
|
* The object to create
|
||||||
|
* @param {RequestParam[]} params
|
||||||
|
* Array with additional params to combine with query string
|
||||||
|
*/
|
||||||
|
create(object: T, ...params: RequestParam[]): Observable<RemoteData<T>> {
|
||||||
|
return this.createData.create(object, ...params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
<<<<<<< HEAD
|
||||||
|
* Perform a post on an endpoint related item with ID. Ex.: endpoint/<itemId>/related?item=<relatedItemId>
|
||||||
|
* @param itemId The item id
|
||||||
|
* @param relatedItemId The related item Id
|
||||||
|
* @param body The optional POST body
|
||||||
|
* @return the RestResponse as an Observable
|
||||||
|
*/
|
||||||
|
public postOnRelated(itemId: string, relatedItemId: string, body?: any) {
|
||||||
|
const requestId = this.requestService.generateRequestId();
|
||||||
|
const hrefObs = this.getIDHrefObs(itemId);
|
||||||
|
|
||||||
|
hrefObs.pipe(
|
||||||
|
take(1)
|
||||||
|
).subscribe((href: string) => {
|
||||||
|
const request = new PostRequest(requestId, href + '/related?item=' + relatedItemId, body);
|
||||||
|
if (hasValue(this.responseMsToLive)) {
|
||||||
|
request.responseMsToLive = this.responseMsToLive;
|
||||||
|
}
|
||||||
|
this.requestService.send(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.rdbService.buildFromRequestUUID<T>(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a delete on an endpoint related item. Ex.: endpoint/<itemId>/related
|
||||||
|
* @param itemId The item id
|
||||||
|
* @return the RestResponse as an Observable
|
||||||
|
*/
|
||||||
|
public deleteOnRelated(itemId: string): Observable<RemoteData<NoContent>> {
|
||||||
|
const requestId = this.requestService.generateRequestId();
|
||||||
|
const hrefObs = this.getIDHrefObs(itemId);
|
||||||
|
|
||||||
|
hrefObs.pipe(
|
||||||
|
find((href: string) => hasValue(href)),
|
||||||
|
map((href: string) => {
|
||||||
|
const request = new DeleteByIDRequest(requestId, href + '/related', itemId);
|
||||||
|
if (hasValue(this.responseMsToLive)) {
|
||||||
|
request.responseMsToLive = this.responseMsToLive;
|
||||||
|
}
|
||||||
|
this.requestService.send(request);
|
||||||
|
})
|
||||||
|
).subscribe();
|
||||||
|
|
||||||
|
return this.rdbService.buildFromRequestUUID(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Invalidate an existing DSpaceObject by marking all requests it is included in as stale
|
||||||
|
* @param objectId The id of the object to be invalidated
|
||||||
|
* @return An Observable that will emit `true` once all requests are stale
|
||||||
|
*/
|
||||||
|
invalidate(objectId: string): Observable<boolean> {
|
||||||
|
return this.getIDHrefObs(objectId).pipe(
|
||||||
|
switchMap((href: string) => this.invalidateByHref(href))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate an existing DSpaceObject by marking all requests it is included in as stale
|
||||||
|
* @param href The self link of the object to be invalidated
|
||||||
|
* @return An Observable that will emit `true` once all requests are stale
|
||||||
|
*/
|
||||||
|
invalidateByHref(href: string): Observable<boolean> {
|
||||||
|
const done$ = new AsyncSubject<boolean>();
|
||||||
|
|
||||||
|
this.objectCache.getByHref(href).pipe(
|
||||||
|
switchMap((oce: ObjectCacheEntry) => observableFrom(oce.requestUUIDs).pipe(
|
||||||
|
mergeMap((requestUUID: string) => this.requestService.setStaleByUUID(requestUUID)),
|
||||||
|
toArray(),
|
||||||
|
)),
|
||||||
|
).subscribe(() => {
|
||||||
|
done$.next(true);
|
||||||
|
done$.complete();
|
||||||
|
});
|
||||||
|
|
||||||
|
return done$;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an existing DSpace Object on the server
|
||||||
|
* @param objectId The id of the object to be removed
|
||||||
|
* @param copyVirtualMetadata (optional parameter) the identifiers of the relationship types for which the virtual
|
||||||
|
* metadata should be saved as real metadata
|
||||||
|
* @return A RemoteData observable with an empty payload, but still representing the state of the request: statusCode,
|
||||||
|
* errorMessage, timeCompleted, etc
|
||||||
|
*/
|
||||||
|
delete(objectId: string, copyVirtualMetadata?: string[]): Observable<RemoteData<NoContent>> {
|
||||||
|
return this.deleteData.delete(objectId, copyVirtualMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an existing DSpace Object on the server
|
||||||
|
* @param href The self link of the object to be removed
|
||||||
|
* @param copyVirtualMetadata (optional parameter) the identifiers of the relationship types for which the virtual
|
||||||
|
* metadata should be saved as real metadata
|
||||||
|
* @return A RemoteData observable with an empty payload, but still representing the state of the request: statusCode,
|
||||||
|
* errorMessage, timeCompleted, etc
|
||||||
|
* Only emits once all request related to the DSO has been invalidated.
|
||||||
|
*/
|
||||||
|
deleteByHref(href: string, copyVirtualMetadata?: string[]): Observable<RemoteData<NoContent>> {
|
||||||
|
return this.deleteData.deleteByHref(href, copyVirtualMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commit current object changes to the server
|
||||||
|
* @param method The RestRequestMethod for which de server sync buffer should be committed
|
||||||
|
*/
|
||||||
|
commitUpdates(method?: RestRequestMethod) {
|
||||||
|
this.patchData.commitUpdates(method);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -128,7 +128,7 @@ describe('VersionDataService test', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getHistoryFromVersion', () => {
|
describe('getHistoryFromVersion', () => {
|
||||||
it('should proxy the call to DataService.findByHref', () => {
|
it('should proxy the call to UpdateDataServiceImpl.findByHref', () => {
|
||||||
scheduler.schedule(() => service.getHistoryFromVersion(mockVersion, true, true));
|
scheduler.schedule(() => service.getHistoryFromVersion(mockVersion, true, true));
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
|
@@ -315,7 +315,7 @@ describe('EPersonDataService', () => {
|
|||||||
service.deleteEPerson(EPersonMock).subscribe();
|
service.deleteEPerson(EPersonMock).subscribe();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call DataService.delete with the EPerson\'s UUID', () => {
|
it('should call UpdateDataServiceImpl.delete with the EPerson\'s UUID', () => {
|
||||||
expect(service.delete).toHaveBeenCalledWith(EPersonMock.id);
|
expect(service.delete).toHaveBeenCalledWith(EPersonMock.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -0,0 +1,31 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { AuthorizationDataService } from '../data/feature-authorization/authorization-data.service';
|
||||||
|
import { FeatureID } from '../data/feature-authorization/feature-id';
|
||||||
|
import {
|
||||||
|
SingleFeatureAuthorizationGuard
|
||||||
|
} from '../data/feature-authorization/feature-authorization-guard/single-feature-authorization.guard';
|
||||||
|
import { AuthService } from '../auth/auth.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Guard that checks if the forgot-password feature is enabled
|
||||||
|
*/
|
||||||
|
export class ForgotPasswordCheckGuard extends SingleFeatureAuthorizationGuard {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected readonly authorizationService: AuthorizationDataService,
|
||||||
|
protected readonly router: Router,
|
||||||
|
protected readonly authService: AuthService
|
||||||
|
) {
|
||||||
|
super(authorizationService, router, authService);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID> {
|
||||||
|
return of(FeatureID.EPersonForgotPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -27,6 +27,7 @@ const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
|
|||||||
export const FILTER_CONFIG: InjectionToken<SearchFilterConfig> = new InjectionToken<SearchFilterConfig>('filterConfig');
|
export const FILTER_CONFIG: InjectionToken<SearchFilterConfig> = new InjectionToken<SearchFilterConfig>('filterConfig');
|
||||||
export const IN_PLACE_SEARCH: InjectionToken<boolean> = new InjectionToken<boolean>('inPlaceSearch');
|
export const IN_PLACE_SEARCH: InjectionToken<boolean> = new InjectionToken<boolean>('inPlaceSearch');
|
||||||
export const REFRESH_FILTER: InjectionToken<BehaviorSubject<any>> = new InjectionToken<boolean>('refreshFilters');
|
export const REFRESH_FILTER: InjectionToken<BehaviorSubject<any>> = new InjectionToken<boolean>('refreshFilters');
|
||||||
|
export const SCOPE: InjectionToken<string> = new InjectionToken<string>('scope');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service that performs all actions that have to do with search filters and facets
|
* Service that performs all actions that have to do with search filters and facets
|
||||||
|
@@ -4,7 +4,10 @@ import { WorkspaceitemSectionUploadFileObject } from './workspaceitem-section-up
|
|||||||
* An interface to represent submission's upload section data.
|
* An interface to represent submission's upload section data.
|
||||||
*/
|
*/
|
||||||
export interface WorkspaceitemSectionUploadObject {
|
export interface WorkspaceitemSectionUploadObject {
|
||||||
|
/**
|
||||||
|
* Primary bitstream flag
|
||||||
|
*/
|
||||||
|
primary: string | null;
|
||||||
/**
|
/**
|
||||||
* A list of [[WorkspaceitemSectionUploadFileObject]]
|
* A list of [[WorkspaceitemSectionUploadFileObject]]
|
||||||
*/
|
*/
|
||||||
|
@@ -126,7 +126,7 @@ describe('WorkflowItemDataService test', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('findByItem', () => {
|
describe('findByItem', () => {
|
||||||
it('should proxy the call to DataService.findByHref', () => {
|
it('should proxy the call to UpdateDataServiceImpl.findByHref', () => {
|
||||||
scheduler.schedule(() => service.findByItem('1234-1234', true, true, pageInfo));
|
scheduler.schedule(() => service.findByItem('1234-1234', true, true, pageInfo));
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ import { ObjectCacheService } from '../cache/object-cache.service';
|
|||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { PageInfo } from '../shared/page-info.model';
|
import { PageInfo } from '../shared/page-info.model';
|
||||||
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
import { HrefOnlyDataService } from '../data/href-only-data.service';
|
import { HrefOnlyDataService } from '../data/href-only-data.service';
|
||||||
import { getMockHrefOnlyDataService } from '../../shared/mocks/href-only-data.service.mock';
|
import { getMockHrefOnlyDataService } from '../../shared/mocks/href-only-data.service.mock';
|
||||||
import { WorkspaceitemDataService } from './workspaceitem-data.service';
|
import { WorkspaceitemDataService } from './workspaceitem-data.service';
|
||||||
@@ -21,6 +21,11 @@ import { RequestEntry } from '../data/request-entry.model';
|
|||||||
import { CoreState } from '../core-state.model';
|
import { CoreState } from '../core-state.model';
|
||||||
import { testSearchDataImplementation } from '../data/base/search-data.spec';
|
import { testSearchDataImplementation } from '../data/base/search-data.spec';
|
||||||
import { testDeleteDataImplementation } from '../data/base/delete-data.spec';
|
import { testDeleteDataImplementation } from '../data/base/delete-data.spec';
|
||||||
|
import { SearchData } from '../data/base/search-data';
|
||||||
|
import { DeleteData } from '../data/base/delete-data';
|
||||||
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
|
import { PostRequest } from '../data/request.models';
|
||||||
|
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
|
||||||
|
|
||||||
describe('WorkspaceitemDataService test', () => {
|
describe('WorkspaceitemDataService test', () => {
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
@@ -68,15 +73,12 @@ describe('WorkspaceitemDataService test', () => {
|
|||||||
const wsiRD = createSuccessfulRemoteDataObject(wsi);
|
const wsiRD = createSuccessfulRemoteDataObject(wsi);
|
||||||
|
|
||||||
const endpointURL = `https://rest.api/rest/api/submission/workspaceitems`;
|
const endpointURL = `https://rest.api/rest/api/submission/workspaceitems`;
|
||||||
const searchRequestURL = `https://rest.api/rest/api/submission/workspaceitems/search/item?uuid=1234-1234`;
|
|
||||||
const searchRequestURL$ = observableOf(searchRequestURL);
|
|
||||||
|
|
||||||
const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a';
|
const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a';
|
||||||
|
|
||||||
objectCache = {} as ObjectCacheService;
|
objectCache = {} as ObjectCacheService;
|
||||||
const notificationsService = {} as NotificationsService;
|
const notificationsService = {} as NotificationsService;
|
||||||
const http = {} as HttpClient;
|
const http = {} as HttpClient;
|
||||||
const comparator = {} as any;
|
|
||||||
const comparatorEntry = {} as any;
|
const comparatorEntry = {} as any;
|
||||||
const store = {} as Store<CoreState>;
|
const store = {} as Store<CoreState>;
|
||||||
const pageInfo = new PageInfo();
|
const pageInfo = new PageInfo();
|
||||||
@@ -84,18 +86,23 @@ describe('WorkspaceitemDataService test', () => {
|
|||||||
function initTestService() {
|
function initTestService() {
|
||||||
hrefOnlyDataService = getMockHrefOnlyDataService();
|
hrefOnlyDataService = getMockHrefOnlyDataService();
|
||||||
return new WorkspaceitemDataService(
|
return new WorkspaceitemDataService(
|
||||||
|
comparatorEntry,
|
||||||
|
halService,
|
||||||
|
http,
|
||||||
|
notificationsService,
|
||||||
requestService,
|
requestService,
|
||||||
rdbService,
|
rdbService,
|
||||||
objectCache,
|
objectCache,
|
||||||
halService,
|
store
|
||||||
notificationsService,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('composition', () => {
|
describe('composition', () => {
|
||||||
const initService = () => new WorkspaceitemDataService(null, null, null, null, null);
|
const initSearchService = () => new WorkspaceitemDataService(null, null, null, null, null, null, null, null) as unknown as SearchData<any>;
|
||||||
testSearchDataImplementation(initService);
|
const initDeleteService = () => new WorkspaceitemDataService(null, null, null, null, null, null, null, null) as unknown as DeleteData<any>;
|
||||||
testDeleteDataImplementation(initService);
|
|
||||||
|
testSearchDataImplementation(initSearchService);
|
||||||
|
testDeleteDataImplementation(initDeleteService);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('', () => {
|
describe('', () => {
|
||||||
@@ -104,7 +111,7 @@ describe('WorkspaceitemDataService test', () => {
|
|||||||
scheduler = getTestScheduler();
|
scheduler = getTestScheduler();
|
||||||
|
|
||||||
halService = jasmine.createSpyObj('halService', {
|
halService = jasmine.createSpyObj('halService', {
|
||||||
getEndpoint: cold('a', { a: endpointURL })
|
getEndpoint: observableOf(endpointURL)
|
||||||
});
|
});
|
||||||
responseCacheEntry = new RequestEntry();
|
responseCacheEntry = new RequestEntry();
|
||||||
responseCacheEntry.request = { href: 'https://rest.api/' } as any;
|
responseCacheEntry.request = { href: 'https://rest.api/' } as any;
|
||||||
@@ -120,13 +127,13 @@ describe('WorkspaceitemDataService test', () => {
|
|||||||
rdbService = jasmine.createSpyObj('rdbService', {
|
rdbService = jasmine.createSpyObj('rdbService', {
|
||||||
buildSingle: hot('a|', {
|
buildSingle: hot('a|', {
|
||||||
a: wsiRD
|
a: wsiRD
|
||||||
})
|
}),
|
||||||
|
buildFromRequestUUID: createSuccessfulRemoteDataObject$({})
|
||||||
});
|
});
|
||||||
|
|
||||||
service = initTestService();
|
service = initTestService();
|
||||||
|
|
||||||
spyOn((service as any), 'findByHref').and.callThrough();
|
spyOn((service as any), 'findByHref').and.callThrough();
|
||||||
spyOn((service as any), 'getSearchByHref').and.returnValue(searchRequestURL$);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -134,11 +141,11 @@ describe('WorkspaceitemDataService test', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('findByItem', () => {
|
describe('findByItem', () => {
|
||||||
it('should proxy the call to DataService.findByHref', () => {
|
it('should proxy the call to UpdateDataServiceImpl.findByHref', () => {
|
||||||
scheduler.schedule(() => service.findByItem('1234-1234', true, true, pageInfo));
|
scheduler.schedule(() => service.findByItem('1234-1234', true, true, pageInfo));
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
const searchUrl = service.getIDHref('item', [new RequestParam('uuid', encodeURIComponent('1234-1234'))]);
|
||||||
expect((service as any).findByHref).toHaveBeenCalledWith(searchRequestURL$, true, true);
|
expect((service as any).findByHref).toHaveBeenCalledWith(searchUrl, true, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a RemoteData<WorkspaceItem> for the search', () => {
|
it('should return a RemoteData<WorkspaceItem> for the search', () => {
|
||||||
@@ -150,6 +157,19 @@ describe('WorkspaceitemDataService test', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
|
describe('importExternalSourceEntry', () => {
|
||||||
|
it('should send a POST request containing the provided item request', (done) => {
|
||||||
|
const options: HttpOptions = Object.create({});
|
||||||
|
let headers = new HttpHeaders();
|
||||||
|
headers = headers.append('Content-Type', 'text/uri-list');
|
||||||
|
options.headers = headers;
|
||||||
|
|
||||||
|
service.importExternalSourceEntry('externalHref', 'testId').subscribe(() => {
|
||||||
|
expect(requestService.send).toHaveBeenCalledWith(new PostRequest(requestUUID, `${endpointURL}?owningCollection=testId`, 'externalHref', options));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,46 +1,58 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import {HttpClient, HttpHeaders} from '@angular/common/http';
|
||||||
|
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { dataService } from '../cache/builders/build-decorators';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
|
||||||
import { WorkspaceItem } from './models/workspaceitem.model';
|
import { WorkspaceItem } from './models/workspaceitem.model';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { RemoteData } from '../data/remote-data';
|
import { RemoteData } from '../data/remote-data';
|
||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { RequestParam } from '../cache/models/request-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
|
import { CoreState } from '../core-state.model';
|
||||||
import { FindListOptions } from '../data/find-list-options.model';
|
import { FindListOptions } from '../data/find-list-options.model';
|
||||||
|
import {HttpOptions} from '../dspace-rest/dspace-rest.service';
|
||||||
|
import {find, map} from 'rxjs/operators';
|
||||||
|
import {PostRequest} from '../data/request.models';
|
||||||
|
import {hasValue} from '../../shared/empty.util';
|
||||||
import { IdentifiableDataService } from '../data/base/identifiable-data.service';
|
import { IdentifiableDataService } from '../data/base/identifiable-data.service';
|
||||||
|
import { NoContent } from '../shared/NoContent.model';
|
||||||
|
import { DeleteData, DeleteDataImpl } from '../data/base/delete-data';
|
||||||
import { SearchData, SearchDataImpl } from '../data/base/search-data';
|
import { SearchData, SearchDataImpl } from '../data/base/search-data';
|
||||||
import { PaginatedList } from '../data/paginated-list.model';
|
import { PaginatedList } from '../data/paginated-list.model';
|
||||||
import { DeleteData, DeleteDataImpl } from '../data/base/delete-data';
|
|
||||||
import { NoContent } from '../shared/NoContent.model';
|
|
||||||
import { dataService } from '../data/base/data-service.decorator';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A service that provides methods to make REST requests with workspaceitems endpoint.
|
* A service that provides methods to make REST requests with workspaceitems endpoint.
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@dataService(WorkspaceItem.type)
|
@dataService(WorkspaceItem.type)
|
||||||
export class WorkspaceitemDataService extends IdentifiableDataService<WorkspaceItem> implements SearchData<WorkspaceItem>, DeleteData<WorkspaceItem> {
|
export class WorkspaceitemDataService extends IdentifiableDataService<WorkspaceItem> implements DeleteData<WorkspaceItem>, SearchData<WorkspaceItem>{
|
||||||
|
protected linkPath = 'workspaceitems';
|
||||||
protected searchByItemLinkPath = 'item';
|
protected searchByItemLinkPath = 'item';
|
||||||
|
private deleteData: DeleteData<WorkspaceItem>;
|
||||||
private searchData: SearchDataImpl<WorkspaceItem>;
|
private searchData: SearchData<WorkspaceItem>;
|
||||||
private deleteData: DeleteDataImpl<WorkspaceItem>;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
protected comparator: DSOChangeAnalyzer<WorkspaceItem>,
|
||||||
|
protected halService: HALEndpointService,
|
||||||
|
protected http: HttpClient,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
protected rdbService: RemoteDataBuildService,
|
protected rdbService: RemoteDataBuildService,
|
||||||
protected objectCache: ObjectCacheService,
|
protected objectCache: ObjectCacheService,
|
||||||
protected halService: HALEndpointService,
|
protected store: Store<CoreState>) {
|
||||||
protected notificationsService: NotificationsService,
|
|
||||||
) {
|
|
||||||
super('workspaceitems', requestService, rdbService, objectCache, halService);
|
super('workspaceitems', requestService, rdbService, objectCache, halService);
|
||||||
|
|
||||||
this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
|
||||||
this.deleteData = new DeleteDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive, this.constructIdEndpoint);
|
this.deleteData = new DeleteDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive, this.constructIdEndpoint);
|
||||||
|
this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||||
|
}
|
||||||
|
public delete(objectId: string, copyVirtualMetadata?: string[]): Observable<RemoteData<NoContent>> {
|
||||||
|
return this.deleteData.delete(objectId, copyVirtualMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the WorkspaceItem object found through the UUID of an item
|
* Return the WorkspaceItem object found through the UUID of an item
|
||||||
*
|
*
|
||||||
@@ -55,21 +67,46 @@ export class WorkspaceitemDataService extends IdentifiableDataService<WorkspaceI
|
|||||||
public findByItem(uuid: string, useCachedVersionIfAvailable = false, reRequestOnStale = true, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<WorkspaceItem>[]): Observable<RemoteData<WorkspaceItem>> {
|
public findByItem(uuid: string, useCachedVersionIfAvailable = false, reRequestOnStale = true, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<WorkspaceItem>[]): Observable<RemoteData<WorkspaceItem>> {
|
||||||
const findListOptions = new FindListOptions();
|
const findListOptions = new FindListOptions();
|
||||||
findListOptions.searchParams = [new RequestParam('uuid', encodeURIComponent(uuid))];
|
findListOptions.searchParams = [new RequestParam('uuid', encodeURIComponent(uuid))];
|
||||||
const href$ = this.getSearchByHref(this.searchByItemLinkPath, findListOptions, ...linksToFollow);
|
const href$ = this.getIDHref(this.searchByItemLinkPath, findListOptions, ...linksToFollow);
|
||||||
return this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
return this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the HREF for a specific object's search method with given options object
|
* Import an external source entry into a collection
|
||||||
*
|
* @param externalSourceEntryHref
|
||||||
* @param searchMethod The search method for the object
|
* @param collectionId
|
||||||
* @param options The [[FindListOptions]] object
|
|
||||||
* @return {Observable<string>}
|
|
||||||
* Return an observable that emits created HREF
|
|
||||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
|
||||||
*/
|
*/
|
||||||
public getSearchByHref(searchMethod: string, options?: FindListOptions, ...linksToFollow): Observable<string> {
|
public importExternalSourceEntry(externalSourceEntryHref: string, collectionId: string): Observable<RemoteData<WorkspaceItem>> {
|
||||||
return this.searchData.getSearchByHref(searchMethod, options, ...linksToFollow);
|
const options: HttpOptions = Object.create({});
|
||||||
|
let headers = new HttpHeaders();
|
||||||
|
headers = headers.append('Content-Type', 'text/uri-list');
|
||||||
|
options.headers = headers;
|
||||||
|
|
||||||
|
const requestId = this.requestService.generateRequestId();
|
||||||
|
const href$ = this.halService.getEndpoint(this.linkPath).pipe(map((href) => `${href}?owningCollection=${collectionId}`));
|
||||||
|
|
||||||
|
href$.pipe(
|
||||||
|
find((href: string) => hasValue(href)),
|
||||||
|
map((href: string) => {
|
||||||
|
const request = new PostRequest(requestId, href, externalSourceEntryHref, options);
|
||||||
|
this.requestService.send(request);
|
||||||
|
})
|
||||||
|
).subscribe();
|
||||||
|
|
||||||
|
return this.rdbService.buildFromRequestUUID(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an existing object on the server
|
||||||
|
* @param href The self link of the object to be removed
|
||||||
|
* @param copyVirtualMetadata (optional parameter) the identifiers of the relationship types for which the virtual
|
||||||
|
* metadata should be saved as real metadata
|
||||||
|
* @return A RemoteData observable with an empty payload, but still representing the state of the request: statusCode,
|
||||||
|
* errorMessage, timeCompleted, etc
|
||||||
|
* Only emits once all request related to the DSO has been invalidated.
|
||||||
|
*/
|
||||||
|
deleteByHref(href: string, copyVirtualMetadata?: string[]): Observable<RemoteData<NoContent>> {
|
||||||
|
return this.deleteData.deleteByHref(href, copyVirtualMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,33 +123,7 @@ export class WorkspaceitemDataService extends IdentifiableDataService<WorkspaceI
|
|||||||
* @return {Observable<RemoteData<PaginatedList<T>>}
|
* @return {Observable<RemoteData<PaginatedList<T>>}
|
||||||
* Return an observable that emits response from the server
|
* Return an observable that emits response from the server
|
||||||
*/
|
*/
|
||||||
public searchBy(searchMethod: string, options?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig<WorkspaceItem>[]): Observable<RemoteData<PaginatedList<WorkspaceItem>>> {
|
searchBy(searchMethod: string, options?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig<WorkspaceItem>[]): Observable<RemoteData<PaginatedList<WorkspaceItem>>> {
|
||||||
return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete an existing object on the server
|
|
||||||
* @param objectId The id of the object to be removed
|
|
||||||
* @param copyVirtualMetadata (optional parameter) the identifiers of the relationship types for which the virtual
|
|
||||||
* metadata should be saved as real metadata
|
|
||||||
* @return A RemoteData observable with an empty payload, but still representing the state of the request: statusCode,
|
|
||||||
* errorMessage, timeCompleted, etc
|
|
||||||
*/
|
|
||||||
public delete(objectId: string, copyVirtualMetadata?: string[]): Observable<RemoteData<NoContent>> {
|
|
||||||
return this.deleteData.delete(objectId, copyVirtualMetadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete an existing object on the server
|
|
||||||
* @param href The self link of the object to be removed
|
|
||||||
* @param copyVirtualMetadata (optional parameter) the identifiers of the relationship types for which the virtual
|
|
||||||
* metadata should be saved as real metadata
|
|
||||||
* @return A RemoteData observable with an empty payload, but still representing the state of the request: statusCode,
|
|
||||||
* errorMessage, timeCompleted, etc
|
|
||||||
* Only emits once all request related to the DSO has been invalidated.
|
|
||||||
*/
|
|
||||||
public deleteByHref(href: string, copyVirtualMetadata?: string[]): Observable<RemoteData<NoContent>> {
|
|
||||||
return this.deleteData.deleteByHref(href, copyVirtualMetadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource type for the Suggestion object
|
||||||
|
*
|
||||||
|
* Needs to be in a separate file to prevent circular
|
||||||
|
* dependencies in webpack.
|
||||||
|
*/
|
||||||
|
export const SUGGESTION = new ResourceType('suggestion');
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource type for the Suggestion Source object
|
||||||
|
*
|
||||||
|
* Needs to be in a separate file to prevent circular
|
||||||
|
* dependencies in webpack.
|
||||||
|
*/
|
||||||
|
export const SUGGESTION_SOURCE = new ResourceType('suggestionsource');
|
@@ -0,0 +1,47 @@
|
|||||||
|
import { autoserialize, deserialize } from 'cerialize';
|
||||||
|
|
||||||
|
import { SUGGESTION_SOURCE } from './suggestion-source-object.resource-type';
|
||||||
|
import { excludeFromEquals } from '../../utilities/equals.decorators';
|
||||||
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
|
import { HALLink } from '../../shared/hal-link.model';
|
||||||
|
import { typedObject } from '../../cache/builders/build-decorators';
|
||||||
|
import {CacheableObject} from '../../cache/cacheable-object.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface representing the Suggestion Source model
|
||||||
|
*/
|
||||||
|
@typedObject
|
||||||
|
export class SuggestionSource implements CacheableObject {
|
||||||
|
/**
|
||||||
|
* A string representing the kind of object, e.g. community, item, …
|
||||||
|
*/
|
||||||
|
static type = SUGGESTION_SOURCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Suggestion Target id
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total number of suggestions provided by Suggestion Target for
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of this ConfigObject
|
||||||
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
|
@autoserialize
|
||||||
|
type: ResourceType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The links to all related resources returned by the rest api.
|
||||||
|
*/
|
||||||
|
@deserialize
|
||||||
|
_links: {
|
||||||
|
self: HALLink,
|
||||||
|
suggestiontargets: HALLink
|
||||||
|
};
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource type for the Suggestion Target object
|
||||||
|
*
|
||||||
|
* Needs to be in a separate file to prevent circular
|
||||||
|
* dependencies in webpack.
|
||||||
|
*/
|
||||||
|
export const SUGGESTION_TARGET = new ResourceType('suggestiontarget');
|
@@ -0,0 +1,61 @@
|
|||||||
|
import { autoserialize, deserialize } from 'cerialize';
|
||||||
|
|
||||||
|
|
||||||
|
import { CacheableObject } from '../../cache/cacheable-object.model';
|
||||||
|
import { SUGGESTION_TARGET } from './suggestion-target-object.resource-type';
|
||||||
|
import { excludeFromEquals } from '../../utilities/equals.decorators';
|
||||||
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
|
import { HALLink } from '../../shared/hal-link.model';
|
||||||
|
import { typedObject } from '../../cache/builders/build-decorators';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface representing the Suggestion Target model
|
||||||
|
*/
|
||||||
|
@typedObject
|
||||||
|
export class SuggestionTarget implements CacheableObject {
|
||||||
|
/**
|
||||||
|
* A string representing the kind of object, e.g. community, item, …
|
||||||
|
*/
|
||||||
|
static type = SUGGESTION_TARGET;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Suggestion Target id
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Suggestion Target name to display
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
display: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Suggestion Target source to display
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
source: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total number of suggestions provided by Suggestion Target for
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of this ConfigObject
|
||||||
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
|
@autoserialize
|
||||||
|
type: ResourceType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The links to all related resources returned by the rest api.
|
||||||
|
*/
|
||||||
|
@deserialize
|
||||||
|
_links: {
|
||||||
|
self: HALLink,
|
||||||
|
suggestions: HALLink,
|
||||||
|
target: HALLink
|
||||||
|
};
|
||||||
|
}
|
@@ -0,0 +1,88 @@
|
|||||||
|
import { autoserialize, autoserializeAs, deserialize } from 'cerialize';
|
||||||
|
|
||||||
|
import { SUGGESTION } from './suggestion-objects.resource-type';
|
||||||
|
import { excludeFromEquals } from '../../utilities/equals.decorators';
|
||||||
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
|
import { HALLink } from '../../shared/hal-link.model';
|
||||||
|
import { typedObject } from '../../cache/builders/build-decorators';
|
||||||
|
import { MetadataMap, MetadataMapSerializer } from '../../shared/metadata.models';
|
||||||
|
import {CacheableObject} from '../../cache/cacheable-object.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface representing Suggestion Evidences such as scores (authorScore, datescore)
|
||||||
|
*/
|
||||||
|
export interface SuggestionEvidences {
|
||||||
|
[sectionId: string]: {
|
||||||
|
score: string;
|
||||||
|
notes: string
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* The interface representing the Suggestion Source model
|
||||||
|
*/
|
||||||
|
@typedObject
|
||||||
|
export class Suggestion implements CacheableObject {
|
||||||
|
/**
|
||||||
|
* A string representing the kind of object, e.g. community, item, …
|
||||||
|
*/
|
||||||
|
static type = SUGGESTION;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Suggestion id
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Suggestion name to display
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
display: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Suggestion source to display
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
source: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Suggestion external source uri
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
externalSourceUri: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Total Score of the suggestion
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
score: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total number of suggestions provided by Suggestion Target for
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
evidences: SuggestionEvidences;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All metadata of this suggestion object
|
||||||
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
|
@autoserializeAs(MetadataMapSerializer)
|
||||||
|
metadata: MetadataMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of this ConfigObject
|
||||||
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
|
@autoserialize
|
||||||
|
type: ResourceType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The links to all related resources returned by the rest api.
|
||||||
|
*/
|
||||||
|
@deserialize
|
||||||
|
_links: {
|
||||||
|
self: HALLink,
|
||||||
|
target: HALLink
|
||||||
|
};
|
||||||
|
}
|
@@ -0,0 +1,96 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { dataService } from '../../data/base/data-service.decorator';
|
||||||
|
import { SUGGESTION_SOURCE } from '../models/suggestion-source-object.resource-type';
|
||||||
|
import { IdentifiableDataService } from '../../data/base/identifiable-data.service';
|
||||||
|
import { SuggestionSource } from '../models/suggestion-source.model';
|
||||||
|
import { FindAllData, FindAllDataImpl } from '../../data/base/find-all-data';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { RequestService } from '../../data/request.service';
|
||||||
|
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
||||||
|
import { CoreState } from '../../core-state.model';
|
||||||
|
import { ObjectCacheService } from '../../cache/object-cache.service';
|
||||||
|
import { HALEndpointService } from '../../shared/hal-endpoint.service';
|
||||||
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { FindListOptions } from '../../data/find-list-options.model';
|
||||||
|
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
|
||||||
|
import { PaginatedList } from '../../data/paginated-list.model';
|
||||||
|
import { RemoteData } from '../../data/remote-data';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { DefaultChangeAnalyzer } from '../../data/default-change-analyzer.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that retrieves Suggestion Source data
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
@dataService(SUGGESTION_SOURCE)
|
||||||
|
export class SuggestionSourceDataService extends IdentifiableDataService<SuggestionSource> {
|
||||||
|
|
||||||
|
protected linkPath = 'suggestionsources';
|
||||||
|
|
||||||
|
private findAllData: FindAllData<SuggestionSource>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected requestService: RequestService,
|
||||||
|
protected rdbService: RemoteDataBuildService,
|
||||||
|
protected store: Store<CoreState>,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
protected halService: HALEndpointService,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected http: HttpClient,
|
||||||
|
protected comparator: DefaultChangeAnalyzer<SuggestionSource>) {
|
||||||
|
super('suggestionsources', requestService, rdbService, objectCache, halService);
|
||||||
|
this.findAllData = new FindAllDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return the list of Suggestion source.
|
||||||
|
*
|
||||||
|
* @param options Find list options object.
|
||||||
|
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||||
|
* no valid cached version. Defaults to true
|
||||||
|
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||||
|
* requested after the response becomes stale
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved.
|
||||||
|
*
|
||||||
|
* @return Observable<RemoteData<PaginatedList<QualityAssuranceSourceObject>>>
|
||||||
|
* The list of Quality Assurance source.
|
||||||
|
*/
|
||||||
|
public getSources(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<SuggestionSource>[]): Observable<RemoteData<PaginatedList<SuggestionSource>>> {
|
||||||
|
return this.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a single Suggestoin source.
|
||||||
|
*
|
||||||
|
* @param id The Quality Assurance source id
|
||||||
|
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||||
|
* no valid cached version. Defaults to true
|
||||||
|
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||||
|
* requested after the response becomes stale
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved.
|
||||||
|
*
|
||||||
|
* @return Observable<RemoteData<QualityAssuranceSourceObject>> The Quality Assurance source.
|
||||||
|
*/
|
||||||
|
public getSource(id: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<SuggestionSource>[]): Observable<RemoteData<SuggestionSource>> {
|
||||||
|
return this.findById(id, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@link RemoteData} of all object with a list of {@link FollowLinkConfig}, to indicate which embedded
|
||||||
|
* info should be added to the objects
|
||||||
|
*
|
||||||
|
* @param options Find list options object
|
||||||
|
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||||
|
* no valid cached version. Defaults to true
|
||||||
|
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||||
|
* requested after the response becomes stale
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||||
|
* {@link HALLink}s should be automatically resolved
|
||||||
|
* @return {Observable<RemoteData<PaginatedList<T>>>}
|
||||||
|
* Return an observable that emits object list
|
||||||
|
*/
|
||||||
|
findAll(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<SuggestionSource>[]): Observable<RemoteData<PaginatedList<SuggestionSource>>> {
|
||||||
|
return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,115 @@
|
|||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { RequestService } from '../../data/request.service';
|
||||||
|
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
||||||
|
import { ObjectCacheService } from '../../cache/object-cache.service';
|
||||||
|
import { HALEndpointService } from '../../shared/hal-endpoint.service';
|
||||||
|
import { RequestEntry } from '../../data/request-entry.model';
|
||||||
|
import { cold, getTestScheduler } from 'jasmine-marbles';
|
||||||
|
import { RestResponse } from '../../cache/response.models';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { CoreState } from '../../core-state.model';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
|
import { DefaultChangeAnalyzer } from '../../data/default-change-analyzer.service';
|
||||||
|
import { testFindAllDataImplementation } from '../../data/base/find-all-data.spec';
|
||||||
|
import { FindAllData } from '../../data/base/find-all-data';
|
||||||
|
import { GetRequest } from '../../data/request.models';
|
||||||
|
import {
|
||||||
|
createSuccessfulRemoteDataObject$
|
||||||
|
} from '../../../shared/remote-data.utils';
|
||||||
|
import { RemoteData } from '../../data/remote-data';
|
||||||
|
import { RequestEntryState } from '../../data/request-entry-state.model';
|
||||||
|
import { SuggestionSourceDataService } from './suggestion-source-data.service';
|
||||||
|
import { SuggestionSource } from '../models/suggestion-source.model';
|
||||||
|
|
||||||
|
describe('SuggestionSourceDataService test', () => {
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
let service: SuggestionSourceDataService;
|
||||||
|
let requestService: RequestService;
|
||||||
|
let rdbService: RemoteDataBuildService;
|
||||||
|
let objectCache: ObjectCacheService;
|
||||||
|
let halService: HALEndpointService;
|
||||||
|
let notificationsService: NotificationsService;
|
||||||
|
let http: HttpClient;
|
||||||
|
let comparator: DefaultChangeAnalyzer<SuggestionSource>;
|
||||||
|
let responseCacheEntry: RequestEntry;
|
||||||
|
|
||||||
|
const store = {} as Store<CoreState>;
|
||||||
|
const endpointURL = `https://rest.api/rest/api/suggestionsources`;
|
||||||
|
const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a';
|
||||||
|
|
||||||
|
const remoteDataMocks = {
|
||||||
|
Success: new RemoteData(null, null, null, RequestEntryState.Success, null, null, 200),
|
||||||
|
};
|
||||||
|
|
||||||
|
function initTestService() {
|
||||||
|
return new SuggestionSourceDataService(
|
||||||
|
requestService,
|
||||||
|
rdbService,
|
||||||
|
store,
|
||||||
|
objectCache,
|
||||||
|
halService,
|
||||||
|
notificationsService,
|
||||||
|
http,
|
||||||
|
comparator
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
|
||||||
|
objectCache = {} as ObjectCacheService;
|
||||||
|
http = {} as HttpClient;
|
||||||
|
notificationsService = {} as NotificationsService;
|
||||||
|
comparator = {} as DefaultChangeAnalyzer<SuggestionSource>;
|
||||||
|
responseCacheEntry = new RequestEntry();
|
||||||
|
responseCacheEntry.request = { href: 'https://rest.api/' } as any;
|
||||||
|
responseCacheEntry.response = new RestResponse(true, 200, 'Success');
|
||||||
|
|
||||||
|
requestService = jasmine.createSpyObj('requestService', {
|
||||||
|
generateRequestId: requestUUID,
|
||||||
|
send: true,
|
||||||
|
removeByHrefSubstring: {},
|
||||||
|
getByHref: observableOf(responseCacheEntry),
|
||||||
|
getByUUID: observableOf(responseCacheEntry),
|
||||||
|
});
|
||||||
|
|
||||||
|
halService = jasmine.createSpyObj('halService', {
|
||||||
|
getEndpoint: observableOf(endpointURL)
|
||||||
|
});
|
||||||
|
|
||||||
|
rdbService = jasmine.createSpyObj('rdbService', {
|
||||||
|
buildSingle: createSuccessfulRemoteDataObject$({}, 500),
|
||||||
|
buildList: cold('a', { a: remoteDataMocks.Success })
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
service = initTestService();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('composition', () => {
|
||||||
|
const initFindAllService = () => new SuggestionSourceDataService(null, null, null, null, null, null, null, null) as unknown as FindAllData<any>;
|
||||||
|
testFindAllDataImplementation(initFindAllService);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getSources', () => {
|
||||||
|
it('should send a new GetRequest', () => {
|
||||||
|
const expected = new GetRequest(requestService.generateRequestId(), `${endpointURL}`);
|
||||||
|
scheduler.schedule(() => service.getSources().subscribe());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(requestService.send).toHaveBeenCalledWith(expected, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getSource', () => {
|
||||||
|
it('should send a new GetRequest', () => {
|
||||||
|
const expected = new GetRequest(requestService.generateRequestId(), `${endpointURL}/testId`);
|
||||||
|
scheduler.schedule(() => service.getSource('testId').subscribe());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(requestService.send).toHaveBeenCalledWith(expected, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,173 @@
|
|||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { SuggestionDataServiceImpl, SuggestionsDataService } from './suggestions-data.service';
|
||||||
|
import { RequestService } from '../data/request.service';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service';
|
||||||
|
import { Suggestion } from './models/suggestion.model';
|
||||||
|
import { cold, getTestScheduler } from 'jasmine-marbles';
|
||||||
|
import { RequestEntry } from '../data/request-entry.model';
|
||||||
|
import { RestResponse } from '../cache/response.models';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
|
import { RemoteData } from '../data/remote-data';
|
||||||
|
import { RequestEntryState } from '../data/request-entry-state.model';
|
||||||
|
import { SuggestionSource } from './models/suggestion-source.model';
|
||||||
|
import { SuggestionTarget } from './models/suggestion-target.model';
|
||||||
|
import { SuggestionSourceDataService } from './source/suggestion-source-data.service';
|
||||||
|
import { SuggestionTargetDataService } from './target/suggestion-target-data.service';
|
||||||
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
|
|
||||||
|
describe('SuggestionDataService test', () => {
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
let service: SuggestionsDataService;
|
||||||
|
let requestService: RequestService;
|
||||||
|
let rdbService: RemoteDataBuildService;
|
||||||
|
let objectCache: ObjectCacheService;
|
||||||
|
let halService: HALEndpointService;
|
||||||
|
let notificationsService: NotificationsService;
|
||||||
|
let http: HttpClient;
|
||||||
|
let comparatorSuggestion: DefaultChangeAnalyzer<Suggestion>;
|
||||||
|
let comparatorSuggestionSource: DefaultChangeAnalyzer<SuggestionSource>;
|
||||||
|
let comparatorSuggestionTarget: DefaultChangeAnalyzer<SuggestionTarget>;
|
||||||
|
let suggestionSourcesDataService: SuggestionSourceDataService;
|
||||||
|
let suggestionTargetsDataService: SuggestionTargetDataService;
|
||||||
|
let suggestionsDataService: SuggestionDataServiceImpl;
|
||||||
|
let responseCacheEntry: RequestEntry;
|
||||||
|
|
||||||
|
|
||||||
|
const testSource = 'test-source';
|
||||||
|
const testUserId = '1234-4321';
|
||||||
|
const endpointURL = `https://rest.api/rest/api/`;
|
||||||
|
const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a';
|
||||||
|
const remoteDataMocks = {
|
||||||
|
Success: new RemoteData(null, null, null, RequestEntryState.Success, null, null, 200),
|
||||||
|
};
|
||||||
|
|
||||||
|
function initTestService() {
|
||||||
|
return new SuggestionsDataService(
|
||||||
|
requestService,
|
||||||
|
rdbService,
|
||||||
|
objectCache,
|
||||||
|
halService,
|
||||||
|
notificationsService,
|
||||||
|
http,
|
||||||
|
comparatorSuggestion,
|
||||||
|
comparatorSuggestionSource,
|
||||||
|
comparatorSuggestionTarget
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
|
||||||
|
objectCache = {} as ObjectCacheService;
|
||||||
|
http = {} as HttpClient;
|
||||||
|
notificationsService = {} as NotificationsService;
|
||||||
|
comparatorSuggestion = {} as DefaultChangeAnalyzer<Suggestion>;
|
||||||
|
comparatorSuggestionTarget = {} as DefaultChangeAnalyzer<SuggestionTarget>;
|
||||||
|
comparatorSuggestionSource = {} as DefaultChangeAnalyzer<SuggestionSource>;
|
||||||
|
responseCacheEntry = new RequestEntry();
|
||||||
|
responseCacheEntry.request = { href: 'https://rest.api/' } as any;
|
||||||
|
responseCacheEntry.response = new RestResponse(true, 200, 'Success');
|
||||||
|
|
||||||
|
requestService = jasmine.createSpyObj('requestService', {
|
||||||
|
generateRequestId: requestUUID,
|
||||||
|
send: true,
|
||||||
|
removeByHrefSubstring: {},
|
||||||
|
getByHref: observableOf(responseCacheEntry),
|
||||||
|
getByUUID: observableOf(responseCacheEntry),
|
||||||
|
setStaleByHrefSubstring: observableOf(true)
|
||||||
|
});
|
||||||
|
|
||||||
|
halService = jasmine.createSpyObj('halService', {
|
||||||
|
getEndpoint: observableOf(endpointURL)
|
||||||
|
});
|
||||||
|
|
||||||
|
rdbService = jasmine.createSpyObj('rdbService', {
|
||||||
|
buildSingle: createSuccessfulRemoteDataObject$({}, 500),
|
||||||
|
buildList: cold('a', { a: remoteDataMocks.Success })
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
suggestionSourcesDataService = jasmine.createSpyObj('suggestionSourcesDataService', {
|
||||||
|
getSources: observableOf(null),
|
||||||
|
});
|
||||||
|
|
||||||
|
suggestionTargetsDataService = jasmine.createSpyObj('suggestionTargetsDataService', {
|
||||||
|
getTargets: observableOf(null),
|
||||||
|
getTargetsByUser: observableOf(null),
|
||||||
|
findById: observableOf(null),
|
||||||
|
});
|
||||||
|
|
||||||
|
suggestionsDataService = jasmine.createSpyObj('suggestionsDataService', {
|
||||||
|
searchBy: observableOf(null),
|
||||||
|
delete: observableOf(null),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
service = initTestService();
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/dot-notation */
|
||||||
|
service['suggestionSourcesDataService'] = suggestionSourcesDataService;
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/dot-notation */
|
||||||
|
service['suggestionTargetsDataService'] = suggestionTargetsDataService;
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/dot-notation */
|
||||||
|
service['suggestionsDataService'] = suggestionsDataService;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Suggestion targets service', () => {
|
||||||
|
it('should call suggestionSourcesDataService.getTargets', () => {
|
||||||
|
const options = {
|
||||||
|
searchParams: [new RequestParam('source', testSource)]
|
||||||
|
};
|
||||||
|
service.getTargets(testSource);
|
||||||
|
expect(suggestionTargetsDataService.getTargets).toHaveBeenCalledWith('findBySource', options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call suggestionSourcesDataService.getTargetsByUser', () => {
|
||||||
|
const options = {
|
||||||
|
searchParams: [new RequestParam('target', testUserId)]
|
||||||
|
};
|
||||||
|
service.getTargetsByUser(testUserId);
|
||||||
|
expect(suggestionTargetsDataService.getTargetsByUser).toHaveBeenCalledWith(testUserId, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call suggestionSourcesDataService.getTargetById', () => {
|
||||||
|
service.getTargetById('1');
|
||||||
|
expect(suggestionTargetsDataService.findById).toHaveBeenCalledWith('1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('Suggestion sources service', () => {
|
||||||
|
it('should call suggestionSourcesDataService.getSources', () => {
|
||||||
|
service.getSources();
|
||||||
|
expect(suggestionSourcesDataService.getSources).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Suggestion service', () => {
|
||||||
|
it('should call suggestionsDataService.searchBy', () => {
|
||||||
|
const options = {
|
||||||
|
searchParams: [new RequestParam('target', testUserId), new RequestParam('source', testSource)]
|
||||||
|
};
|
||||||
|
service.getSuggestionsByTargetAndSource(testUserId, testSource);
|
||||||
|
expect(suggestionsDataService.searchBy).toHaveBeenCalledWith('findByTargetAndSource', options, false, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call suggestionsDataService.delete', () => {
|
||||||
|
service.deleteSuggestion('1');
|
||||||
|
expect(suggestionsDataService.delete).toHaveBeenCalledWith('1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Request service', () => {
|
||||||
|
it('should call requestService.setStaleByHrefSubstring', () => {
|
||||||
|
service.clearSuggestionRequests();
|
||||||
|
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,229 @@
|
|||||||
|
/* eslint-disable max-classes-per-file */
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { dataService } from '../cache/builders/build-decorators';
|
||||||
|
import { RequestService } from '../data/request.service';
|
||||||
|
import { UpdateDataServiceImpl } from '../data/update-data.service';
|
||||||
|
import { ChangeAnalyzer } from '../data/change-analyzer';
|
||||||
|
import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service';
|
||||||
|
import { RemoteData } from '../data/remote-data';
|
||||||
|
import { SUGGESTION } from './models/suggestion-objects.resource-type';
|
||||||
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
|
import { PaginatedList } from '../data/paginated-list.model';
|
||||||
|
import { SuggestionSource } from './models/suggestion-source.model';
|
||||||
|
import { SuggestionTarget } from './models/suggestion-target.model';
|
||||||
|
import { Suggestion } from './models/suggestion.model';
|
||||||
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
|
import { NoContent } from '../shared/NoContent.model';
|
||||||
|
import {CoreState} from '../core-state.model';
|
||||||
|
import {FindListOptions} from '../data/find-list-options.model';
|
||||||
|
import { SuggestionSourceDataService } from './source/suggestion-source-data.service';
|
||||||
|
import { SuggestionTargetDataService } from './target/suggestion-target-data.service';
|
||||||
|
|
||||||
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A private UpdateDataServiceImpl implementation to delegate specific methods to.
|
||||||
|
*/
|
||||||
|
export class SuggestionDataServiceImpl extends UpdateDataServiceImpl<Suggestion> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize service variables
|
||||||
|
* @param {RequestService} requestService
|
||||||
|
* @param {RemoteDataBuildService} rdbService
|
||||||
|
* @param {Store<CoreState>} store
|
||||||
|
* @param {ObjectCacheService} objectCache
|
||||||
|
* @param {HALEndpointService} halService
|
||||||
|
* @param {NotificationsService} notificationsService
|
||||||
|
* @param {HttpClient} http
|
||||||
|
* @param {ChangeAnalyzer<Suggestion>} comparator
|
||||||
|
* @param responseMsToLive
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
protected requestService: RequestService,
|
||||||
|
protected rdbService: RemoteDataBuildService,
|
||||||
|
protected store: Store<CoreState>,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
protected halService: HALEndpointService,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected http: HttpClient,
|
||||||
|
protected comparator: ChangeAnalyzer<Suggestion>,
|
||||||
|
protected responseMsToLive: number,
|
||||||
|
) {
|
||||||
|
super('suggestions', requestService, rdbService, objectCache, halService, notificationsService, comparator ,responseMsToLive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The service handling all Suggestion Target REST requests.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
@dataService(SUGGESTION)
|
||||||
|
export class SuggestionsDataService {
|
||||||
|
protected searchFindBySourceMethod = 'findBySource';
|
||||||
|
protected searchFindByTargetAndSourceMethod = 'findByTargetAndSource';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A private UpdateDataServiceImpl implementation to delegate specific methods to.
|
||||||
|
*/
|
||||||
|
private suggestionsDataService: SuggestionDataServiceImpl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A private UpdateDataServiceImpl implementation to delegate specific methods to.
|
||||||
|
*/
|
||||||
|
private suggestionSourcesDataService: SuggestionSourceDataService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A private UpdateDataServiceImpl implementation to delegate specific methods to.
|
||||||
|
*/
|
||||||
|
private suggestionTargetsDataService: SuggestionTargetDataService;
|
||||||
|
|
||||||
|
private responseMsToLive = 10 * 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize service variables
|
||||||
|
* @param {RequestService} requestService
|
||||||
|
* @param {RemoteDataBuildService} rdbService
|
||||||
|
* @param {ObjectCacheService} objectCache
|
||||||
|
* @param {HALEndpointService} halService
|
||||||
|
* @param {NotificationsService} notificationsService
|
||||||
|
* @param {HttpClient} http
|
||||||
|
* @param {DefaultChangeAnalyzer<Suggestion>} comparatorSuggestions
|
||||||
|
* @param {DefaultChangeAnalyzer<SuggestionSource>} comparatorSources
|
||||||
|
* @param {DefaultChangeAnalyzer<SuggestionTarget>} comparatorTargets
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
protected requestService: RequestService,
|
||||||
|
protected rdbService: RemoteDataBuildService,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
protected halService: HALEndpointService,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected http: HttpClient,
|
||||||
|
protected comparatorSuggestions: DefaultChangeAnalyzer<Suggestion>,
|
||||||
|
protected comparatorSources: DefaultChangeAnalyzer<SuggestionSource>,
|
||||||
|
protected comparatorTargets: DefaultChangeAnalyzer<SuggestionTarget>,
|
||||||
|
) {
|
||||||
|
this.suggestionsDataService = new SuggestionDataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparatorSuggestions, this.responseMsToLive);
|
||||||
|
this.suggestionSourcesDataService = new SuggestionSourceDataService(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparatorSources);
|
||||||
|
this.suggestionTargetsDataService = new SuggestionTargetDataService(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparatorTargets);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the list of Suggestion Sources
|
||||||
|
*
|
||||||
|
* @param options
|
||||||
|
* Find list options object.
|
||||||
|
* @return Observable<RemoteData<PaginatedList<SuggestionSource>>>
|
||||||
|
* The list of Suggestion Sources.
|
||||||
|
*/
|
||||||
|
public getSources(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<SuggestionSource>>> {
|
||||||
|
return this.suggestionSourcesDataService.getSources(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the list of Suggestion Target for a given source
|
||||||
|
*
|
||||||
|
* @param source
|
||||||
|
* The source for which to find targets.
|
||||||
|
* @param options
|
||||||
|
* Find list options object.
|
||||||
|
* @param linksToFollow
|
||||||
|
* List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved.
|
||||||
|
* @return Observable<RemoteData<PaginatedList<SuggestionTarget>>>
|
||||||
|
* The list of Suggestion Target.
|
||||||
|
*/
|
||||||
|
public getTargets(
|
||||||
|
source: string,
|
||||||
|
options: FindListOptions = {},
|
||||||
|
...linksToFollow: FollowLinkConfig<SuggestionTarget>[]
|
||||||
|
): Observable<RemoteData<PaginatedList<SuggestionTarget>>> {
|
||||||
|
options.searchParams = [new RequestParam('source', source)];
|
||||||
|
|
||||||
|
return this.suggestionTargetsDataService.getTargets(this.searchFindBySourceMethod, options, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the list of Suggestion Target for a given user
|
||||||
|
*
|
||||||
|
* @param userId
|
||||||
|
* The user Id for which to find targets.
|
||||||
|
* @param options
|
||||||
|
* Find list options object.
|
||||||
|
* @param linksToFollow
|
||||||
|
* List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved.
|
||||||
|
* @return Observable<RemoteData<PaginatedList<SuggestionTarget>>>
|
||||||
|
* The list of Suggestion Target.
|
||||||
|
*/
|
||||||
|
public getTargetsByUser(
|
||||||
|
userId: string,
|
||||||
|
options: FindListOptions = {},
|
||||||
|
...linksToFollow: FollowLinkConfig<SuggestionTarget>[]
|
||||||
|
): Observable<RemoteData<PaginatedList<SuggestionTarget>>> {
|
||||||
|
options.searchParams = [new RequestParam('target', userId)];
|
||||||
|
return this.suggestionTargetsDataService.getTargetsByUser(userId, options, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a Suggestion Target for a given id
|
||||||
|
*
|
||||||
|
* @param targetId
|
||||||
|
* The target id to retrieve.
|
||||||
|
*
|
||||||
|
* @return Observable<RemoteData<SuggestionTarget>>
|
||||||
|
* The list of Suggestion Target.
|
||||||
|
*/
|
||||||
|
public getTargetById(targetId: string): Observable<RemoteData<SuggestionTarget>> {
|
||||||
|
return this.suggestionTargetsDataService.findById(targetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to delete Suggestion
|
||||||
|
* @suggestionId
|
||||||
|
*/
|
||||||
|
public deleteSuggestion(suggestionId: string): Observable<RemoteData<NoContent>> {
|
||||||
|
return this.suggestionsDataService.delete(suggestionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the list of Suggestion for a given target and source
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* The target for which to find suggestions.
|
||||||
|
* @param source
|
||||||
|
* The source for which to find suggestions.
|
||||||
|
* @param options
|
||||||
|
* Find list options object.
|
||||||
|
* @param linksToFollow
|
||||||
|
* List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved.
|
||||||
|
* @return Observable<RemoteData<PaginatedList<Suggestion>>>
|
||||||
|
* The list of Suggestion.
|
||||||
|
*/
|
||||||
|
public getSuggestionsByTargetAndSource(
|
||||||
|
target: string,
|
||||||
|
source: string,
|
||||||
|
options: FindListOptions = {},
|
||||||
|
...linksToFollow: FollowLinkConfig<Suggestion>[]
|
||||||
|
): Observable<RemoteData<PaginatedList<Suggestion>>> {
|
||||||
|
options.searchParams = [
|
||||||
|
new RequestParam('target', target),
|
||||||
|
new RequestParam('source', source)
|
||||||
|
];
|
||||||
|
|
||||||
|
return this.suggestionsDataService.searchBy(this.searchFindByTargetAndSourceMethod, options, false, true, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear findByTargetAndSource suggestions requests from cache
|
||||||
|
*/
|
||||||
|
public clearSuggestionRequests() {
|
||||||
|
this.requestService.setStaleByHrefSubstring(this.searchFindByTargetAndSourceMethod);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user