mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-17 23:13:04 +00:00
Merge remote-tracking branch 'origin/main' into CST-4506_item_embargo
# Conflicts: # src/app/core/core.module.ts # src/app/submission/objects/submission-objects.effects.ts # src/app/submission/submission.module.ts
This commit is contained in:
@@ -9,13 +9,15 @@ import { GroupFormComponent } from './group-registry/group-form/group-form.compo
|
||||
import { MembersListComponent } from './group-registry/group-form/members-list/members-list.component';
|
||||
import { SubgroupsListComponent } from './group-registry/group-form/subgroup-list/subgroups-list.component';
|
||||
import { GroupsRegistryComponent } from './group-registry/groups-registry.component';
|
||||
import { FormModule } from '../shared/form/form.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
RouterModule,
|
||||
AccessControlRoutingModule
|
||||
AccessControlRoutingModule,
|
||||
FormModule
|
||||
],
|
||||
declarations: [
|
||||
EPeopleRegistryComponent,
|
||||
|
@@ -19,7 +19,7 @@
|
||||
class="btn btn-outline-secondary"><i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}</button>
|
||||
</div>
|
||||
<div between class="btn-group">
|
||||
<button class="btn btn-primary" [disabled]="!(canReset$ | async)">
|
||||
<button class="btn btn-primary" [disabled]="!(canReset$ | async)" (click)="resetPassword()">
|
||||
<i class="fa fa-key"></i> {{'admin.access-control.epeople.actions.reset' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
@@ -36,9 +36,13 @@
|
||||
</button>
|
||||
</ds-form>
|
||||
|
||||
<ds-loading [showMessage]="false" *ngIf="!formGroup"></ds-loading>
|
||||
|
||||
<div *ngIf="epersonService.getActiveEPerson() | async">
|
||||
<h5>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h5>
|
||||
|
||||
<ds-loading [showMessage]="false" *ngIf="!(groups | async)"></ds-loading>
|
||||
|
||||
<ds-pagination
|
||||
*ngIf="(groups | async)?.payload?.totalElements > 0"
|
||||
[paginationOptions]="config"
|
||||
|
@@ -2,7 +2,7 @@ import { Observable, of as observableOf } from 'rxjs';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { BrowserModule, By } from '@angular/platform-browser';
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
@@ -14,6 +14,7 @@ import { EPerson } from '../../../core/eperson/models/eperson.model';
|
||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||
import { FormBuilderService } from '../../../shared/form/builder/form-builder.service';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { EPeopleRegistryComponent } from '../epeople-registry.component';
|
||||
import { EPersonFormComponent } from './eperson-form.component';
|
||||
import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||
@@ -28,9 +29,8 @@ import { createPaginatedList } from '../../../shared/testing/utils.test';
|
||||
import { RequestService } from '../../../core/data/request.service';
|
||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||
import { FormArray, FormControl, FormGroup,Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS } from '@angular/forms';
|
||||
import { ValidateEmailNotTaken } from './validators/email-taken.validator';
|
||||
|
||||
import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service';
|
||||
|
||||
describe('EPersonFormComponent', () => {
|
||||
let component: EPersonFormComponent;
|
||||
@@ -42,6 +42,7 @@ describe('EPersonFormComponent', () => {
|
||||
let authService: AuthServiceStub;
|
||||
let authorizationService: AuthorizationDataService;
|
||||
let groupsDataService: GroupDataService;
|
||||
let epersonRegistrationService: EpersonRegistrationService;
|
||||
|
||||
let paginationService;
|
||||
|
||||
@@ -199,12 +200,18 @@ describe('EPersonFormComponent', () => {
|
||||
{ provide: AuthService, useValue: authService },
|
||||
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||
{ provide: PaginationService, useValue: paginationService },
|
||||
{ provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeByHrefSubstring']) }
|
||||
{ provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeByHrefSubstring'])},
|
||||
{ provide: EpersonRegistrationService, useValue: epersonRegistrationService },
|
||||
EPeopleRegistryComponent
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', {
|
||||
registerEmail: createSuccessfulRemoteDataObject$(null)
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EPersonFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
@@ -514,4 +521,23 @@ describe('EPersonFormComponent', () => {
|
||||
expect(component.epersonService.deleteEPerson).toHaveBeenCalledWith(eperson);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Reset Password', () => {
|
||||
let ePersonId;
|
||||
let ePersonEmail;
|
||||
|
||||
beforeEach(() => {
|
||||
ePersonId = 'testEPersonId';
|
||||
ePersonEmail = 'person.email@4science.it';
|
||||
component.epersonInitial = Object.assign(new EPerson(), {
|
||||
id: ePersonId,
|
||||
email: ePersonEmail
|
||||
});
|
||||
component.resetPassword();
|
||||
});
|
||||
|
||||
it('should call epersonRegistrationService.registerEmail', () => {
|
||||
expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith(ePersonEmail);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -34,6 +34,8 @@ import { NoContent } from '../../../core/shared/NoContent.model';
|
||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||
import { ValidateEmailNotTaken } from './validators/email-taken.validator';
|
||||
import { Registration } from '../../../core/shared/registration.model';
|
||||
import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-eperson-form',
|
||||
@@ -121,7 +123,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
||||
* Observable whether or not the admin is allowed to reset the EPerson's password
|
||||
* TODO: Initialize the observable once the REST API supports this (currently hardcoded to return false)
|
||||
*/
|
||||
canReset$: Observable<boolean> = observableOf(false);
|
||||
canReset$: Observable<boolean>;
|
||||
|
||||
/**
|
||||
* Observable whether or not the admin is allowed to delete the EPerson
|
||||
@@ -167,17 +169,20 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
emailValueChangeSubscribe: Subscription;
|
||||
|
||||
constructor(protected changeDetectorRef: ChangeDetectorRef,
|
||||
public epersonService: EPersonDataService,
|
||||
public groupsDataService: GroupDataService,
|
||||
private formBuilderService: FormBuilderService,
|
||||
private translateService: TranslateService,
|
||||
private notificationsService: NotificationsService,
|
||||
private authService: AuthService,
|
||||
private authorizationService: AuthorizationDataService,
|
||||
private modalService: NgbModal,
|
||||
private paginationService: PaginationService,
|
||||
public requestService: RequestService) {
|
||||
constructor(
|
||||
protected changeDetectorRef: ChangeDetectorRef,
|
||||
public epersonService: EPersonDataService,
|
||||
public groupsDataService: GroupDataService,
|
||||
private formBuilderService: FormBuilderService,
|
||||
private translateService: TranslateService,
|
||||
private notificationsService: NotificationsService,
|
||||
private authService: AuthService,
|
||||
private authorizationService: AuthorizationDataService,
|
||||
private modalService: NgbModal,
|
||||
private paginationService: PaginationService,
|
||||
public requestService: RequestService,
|
||||
private epersonRegistrationService: EpersonRegistrationService,
|
||||
) {
|
||||
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
||||
this.epersonInitial = eperson;
|
||||
if (hasValue(eperson)) {
|
||||
@@ -310,6 +315,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
||||
this.canDelete$ = activeEPerson$.pipe(
|
||||
switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined))
|
||||
);
|
||||
this.canReset$ = observableOf(true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -479,6 +485,26 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
||||
this.isImpersonated = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an email to current eperson address with the information
|
||||
* to reset password
|
||||
*/
|
||||
resetPassword() {
|
||||
if (hasValue(this.epersonInitial.email)) {
|
||||
this.epersonRegistrationService.registerEmail(this.epersonInitial.email).pipe(getFirstCompletedRemoteData())
|
||||
.subscribe((response: RemoteData<Registration>) => {
|
||||
if (response.hasSucceeded) {
|
||||
this.notificationsService.success(this.translateService.get('admin.access-control.epeople.actions.reset'),
|
||||
this.translateService.get('forgot-email.form.success.content', {email: this.epersonInitial.email}));
|
||||
} else {
|
||||
this.notificationsService.error(this.translateService.get('forgot-email.form.error.head'),
|
||||
this.translateService.get('forgot-email.form.error.content', {email: this.epersonInitial.email}));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the current edit when component is destroyed & unsub all subscriptions
|
||||
*/
|
||||
|
@@ -8,6 +8,7 @@ import { SharedModule } from '../../shared/shared.module';
|
||||
import { MetadataSchemaFormComponent } from './metadata-registry/metadata-schema-form/metadata-schema-form.component';
|
||||
import { MetadataFieldFormComponent } from './metadata-schema/metadata-field-form/metadata-field-form.component';
|
||||
import { BitstreamFormatsModule } from './bitstream-formats/bitstream-formats.module';
|
||||
import { FormModule } from '../../shared/form/form.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -15,7 +16,8 @@ import { BitstreamFormatsModule } from './bitstream-formats/bitstream-formats.mo
|
||||
SharedModule,
|
||||
RouterModule,
|
||||
BitstreamFormatsModule,
|
||||
AdminRegistriesRoutingModule
|
||||
AdminRegistriesRoutingModule,
|
||||
FormModule
|
||||
],
|
||||
declarations: [
|
||||
MetadataRegistryComponent,
|
||||
|
@@ -7,13 +7,15 @@ import { FormatFormComponent } from './format-form/format-form.component';
|
||||
import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component';
|
||||
import { BitstreamFormatsRoutingModule } from './bitstream-formats-routing.module';
|
||||
import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component';
|
||||
import { FormModule } from '../../../shared/form/form.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
RouterModule,
|
||||
BitstreamFormatsRoutingModule
|
||||
BitstreamFormatsRoutingModule,
|
||||
FormModule
|
||||
],
|
||||
declarations: [
|
||||
BitstreamFormatsComponent,
|
||||
|
@@ -10,6 +10,7 @@ import { CollectionAdminSearchResultGridElementComponent } from './admin-search-
|
||||
import { ItemAdminSearchResultActionsComponent } from './admin-search-results/item-admin-search-result-actions.component';
|
||||
import { JournalEntitiesModule } from '../../entity-groups/journal-entities/journal-entities.module';
|
||||
import { ResearchEntitiesModule } from '../../entity-groups/research-entities/research-entities.module';
|
||||
import { SearchModule } from '../../shared/search/search.module';
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
// put only entry components that use custom decorator
|
||||
@@ -24,6 +25,7 @@ const ENTRY_COMPONENTS = [
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SearchModule,
|
||||
SharedModule.withEntryComponents(),
|
||||
JournalEntitiesModule.withEntryComponents(),
|
||||
ResearchEntitiesModule.withEntryComponents()
|
||||
|
@@ -18,6 +18,8 @@ import { ActivatedRoute } from '@angular/router';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||
import createSpy = jasmine.createSpy;
|
||||
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
|
||||
describe('AdminSidebarComponent', () => {
|
||||
let comp: AdminSidebarComponent;
|
||||
@@ -26,6 +28,28 @@ describe('AdminSidebarComponent', () => {
|
||||
let authorizationService: AuthorizationDataService;
|
||||
let scriptService;
|
||||
|
||||
|
||||
const mockItem = Object.assign(new Item(), {
|
||||
id: 'fake-id',
|
||||
uuid: 'fake-id',
|
||||
handle: 'fake/handle',
|
||||
lastModified: '2018',
|
||||
_links: {
|
||||
self: {
|
||||
href: 'https://localhost:8000/items/fake-id'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const routeStub = {
|
||||
data: observableOf({
|
||||
dso: createSuccessfulRemoteDataObject(mockItem)
|
||||
}),
|
||||
children: []
|
||||
};
|
||||
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||
isAuthorized: observableOf(true)
|
||||
@@ -42,6 +66,7 @@ describe('AdminSidebarComponent', () => {
|
||||
{ provide: ActivatedRoute, useValue: {} },
|
||||
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||
{ provide: ScriptDataService, useValue: scriptService },
|
||||
{ provide: ActivatedRoute, useValue: routeStub },
|
||||
{
|
||||
provide: NgbModal, useValue: {
|
||||
open: () => {/*comment*/
|
||||
@@ -229,19 +254,19 @@ describe('AdminSidebarComponent', () => {
|
||||
|
||||
it('should contain site admin section', () => {
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||
id: 'admin_search', visible: true,
|
||||
id: 'admin_search', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||
id: 'registries', visible: true,
|
||||
id: 'registries', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||
parentID: 'registries', visible: true,
|
||||
parentID: 'registries', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||
id: 'curation_tasks', visible: true,
|
||||
id: 'curation_tasks', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||
id: 'workflow', visible: true,
|
||||
id: 'workflow', visible: true,
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -259,7 +284,7 @@ describe('AdminSidebarComponent', () => {
|
||||
|
||||
it('should show edit_community', () => {
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||
id: 'edit_community', visible: true,
|
||||
id: 'edit_community', visible: true,
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -277,7 +302,7 @@ describe('AdminSidebarComponent', () => {
|
||||
|
||||
it('should show edit_collection', () => {
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||
id: 'edit_collection', visible: true,
|
||||
id: 'edit_collection', visible: true,
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -295,10 +320,10 @@ describe('AdminSidebarComponent', () => {
|
||||
|
||||
it('should show access control section', () => {
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||
id: 'access_control', visible: true,
|
||||
id: 'access_control', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||
parentID: 'access_control', visible: true,
|
||||
parentID: 'access_control', visible: true,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@@ -21,6 +21,7 @@ import { MenuService } from '../../shared/menu/menu.service';
|
||||
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
|
||||
/**
|
||||
* Component representing the admin sidebar
|
||||
@@ -63,14 +64,15 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||
inFocus$: BehaviorSubject<boolean>;
|
||||
|
||||
constructor(protected menuService: MenuService,
|
||||
protected injector: Injector,
|
||||
private variableService: CSSVariableService,
|
||||
private authService: AuthService,
|
||||
private modalService: NgbModal,
|
||||
private authorizationService: AuthorizationDataService,
|
||||
private scriptDataService: ScriptDataService,
|
||||
protected injector: Injector,
|
||||
private variableService: CSSVariableService,
|
||||
private authService: AuthService,
|
||||
private modalService: NgbModal,
|
||||
public authorizationService: AuthorizationDataService,
|
||||
private scriptDataService: ScriptDataService,
|
||||
public route: ActivatedRoute
|
||||
) {
|
||||
super(menuService, injector);
|
||||
super(menuService, injector, authorizationService, route);
|
||||
this.inFocus$ = new BehaviorSubject(false);
|
||||
}
|
||||
|
||||
@@ -144,7 +146,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.new'
|
||||
} as TextMenuItemModel,
|
||||
icon: 'plus',
|
||||
icon: 'plus',
|
||||
index: 0
|
||||
},
|
||||
{
|
||||
|
@@ -5,6 +5,7 @@ import { WorkflowItemSearchResultAdminWorkflowGridElementComponent } from './adm
|
||||
import { WorkflowItemAdminWorkflowActionsComponent } from './admin-workflow-search-results/workflow-item-admin-workflow-actions.component';
|
||||
import { WorkflowItemSearchResultAdminWorkflowListElementComponent } from './admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component';
|
||||
import { AdminWorkflowPageComponent } from './admin-workflow-page.component';
|
||||
import { SearchModule } from '../../shared/search/search.module';
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
// put only entry components that use custom decorator
|
||||
@@ -14,6 +15,7 @@ const ENTRY_COMPONENTS = [
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SearchModule,
|
||||
SharedModule.withEntryComponents()
|
||||
],
|
||||
declarations: [
|
||||
|
@@ -24,7 +24,7 @@ const ENTRY_COMPONENTS = [
|
||||
AccessControlModule,
|
||||
AdminSearchModule.withEntryComponents(),
|
||||
AdminWorkflowModuleModule.withEntryComponents(),
|
||||
SharedModule,
|
||||
SharedModule
|
||||
],
|
||||
declarations: [
|
||||
AdminCurationTasksComponent,
|
||||
|
@@ -36,6 +36,8 @@ import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
||||
import { ThemeService } from './shared/theme-support/theme.service';
|
||||
import { getMockThemeService } from './shared/mocks/theme-service.mock';
|
||||
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
||||
import { APP_CONFIG } from '../config/app-config.interface';
|
||||
import { environment } from '../environments/environment';
|
||||
|
||||
let comp: AppComponent;
|
||||
let fixture: ComponentFixture<AppComponent>;
|
||||
@@ -83,6 +85,7 @@ describe('App component', () => {
|
||||
{ provide: LocaleService, useValue: getMockLocaleService() },
|
||||
{ provide: ThemeService, useValue: getMockThemeService() },
|
||||
{ provide: BreadcrumbsService, useValue: breadcrumbsServiceSpy },
|
||||
{ provide: APP_CONFIG, useValue: environment },
|
||||
provideMockStore({ initialState }),
|
||||
AppComponent,
|
||||
RouteService
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { distinctUntilChanged, filter, switchMap, take, withLatestFrom } from 'rxjs/operators';
|
||||
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
@@ -17,8 +18,10 @@ import {
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
|
||||
import { isEqual } from 'lodash';
|
||||
import { BehaviorSubject, Observable, of } from 'rxjs';
|
||||
import { select, Store } from '@ngrx/store';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
||||
|
||||
@@ -39,13 +42,12 @@ import { LocaleService } from './core/locale/locale.service';
|
||||
import { hasNoValue, hasValue, isNotEmpty } from './shared/empty.util';
|
||||
import { KlaroService } from './shared/cookies/klaro.service';
|
||||
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
||||
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
||||
import { ThemeService } from './shared/theme-support/theme.service';
|
||||
import { BASE_THEME_NAME } from './shared/theme-support/theme.constants';
|
||||
import { DEFAULT_THEME_CONFIG } from './shared/theme-support/theme.effects';
|
||||
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
||||
import { IdleModalComponent } from './shared/idle-modal/idle-modal.component';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { getDefaultThemeConfig } from '../config/config.util';
|
||||
import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-app',
|
||||
@@ -59,7 +61,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
collapsedSidebarWidth: Observable<string>;
|
||||
totalSidebarWidth: Observable<string>;
|
||||
theme: Observable<ThemeConfig> = of({} as any);
|
||||
notificationOptions = environment.notifications;
|
||||
notificationOptions;
|
||||
models;
|
||||
|
||||
/**
|
||||
@@ -88,6 +90,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
||||
@Inject(DOCUMENT) private document: any,
|
||||
@Inject(PLATFORM_ID) private platformId: any,
|
||||
@Inject(APP_CONFIG) private appConfig: AppConfig,
|
||||
private themeService: ThemeService,
|
||||
private translate: TranslateService,
|
||||
private store: Store<HostWindowState>,
|
||||
@@ -106,6 +109,12 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
@Optional() private googleAnalyticsService: GoogleAnalyticsService,
|
||||
) {
|
||||
|
||||
if (!isEqual(environment, this.appConfig)) {
|
||||
throw new Error('environment does not match app config!');
|
||||
}
|
||||
|
||||
this.notificationOptions = environment.notifications;
|
||||
|
||||
/* Use models object so all decorators are actually called */
|
||||
this.models = models;
|
||||
|
||||
@@ -116,10 +125,13 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
}
|
||||
if (hasValue(themeName)) {
|
||||
this.loadGlobalThemeConfig(themeName);
|
||||
} else if (hasValue(DEFAULT_THEME_CONFIG)) {
|
||||
this.loadGlobalThemeConfig(DEFAULT_THEME_CONFIG.name);
|
||||
} else {
|
||||
this.loadGlobalThemeConfig(BASE_THEME_NAME);
|
||||
const defaultThemeConfig = getDefaultThemeConfig();
|
||||
if (hasValue(defaultThemeConfig)) {
|
||||
this.loadGlobalThemeConfig(defaultThemeConfig.name);
|
||||
} else {
|
||||
this.loadGlobalThemeConfig(BASE_THEME_NAME);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -305,8 +317,8 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
// inherit the head tags of the parent theme
|
||||
return this.createHeadTags(parentThemeName);
|
||||
}
|
||||
|
||||
const defaultThemeName = DEFAULT_THEME_CONFIG.name;
|
||||
const defaultThemeConfig = getDefaultThemeConfig();
|
||||
const defaultThemeName = defaultThemeConfig.name;
|
||||
if (
|
||||
hasNoValue(defaultThemeName) ||
|
||||
themeName === defaultThemeName ||
|
||||
@@ -326,7 +338,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
}
|
||||
|
||||
// inherit the head tags of the default theme
|
||||
return this.createHeadTags(DEFAULT_THEME_CONFIG.name);
|
||||
return this.createHeadTags(defaultThemeConfig.name);
|
||||
}
|
||||
|
||||
return headTagConfigs.map(this.createHeadTag.bind(this));
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import { APP_BASE_HREF, CommonModule } from '@angular/common';
|
||||
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
|
||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
import { AbstractControl } from '@angular/forms';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
@@ -37,7 +39,6 @@ import { NotificationsBoardComponent } from './shared/notifications/notification
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
import { BreadcrumbsComponent } from './breadcrumbs/breadcrumbs.component';
|
||||
import { environment } from '../environments/environment';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { ForbiddenComponent } from './forbidden/forbidden.component';
|
||||
import { AuthInterceptor } from './core/auth/auth.interceptor';
|
||||
import { LocaleInterceptor } from './core/locale/locale.interceptor';
|
||||
@@ -54,16 +55,18 @@ import { ThemedBreadcrumbsComponent } from './breadcrumbs/themed-breadcrumbs.com
|
||||
import { ThemedHeaderNavbarWrapperComponent } from './header-nav-wrapper/themed-header-navbar-wrapper.component';
|
||||
import { IdleModalComponent } from './shared/idle-modal/idle-modal.component';
|
||||
|
||||
import { UUIDService } from './core/shared/uuid.service';
|
||||
import { CookieService } from './core/services/cookie.service';
|
||||
import { AbstractControl } from '@angular/forms';
|
||||
import { AppConfig, APP_CONFIG } from '../config/app-config.interface';
|
||||
|
||||
export function getBase() {
|
||||
return environment.ui.nameSpace;
|
||||
export function getConfig() {
|
||||
return environment;
|
||||
}
|
||||
|
||||
export function getMetaReducers(): MetaReducer<AppState>[] {
|
||||
return environment.debug ? [...appMetaReducers, ...debugMetaReducers] : appMetaReducers;
|
||||
export function getBase(appConfig: AppConfig) {
|
||||
return appConfig.ui.nameSpace;
|
||||
}
|
||||
|
||||
export function getMetaReducers(appConfig: AppConfig): MetaReducer<AppState>[] {
|
||||
return appConfig.debug ? [...appMetaReducers, ...debugMetaReducers] : appMetaReducers;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,13 +101,19 @@ IMPORTS.push(
|
||||
);
|
||||
|
||||
const PROVIDERS = [
|
||||
{
|
||||
provide: APP_CONFIG,
|
||||
useFactory: getConfig
|
||||
},
|
||||
{
|
||||
provide: APP_BASE_HREF,
|
||||
useFactory: (getBase)
|
||||
useFactory: getBase,
|
||||
deps: [APP_CONFIG]
|
||||
},
|
||||
{
|
||||
provide: USER_PROVIDED_META_REDUCERS,
|
||||
useFactory: getMetaReducers,
|
||||
deps: [APP_CONFIG]
|
||||
},
|
||||
{
|
||||
provide: RouterStateSerializer,
|
||||
@@ -117,7 +126,7 @@ const PROVIDERS = [
|
||||
useFactory: (store: Store<AppState>,) => {
|
||||
return () => store.dispatch(new CheckAuthenticationTokenAction());
|
||||
},
|
||||
deps: [ Store ],
|
||||
deps: [Store],
|
||||
multi: true
|
||||
},
|
||||
// register AuthInterceptor as HttpInterceptor
|
||||
@@ -144,21 +153,6 @@ const PROVIDERS = [
|
||||
useClass: LogInterceptor,
|
||||
multi: true
|
||||
},
|
||||
// insert the unique id of the user that is using the application utilizing cookies
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: (cookieService: CookieService, uuidService: UUIDService) => {
|
||||
const correlationId = cookieService.get('CORRELATION-ID');
|
||||
|
||||
// Check if cookie exists, if don't, set it with unique id
|
||||
if (!correlationId) {
|
||||
cookieService.set('CORRELATION-ID', uuidService.generate());
|
||||
}
|
||||
return () => true;
|
||||
},
|
||||
multi: true,
|
||||
deps: [ CookieService, UUIDService ]
|
||||
},
|
||||
{
|
||||
provide: DYNAMIC_ERROR_MESSAGES_MATCHER,
|
||||
useValue: ValidateEmailErrorStateMatcher
|
||||
@@ -195,7 +189,7 @@ const EXPORTS = [
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule.withServerTransition({ appId: 'serverApp' }),
|
||||
BrowserModule.withServerTransition({ appId: 'dspace-angular' }),
|
||||
...IMPORTS
|
||||
],
|
||||
providers: [
|
||||
|
@@ -49,6 +49,7 @@ import {
|
||||
import { sidebarReducer, SidebarState } from './shared/sidebar/sidebar.reducer';
|
||||
import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer';
|
||||
import { ThemeState, themeReducer } from './shared/theme-support/theme.reducer';
|
||||
import { correlationIdReducer } from './correlation-id/correlation-id.reducer';
|
||||
|
||||
export interface AppState {
|
||||
router: fromRouter.RouterReducerState;
|
||||
@@ -69,6 +70,7 @@ export interface AppState {
|
||||
communityList: CommunityListState;
|
||||
epeopleRegistry: EPeopleRegistryState;
|
||||
groupRegistry: GroupRegistryState;
|
||||
correlationId: string;
|
||||
}
|
||||
|
||||
export const appReducers: ActionReducerMap<AppState> = {
|
||||
@@ -90,6 +92,7 @@ export const appReducers: ActionReducerMap<AppState> = {
|
||||
communityList: CommunityListReducer,
|
||||
epeopleRegistry: ePeopleRegistryReducer,
|
||||
groupRegistry: groupRegistryReducer,
|
||||
correlationId: correlationIdReducer
|
||||
};
|
||||
|
||||
export const routerStateSelector = (state: AppState) => state.router;
|
||||
|
@@ -4,6 +4,8 @@ import { SharedModule } from '../shared/shared.module';
|
||||
import { EditBitstreamPageComponent } from './edit-bitstream-page/edit-bitstream-page.component';
|
||||
import { BitstreamPageRoutingModule } from './bitstream-page-routing.module';
|
||||
import { BitstreamAuthorizationsComponent } from './bitstream-authorizations/bitstream-authorizations.component';
|
||||
import { FormModule } from '../shared/form/form.module';
|
||||
import { ResourcePoliciesModule } from '../shared/resource-policies/resource-policies.module';
|
||||
|
||||
/**
|
||||
* This module handles all components that are necessary for Bitstream related pages
|
||||
@@ -12,7 +14,9 @@ import { BitstreamAuthorizationsComponent } from './bitstream-authorizations/bit
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
BitstreamPageRoutingModule
|
||||
BitstreamPageRoutingModule,
|
||||
FormModule,
|
||||
ResourcePoliciesModule
|
||||
],
|
||||
declarations: [
|
||||
BitstreamAuthorizationsComponent,
|
||||
|
@@ -12,7 +12,7 @@ import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { BrowseService } from '../../core/browse/browse.service';
|
||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
||||
import { BrowseByType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||
import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
@@ -29,13 +29,13 @@ import { SortDirection, SortOptions } from '../../core/cache/models/sort-options
|
||||
* A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields.
|
||||
* An example would be 'dateissued' for 'dc.date.issued'
|
||||
*/
|
||||
@rendersBrowseBy(BrowseByType.Date)
|
||||
@rendersBrowseBy(BrowseByDataType.Date)
|
||||
export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
||||
|
||||
/**
|
||||
* The default metadata-field 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
|
||||
*/
|
||||
defaultMetadataField = 'dc.date.issued';
|
||||
defaultMetadataKeys = ['dc.date.issued'];
|
||||
|
||||
public constructor(protected route: ActivatedRoute,
|
||||
protected browseService: BrowseService,
|
||||
@@ -59,13 +59,13 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
||||
return [Object.assign({}, routeParams, queryParams, data), currentPage, currentSort];
|
||||
})
|
||||
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
||||
const metadataField = params.metadataField || this.defaultMetadataField;
|
||||
this.browseId = params.id || this.defaultBrowseId;
|
||||
this.startsWith = +params.startsWith || params.startsWith;
|
||||
const metadataKeys = params.browseDefinition ? params.browseDefinition.metadataKeys : this.defaultMetadataKeys;
|
||||
this.browseId = params.id || this.defaultBrowseId;
|
||||
this.startsWith = +params.startsWith || params.startsWith;
|
||||
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId);
|
||||
this.updatePageWithItems(searchOptions, this.value);
|
||||
this.updatePageWithItems(searchOptions, this.value, undefined);
|
||||
this.updateParent(params.scope);
|
||||
this.updateStartsWithOptions(this.browseId, metadataField, params.scope);
|
||||
this.updateStartsWithOptions(this.browseId, metadataKeys, params.scope);
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -76,15 +76,15 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
||||
* extremely long lists with a one-year difference.
|
||||
* To determine the change in years, the config found under GlobalConfig.BrowseBy is used for this.
|
||||
* @param definition The metadata definition to fetch the first item for
|
||||
* @param metadataField The metadata field to fetch the earliest date from (expects a date field)
|
||||
* @param metadataKeys The metadata fields to fetch the earliest date from (expects a date field)
|
||||
* @param scope The scope under which to fetch the earliest item for
|
||||
*/
|
||||
updateStartsWithOptions(definition: string, metadataField: string, scope?: string) {
|
||||
updateStartsWithOptions(definition: string, metadataKeys: string[], scope?: string) {
|
||||
this.subs.push(
|
||||
this.browseService.getFirstItemFor(definition, scope).subscribe((firstItemRD: RemoteData<Item>) => {
|
||||
let lowerLimit = environment.browseBy.defaultLowerLimit;
|
||||
if (hasValue(firstItemRD.payload)) {
|
||||
const date = firstItemRD.payload.firstMetadataValue(metadataField);
|
||||
const date = firstItemRD.payload.firstMetadataValue(metadataKeys);
|
||||
if (hasValue(date)) {
|
||||
const dateObj = new Date(date);
|
||||
// TODO: it appears that getFullYear (based on local time) is sometimes unreliable. Switching to UTC.
|
||||
@@ -120,5 +120,4 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,20 +1,25 @@
|
||||
import { first } from 'rxjs/operators';
|
||||
import { BrowseByGuard } from './browse-by-guard';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { BrowseDefinitionDataService } from '../core/browse/browse-definition-data.service';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
||||
import { BrowseByDataType } from './browse-by-switcher/browse-by-decorator';
|
||||
|
||||
describe('BrowseByGuard', () => {
|
||||
describe('canActivate', () => {
|
||||
let guard: BrowseByGuard;
|
||||
let dsoService: any;
|
||||
let translateService: any;
|
||||
let browseDefinitionService: any;
|
||||
|
||||
const name = 'An interesting DSO';
|
||||
const title = 'Author';
|
||||
const field = 'Author';
|
||||
const id = 'author';
|
||||
const metadataField = 'dc.contributor';
|
||||
const scope = '1234-65487-12354-1235';
|
||||
const value = 'Filter';
|
||||
const browseDefinition = Object.assign(new BrowseDefinition(), { type: BrowseByDataType.Metadata, metadataKeys: ['dc.contributor'] });
|
||||
|
||||
beforeEach(() => {
|
||||
dsoService = {
|
||||
@@ -24,14 +29,19 @@ describe('BrowseByGuard', () => {
|
||||
translateService = {
|
||||
instant: () => field
|
||||
};
|
||||
guard = new BrowseByGuard(dsoService, translateService);
|
||||
|
||||
browseDefinitionService = {
|
||||
findById: () => createSuccessfulRemoteDataObject$(browseDefinition)
|
||||
};
|
||||
|
||||
guard = new BrowseByGuard(dsoService, translateService, browseDefinitionService);
|
||||
});
|
||||
|
||||
it('should return true, and sets up the data correctly, with a scope and value', () => {
|
||||
const scopedRoute = {
|
||||
data: {
|
||||
title: field,
|
||||
metadataField,
|
||||
browseDefinition,
|
||||
},
|
||||
params: {
|
||||
id,
|
||||
@@ -48,7 +58,7 @@ describe('BrowseByGuard', () => {
|
||||
const result = {
|
||||
title,
|
||||
id,
|
||||
metadataField,
|
||||
browseDefinition,
|
||||
collection: name,
|
||||
field,
|
||||
value: '"' + value + '"'
|
||||
@@ -63,7 +73,7 @@ describe('BrowseByGuard', () => {
|
||||
const scopedNoValueRoute = {
|
||||
data: {
|
||||
title: field,
|
||||
metadataField,
|
||||
browseDefinition,
|
||||
},
|
||||
params: {
|
||||
id,
|
||||
@@ -80,7 +90,7 @@ describe('BrowseByGuard', () => {
|
||||
const result = {
|
||||
title,
|
||||
id,
|
||||
metadataField,
|
||||
browseDefinition,
|
||||
collection: name,
|
||||
field,
|
||||
value: ''
|
||||
@@ -95,7 +105,7 @@ describe('BrowseByGuard', () => {
|
||||
const route = {
|
||||
data: {
|
||||
title: field,
|
||||
metadataField,
|
||||
browseDefinition,
|
||||
},
|
||||
params: {
|
||||
id,
|
||||
@@ -111,7 +121,7 @@ describe('BrowseByGuard', () => {
|
||||
const result = {
|
||||
title,
|
||||
id,
|
||||
metadataField,
|
||||
browseDefinition,
|
||||
collection: '',
|
||||
field,
|
||||
value: '"' + value + '"'
|
||||
|
@@ -2,11 +2,12 @@ import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angul
|
||||
import { Injectable } from '@angular/core';
|
||||
import { DSpaceObjectDataService } from '../core/data/dspace-object-data.service';
|
||||
import { hasNoValue, hasValue } from '../shared/empty.util';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { getFirstSucceededRemoteData } from '../core/shared/operators';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload } from '../core/shared/operators';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { BrowseDefinitionDataService } from '../core/browse/browse-definition-data.service';
|
||||
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
||||
|
||||
@Injectable()
|
||||
/**
|
||||
@@ -15,42 +16,46 @@ import { environment } from '../../environments/environment';
|
||||
export class BrowseByGuard implements CanActivate {
|
||||
|
||||
constructor(protected dsoService: DSpaceObjectDataService,
|
||||
protected translate: TranslateService) {
|
||||
protected translate: TranslateService,
|
||||
protected browseDefinitionService: BrowseDefinitionDataService) {
|
||||
}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const title = route.data.title;
|
||||
const id = route.params.id || route.queryParams.id || route.data.id;
|
||||
let metadataField = route.data.metadataField;
|
||||
if (hasNoValue(metadataField) && hasValue(id)) {
|
||||
const config = environment.browseBy.types.find((conf) => conf.id === id);
|
||||
if (hasValue(config) && hasValue(config.metadataField)) {
|
||||
metadataField = config.metadataField;
|
||||
}
|
||||
let browseDefinition$: Observable<BrowseDefinition>;
|
||||
if (hasNoValue(route.data.browseDefinition) && hasValue(id)) {
|
||||
browseDefinition$ = this.browseDefinitionService.findById(id).pipe(getFirstSucceededRemoteDataPayload());
|
||||
} else {
|
||||
browseDefinition$ = observableOf(route.data.browseDefinition);
|
||||
}
|
||||
const scope = route.queryParams.scope;
|
||||
const value = route.queryParams.value;
|
||||
const metadataTranslated = this.translate.instant('browse.metadata.' + id);
|
||||
if (hasValue(scope)) {
|
||||
const dsoAndMetadata$ = this.dsoService.findById(scope).pipe(getFirstSucceededRemoteData());
|
||||
return dsoAndMetadata$.pipe(
|
||||
map((dsoRD) => {
|
||||
const name = dsoRD.payload.name;
|
||||
route.data = this.createData(title, id, metadataField, name, metadataTranslated, value, route);
|
||||
return true;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
route.data = this.createData(title, id, metadataField, '', metadataTranslated, value, route);
|
||||
return observableOf(true);
|
||||
}
|
||||
return browseDefinition$.pipe(
|
||||
switchMap((browseDefinition) => {
|
||||
if (hasValue(scope)) {
|
||||
const dsoAndMetadata$ = this.dsoService.findById(scope).pipe(getFirstSucceededRemoteData());
|
||||
return dsoAndMetadata$.pipe(
|
||||
map((dsoRD) => {
|
||||
const name = dsoRD.payload.name;
|
||||
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);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private createData(title, id, metadataField, collection, field, value, route) {
|
||||
private createData(title, id, browseDefinition, collection, field, value, route) {
|
||||
return Object.assign({}, route.data, {
|
||||
title: title,
|
||||
id: id,
|
||||
metadataField: metadataField,
|
||||
browseDefinition: browseDefinition,
|
||||
collection: collection,
|
||||
field: field,
|
||||
value: hasValue(value) ? `"${value}"` : ''
|
||||
|
@@ -14,7 +14,7 @@ import { getFirstSucceededRemoteData } from '../../core/shared/operators';
|
||||
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 { BrowseByType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||
import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@@ -28,7 +28,7 @@ import { map } from 'rxjs/operators';
|
||||
* A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields.
|
||||
* An example would be 'author' for 'dc.contributor.*'
|
||||
*/
|
||||
@rendersBrowseBy(BrowseByType.Metadata)
|
||||
@rendersBrowseBy(BrowseByDataType.Metadata)
|
||||
export class BrowseByMetadataPageComponent implements OnInit {
|
||||
|
||||
/**
|
||||
@@ -99,6 +99,11 @@ export class BrowseByMetadataPageComponent implements OnInit {
|
||||
*/
|
||||
value = '';
|
||||
|
||||
/**
|
||||
* The authority key (may be undefined) associated with {@link #value}.
|
||||
*/
|
||||
authority: string;
|
||||
|
||||
/**
|
||||
* The current startsWith option (fetched and updated from query-params)
|
||||
*/
|
||||
@@ -123,11 +128,12 @@ export class BrowseByMetadataPageComponent implements OnInit {
|
||||
})
|
||||
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
||||
this.browseId = params.id || this.defaultBrowseId;
|
||||
this.authority = params.authority;
|
||||
this.value = +params.value || params.value || '';
|
||||
this.startsWith = +params.startsWith || params.startsWith;
|
||||
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId);
|
||||
if (isNotEmpty(this.value)) {
|
||||
this.updatePageWithItems(searchOptions, this.value);
|
||||
this.updatePageWithItems(searchOptions, this.value, this.authority);
|
||||
} else {
|
||||
this.updatePage(searchOptions);
|
||||
}
|
||||
@@ -166,8 +172,8 @@ export class BrowseByMetadataPageComponent implements OnInit {
|
||||
* scope: string }
|
||||
* @param value The value of the browse-entry to display items for
|
||||
*/
|
||||
updatePageWithItems(searchOptions: BrowseEntrySearchOptions, value: string) {
|
||||
this.items$ = this.browseService.getBrowseItemsFor(value, searchOptions);
|
||||
updatePageWithItems(searchOptions: BrowseEntrySearchOptions, value: string, authority: string) {
|
||||
this.items$ = this.browseService.getBrowseItemsFor(value, authority, searchOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { BrowseByType, rendersBrowseBy } from './browse-by-decorator';
|
||||
import { BrowseByDataType, rendersBrowseBy } from './browse-by-decorator';
|
||||
|
||||
describe('BrowseByDecorator', () => {
|
||||
const titleDecorator = rendersBrowseBy(BrowseByType.Title);
|
||||
const dateDecorator = rendersBrowseBy(BrowseByType.Date);
|
||||
const metadataDecorator = rendersBrowseBy(BrowseByType.Metadata);
|
||||
const titleDecorator = rendersBrowseBy(BrowseByDataType.Title);
|
||||
const dateDecorator = rendersBrowseBy(BrowseByDataType.Date);
|
||||
const metadataDecorator = rendersBrowseBy(BrowseByDataType.Metadata);
|
||||
it('should have a decorator for all types', () => {
|
||||
expect(titleDecorator.length).not.toEqual(0);
|
||||
expect(dateDecorator.length).not.toEqual(0);
|
||||
|
@@ -2,13 +2,13 @@ import { hasNoValue } from '../../shared/empty.util';
|
||||
import { InjectionToken } from '@angular/core';
|
||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||
|
||||
export enum BrowseByType {
|
||||
export enum BrowseByDataType {
|
||||
Title = 'title',
|
||||
Metadata = 'metadata',
|
||||
Metadata = 'text',
|
||||
Date = 'date'
|
||||
}
|
||||
|
||||
export const DEFAULT_BROWSE_BY_TYPE = BrowseByType.Metadata;
|
||||
export const DEFAULT_BROWSE_BY_TYPE = BrowseByDataType.Metadata;
|
||||
|
||||
export const BROWSE_BY_COMPONENT_FACTORY = new InjectionToken<(browseByType) => GenericConstructor<any>>('getComponentByBrowseByType', {
|
||||
providedIn: 'root',
|
||||
@@ -21,7 +21,7 @@ const map = new Map();
|
||||
* Decorator used for rendering Browse-By pages by type
|
||||
* @param browseByType The type of page
|
||||
*/
|
||||
export function rendersBrowseBy(browseByType: BrowseByType) {
|
||||
export function rendersBrowseBy(browseByType: BrowseByDataType) {
|
||||
return function decorator(component: any) {
|
||||
if (hasNoValue(map.get(browseByType))) {
|
||||
map.set(browseByType, component);
|
||||
|
@@ -2,20 +2,46 @@ import { BrowseBySwitcherComponent } from './browse-by-switcher.component';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { BROWSE_BY_COMPONENT_FACTORY } from './browse-by-decorator';
|
||||
import { BROWSE_BY_COMPONENT_FACTORY, BrowseByDataType } from './browse-by-decorator';
|
||||
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
||||
import { BehaviorSubject, of as observableOf } from 'rxjs';
|
||||
|
||||
describe('BrowseBySwitcherComponent', () => {
|
||||
let comp: BrowseBySwitcherComponent;
|
||||
let fixture: ComponentFixture<BrowseBySwitcherComponent>;
|
||||
|
||||
const types = environment.browseBy.types;
|
||||
const types = [
|
||||
Object.assign(
|
||||
new BrowseDefinition(), {
|
||||
id: 'title',
|
||||
dataType: BrowseByDataType.Title,
|
||||
}
|
||||
),
|
||||
Object.assign(
|
||||
new BrowseDefinition(), {
|
||||
id: 'dateissued',
|
||||
dataType: BrowseByDataType.Date,
|
||||
metadataKeys: ['dc.date.issued']
|
||||
}
|
||||
),
|
||||
Object.assign(
|
||||
new BrowseDefinition(), {
|
||||
id: 'author',
|
||||
dataType: BrowseByDataType.Metadata,
|
||||
}
|
||||
),
|
||||
Object.assign(
|
||||
new BrowseDefinition(), {
|
||||
id: 'subject',
|
||||
dataType: BrowseByDataType.Metadata,
|
||||
}
|
||||
),
|
||||
];
|
||||
|
||||
const params = new BehaviorSubject(createParamsWithId('initialValue'));
|
||||
const data = new BehaviorSubject(createDataWithBrowseDefinition(new BrowseDefinition()));
|
||||
|
||||
const activatedRouteStub = {
|
||||
params: params
|
||||
data
|
||||
};
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
@@ -34,20 +60,20 @@ describe('BrowseBySwitcherComponent', () => {
|
||||
comp = fixture.componentInstance;
|
||||
}));
|
||||
|
||||
types.forEach((type) => {
|
||||
types.forEach((type: BrowseDefinition) => {
|
||||
describe(`when switching to a browse-by page for "${type.id}"`, () => {
|
||||
beforeEach(() => {
|
||||
params.next(createParamsWithId(type.id));
|
||||
data.next(createDataWithBrowseDefinition(type));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it(`should call getComponentByBrowseByType with type "${type.type}"`, () => {
|
||||
expect((comp as any).getComponentByBrowseByType).toHaveBeenCalledWith(type.type);
|
||||
it(`should call getComponentByBrowseByType with type "${type.dataType}"`, () => {
|
||||
expect((comp as any).getComponentByBrowseByType).toHaveBeenCalledWith(type.dataType);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export function createParamsWithId(id) {
|
||||
return { id: id };
|
||||
export function createDataWithBrowseDefinition(browseDefinition) {
|
||||
return { browseDefinition: browseDefinition };
|
||||
}
|
||||
|
@@ -1,11 +1,10 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { BrowseByTypeConfig } from '../../../config/browse-by-type-config.interface';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { BROWSE_BY_COMPONENT_FACTORY } from './browse-by-decorator';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-browse-by-switcher',
|
||||
@@ -26,15 +25,11 @@ export class BrowseBySwitcherComponent implements OnInit {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the correct browse-by component by using the relevant config from environment.js
|
||||
* Fetch the correct browse-by component by using the relevant config from the route data
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.browseByComponent = this.route.params.pipe(
|
||||
map((params) => {
|
||||
const id = params.id;
|
||||
return environment.browseBy.types.find((config: BrowseByTypeConfig) => config.id === id);
|
||||
}),
|
||||
map((config: BrowseByTypeConfig) => this.getComponentByBrowseByType(config.type))
|
||||
this.browseByComponent = this.route.data.pipe(
|
||||
map((data: { browseDefinition: BrowseDefinition }) => this.getComponentByBrowseByType(data.browseDefinition.dataType))
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -10,7 +10,7 @@ import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-
|
||||
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 { BrowseByType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||
import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
@@ -23,7 +23,7 @@ import { PaginationComponentOptions } from '../../shared/pagination/pagination-c
|
||||
/**
|
||||
* Component for browsing items by title (dc.title)
|
||||
*/
|
||||
@rendersBrowseBy(BrowseByType.Title)
|
||||
@rendersBrowseBy(BrowseByDataType.Title)
|
||||
export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
|
||||
|
||||
public constructor(protected route: ActivatedRoute,
|
||||
@@ -46,7 +46,7 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
|
||||
})
|
||||
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
||||
this.browseId = params.id || this.defaultBrowseId;
|
||||
this.updatePageWithItems(browseParamsToOptions(params, currentPage, currentSort, this.browseId), undefined);
|
||||
this.updatePageWithItems(browseParamsToOptions(params, currentPage, currentSort, this.browseId), undefined, undefined);
|
||||
this.updateParent(params.scope);
|
||||
}));
|
||||
this.updateStartsWithTextOptions();
|
||||
|
@@ -6,6 +6,7 @@ import { BrowseByMetadataPageComponent } from './browse-by-metadata-page/browse-
|
||||
import { BrowseByDatePageComponent } from './browse-by-date-page/browse-by-date-page.component';
|
||||
import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switcher.component';
|
||||
import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component';
|
||||
import { ComcolModule } from '../shared/comcol/comcol.module';
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
// put only entry components that use custom decorator
|
||||
@@ -17,6 +18,7 @@ const ENTRY_COMPONENTS = [
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
ComcolModule,
|
||||
SharedModule
|
||||
],
|
||||
declarations: [
|
||||
|
@@ -10,7 +10,7 @@ import {
|
||||
} from '@ng-dynamic-forms/core';
|
||||
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component';
|
||||
import { ComColFormComponent } from '../../shared/comcol/comcol-forms/comcol-form/comcol-form.component';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
@@ -28,8 +28,8 @@ import { NONE_ENTITY_TYPE } from '../../core/shared/item-relationships/item-type
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-collection-form',
|
||||
styleUrls: ['../../shared/comcol-forms/comcol-form/comcol-form.component.scss'],
|
||||
templateUrl: '../../shared/comcol-forms/comcol-form/comcol-form.component.html'
|
||||
styleUrls: ['../../shared/comcol/comcol-forms/comcol-form/comcol-form.component.scss'],
|
||||
templateUrl: '../../shared/comcol/comcol-forms/comcol-form/comcol-form.component.html'
|
||||
})
|
||||
export class CollectionFormComponent extends ComColFormComponent<Collection> implements OnInit {
|
||||
/**
|
||||
|
@@ -2,9 +2,13 @@ import { NgModule } from '@angular/core';
|
||||
|
||||
import { CollectionFormComponent } from './collection-form.component';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { ComcolModule } from '../../shared/comcol/comcol.module';
|
||||
import { FormModule } from '../../shared/form/form.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
ComcolModule,
|
||||
FormModule,
|
||||
SharedModule
|
||||
],
|
||||
declarations: [
|
||||
|
@@ -33,7 +33,7 @@ import { ErrorComponent } from '../../shared/error/error.component';
|
||||
import { LoadingComponent } from '../../shared/loading/loading.component';
|
||||
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
||||
import { SearchService } from '../../core/shared/search/search.service';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||
import {
|
||||
createFailedRemoteDataObject$,
|
||||
createSuccessfulRemoteDataObject,
|
||||
|
@@ -9,10 +9,11 @@ import { Collection } from '../../core/shared/collection.model';
|
||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||
import { map, startWith, switchMap, take } from 'rxjs/operators';
|
||||
import {
|
||||
getRemoteDataPayload,
|
||||
getFirstSucceededRemoteData,
|
||||
toDSpaceObjectListRD,
|
||||
getFirstCompletedRemoteData, getAllSucceededRemoteData
|
||||
getAllSucceededRemoteData,
|
||||
getFirstCompletedRemoteData,
|
||||
getFirstSucceededRemoteData,
|
||||
getRemoteDataPayload,
|
||||
toDSpaceObjectListRD
|
||||
} from '../../core/shared/operators';
|
||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model';
|
||||
@@ -24,7 +25,7 @@ import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../../my-dspace-page/my-dspace-page.component';
|
||||
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||
import { SearchService } from '../../core/shared/search/search.service';
|
||||
import { followLink } from '../../shared/utils/follow-link-config.model';
|
||||
import { NoContent } from '../../core/shared/NoContent.model';
|
||||
|
@@ -1,13 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest as observableCombineLatest,
|
||||
Observable,
|
||||
Subject
|
||||
} from 'rxjs';
|
||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subject } from 'rxjs';
|
||||
import { filter, map, mergeMap, startWith, switchMap, take } from 'rxjs/operators';
|
||||
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
||||
import { PaginatedSearchOptions } from '../shared/search/models/paginated-search-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';
|
||||
@@ -103,20 +98,20 @@ export class CollectionPageComponent implements OnInit {
|
||||
const currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig);
|
||||
|
||||
this.itemRD$ = observableCombineLatest([currentPagination$, currentSort$]).pipe(
|
||||
switchMap(([currentPagination, currentSort ]) => this.collectionRD$.pipe(
|
||||
switchMap(([currentPagination, currentSort]) => this.collectionRD$.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
map((rd) => rd.payload.id),
|
||||
switchMap((id: string) => {
|
||||
return this.searchService.search(
|
||||
new PaginatedSearchOptions({
|
||||
scope: id,
|
||||
pagination: currentPagination,
|
||||
sort: currentSort,
|
||||
dsoTypes: [DSpaceObjectType.ITEM]
|
||||
})).pipe(toDSpaceObjectListRD()) as Observable<RemoteData<PaginatedList<Item>>>;
|
||||
new PaginatedSearchOptions({
|
||||
scope: id,
|
||||
pagination: currentPagination,
|
||||
sort: currentSort,
|
||||
dsoTypes: [DSpaceObjectType.ITEM]
|
||||
})).pipe(toDSpaceObjectListRD()) as Observable<RemoteData<PaginatedList<Item>>>;
|
||||
}),
|
||||
startWith(undefined) // Make sure switching pages shows loading component
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
|
@@ -14,6 +14,7 @@ import { SearchService } from '../core/shared/search/search.service';
|
||||
import { StatisticsModule } from '../statistics/statistics.module';
|
||||
import { CollectionFormModule } from './collection-form/collection-form.module';
|
||||
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
|
||||
import { ComcolModule } from '../shared/comcol/comcol.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -22,7 +23,8 @@ import { ThemedCollectionPageComponent } from './themed-collection-page.componen
|
||||
CollectionPageRoutingModule,
|
||||
StatisticsModule.forRoot(),
|
||||
EditItemPageModule,
|
||||
CollectionFormModule
|
||||
CollectionFormModule,
|
||||
ComcolModule
|
||||
],
|
||||
declarations: [
|
||||
CollectionPageComponent,
|
||||
|
@@ -2,12 +2,12 @@ import { Component } from '@angular/core';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { RouteService } from '../../core/services/route.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component';
|
||||
import { CreateComColPageComponent } from '../../shared/comcol/comcol-forms/create-comcol-page/create-comcol-page.component';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
import { RequestService } from '../../core/data/request.service';
|
||||
|
||||
/**
|
||||
* Component that represents the page where a user can create a new Collection
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { DeleteComColPageComponent } from '../../shared/comcol-forms/delete-comcol-page/delete-comcol-page.component';
|
||||
import { DeleteComColPageComponent } from '../../shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
import { RequestService } from '../../core/data/request.service';
|
||||
|
||||
/**
|
||||
* Component that represents the page where a user can delete an existing Collection
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ComcolMetadataComponent } from '../../../shared/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
|
||||
import { ComcolMetadataComponent } from '../../../shared/comcol/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
@@ -12,6 +12,7 @@ import { RequestService } from '../../../core/data/request.service';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ComcolModule } from '../../../shared/comcol/comcol.module';
|
||||
|
||||
describe('CollectionRolesComponent', () => {
|
||||
|
||||
@@ -65,6 +66,7 @@ describe('CollectionRolesComponent', () => {
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
ComcolModule,
|
||||
SharedModule,
|
||||
RouterTestingModule.withRoutes([]),
|
||||
TranslateModule.forRoot(),
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component';
|
||||
import { EditComColPageComponent } from '../../shared/comcol/comcol-forms/edit-comcol-page/edit-comcol-page.component';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { getCollectionPageRoute } from '../collection-page-routing-paths';
|
||||
|
||||
@@ -9,7 +9,7 @@ import { getCollectionPageRoute } from '../collection-page-routing-paths';
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-edit-collection',
|
||||
templateUrl: '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.html'
|
||||
templateUrl: '../../shared/comcol/comcol-forms/edit-comcol-page/edit-comcol-page.component.html'
|
||||
})
|
||||
export class EditCollectionPageComponent extends EditComColPageComponent<Collection> {
|
||||
type = 'collection';
|
||||
|
@@ -10,6 +10,9 @@ import { CollectionSourceComponent } from './collection-source/collection-source
|
||||
import { CollectionAuthorizationsComponent } from './collection-authorizations/collection-authorizations.component';
|
||||
import { CollectionFormModule } from '../collection-form/collection-form.module';
|
||||
import { CollectionSourceControlsComponent } from './collection-source/collection-source-controls/collection-source-controls.component';
|
||||
import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-policies.module';
|
||||
import { FormModule } from '../../shared/form/form.module';
|
||||
import { ComcolModule } from '../../shared/comcol/comcol.module';
|
||||
|
||||
/**
|
||||
* Module that contains all components related to the Edit Collection page administrator functionality
|
||||
@@ -19,7 +22,10 @@ import { CollectionSourceControlsComponent } from './collection-source/collectio
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
EditCollectionPageRoutingModule,
|
||||
CollectionFormModule
|
||||
CollectionFormModule,
|
||||
ResourcePoliciesModule,
|
||||
FormModule,
|
||||
ComcolModule
|
||||
],
|
||||
declarations: [
|
||||
EditCollectionPageComponent,
|
||||
|
@@ -6,7 +6,7 @@ import {
|
||||
DynamicTextAreaModel
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component';
|
||||
import { ComColFormComponent } from '../../shared/comcol/comcol-forms/comcol-form/comcol-form.component';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
@@ -19,8 +19,8 @@ import { ObjectCacheService } from '../../core/cache/object-cache.service';
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-community-form',
|
||||
styleUrls: ['../../shared/comcol-forms/comcol-form/comcol-form.component.scss'],
|
||||
templateUrl: '../../shared/comcol-forms/comcol-form/comcol-form.component.html'
|
||||
styleUrls: ['../../shared/comcol/comcol-forms/comcol-form/comcol-form.component.scss'],
|
||||
templateUrl: '../../shared/comcol/comcol-forms/comcol-form/comcol-form.component.html'
|
||||
})
|
||||
export class CommunityFormComponent extends ComColFormComponent<Community> {
|
||||
/**
|
||||
|
@@ -2,9 +2,13 @@ import { NgModule } from '@angular/core';
|
||||
|
||||
import { CommunityFormComponent } from './community-form.component';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { ComcolModule } from '../../shared/comcol/comcol.module';
|
||||
import { FormModule } from '../../shared/form/form.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
ComcolModule,
|
||||
FormModule,
|
||||
SharedModule
|
||||
],
|
||||
declarations: [
|
||||
|
@@ -12,6 +12,7 @@ import { DeleteCommunityPageComponent } from './delete-community-page/delete-com
|
||||
import { StatisticsModule } from '../statistics/statistics.module';
|
||||
import { CommunityFormModule } from './community-form/community-form.module';
|
||||
import { ThemedCommunityPageComponent } from './themed-community-page.component';
|
||||
import { ComcolModule } from '../shared/comcol/comcol.module';
|
||||
|
||||
const DECLARATIONS = [CommunityPageComponent,
|
||||
ThemedCommunityPageComponent,
|
||||
@@ -26,7 +27,8 @@ const DECLARATIONS = [CommunityPageComponent,
|
||||
SharedModule,
|
||||
CommunityPageRoutingModule,
|
||||
StatisticsModule.forRoot(),
|
||||
CommunityFormModule
|
||||
CommunityFormModule,
|
||||
ComcolModule
|
||||
],
|
||||
declarations: [
|
||||
...DECLARATIONS
|
||||
|
@@ -3,7 +3,7 @@ import { Community } from '../../core/shared/community.model';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { RouteService } from '../../core/services/route.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component';
|
||||
import { CreateComColPageComponent } from '../../shared/comcol/comcol-forms/create-comcol-page/create-comcol-page.component';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { RequestService } from '../../core/data/request.service';
|
||||
|
@@ -2,10 +2,10 @@ import { Component } from '@angular/core';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { DeleteComColPageComponent } from '../../shared/comcol-forms/delete-comcol-page/delete-comcol-page.component';
|
||||
import { DeleteComColPageComponent } from '../../shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
import { RequestService } from '../../core/data/request.service';
|
||||
|
||||
/**
|
||||
* Component that represents the page where a user can delete an existing Community
|
||||
|
@@ -2,8 +2,8 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { first, map } from 'rxjs/operators';
|
||||
import { RemoteData } from 'src/app/core/data/remote-data';
|
||||
import { DSpaceObject } from 'src/app/core/shared/dspace-object.model';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-community-authorizations',
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ComcolMetadataComponent } from '../../../shared/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
|
||||
import { ComcolMetadataComponent } from '../../../shared/comcol/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Community } from '../../../core/shared/community.model';
|
||||
import { CommunityDataService } from '../../../core/data/community-data.service';
|
||||
|
@@ -12,6 +12,7 @@ import { SharedModule } from '../../../shared/shared.module';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ComcolModule } from '../../../shared/comcol/comcol.module';
|
||||
|
||||
describe('CommunityRolesComponent', () => {
|
||||
|
||||
@@ -50,6 +51,7 @@ describe('CommunityRolesComponent', () => {
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
ComcolModule,
|
||||
SharedModule,
|
||||
RouterTestingModule.withRoutes([]),
|
||||
TranslateModule.forRoot(),
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component';
|
||||
import { EditComColPageComponent } from '../../shared/comcol/comcol-forms/edit-comcol-page/edit-comcol-page.component';
|
||||
import { getCommunityPageRoute } from '../community-page-routing-paths';
|
||||
|
||||
/**
|
||||
@@ -9,7 +9,7 @@ import { getCommunityPageRoute } from '../community-page-routing-paths';
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-edit-community',
|
||||
templateUrl: '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.html'
|
||||
templateUrl: '../../shared/comcol/comcol-forms/edit-comcol-page/edit-comcol-page.component.html'
|
||||
})
|
||||
export class EditCommunityPageComponent extends EditComColPageComponent<Community> {
|
||||
type = 'community';
|
||||
|
@@ -8,6 +8,8 @@ import { CommunityMetadataComponent } from './community-metadata/community-metad
|
||||
import { CommunityRolesComponent } from './community-roles/community-roles.component';
|
||||
import { CommunityAuthorizationsComponent } from './community-authorizations/community-authorizations.component';
|
||||
import { CommunityFormModule } from '../community-form/community-form.module';
|
||||
import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-policies.module';
|
||||
import { ComcolModule } from '../../shared/comcol/comcol.module';
|
||||
|
||||
/**
|
||||
* Module that contains all components related to the Edit Community page administrator functionality
|
||||
@@ -17,7 +19,9 @@ import { CommunityFormModule } from '../community-form/community-form.module';
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
EditCommunityPageRoutingModule,
|
||||
CommunityFormModule
|
||||
CommunityFormModule,
|
||||
ComcolModule,
|
||||
ResourcePoliciesModule
|
||||
],
|
||||
declarations: [
|
||||
EditCommunityPageComponent,
|
||||
|
@@ -9,9 +9,11 @@ describe(`BrowseDefinitionDataService`, () => {
|
||||
findAll: EMPTY,
|
||||
findByHref: EMPTY,
|
||||
findAllByHref: EMPTY,
|
||||
findById: EMPTY,
|
||||
});
|
||||
const hrefAll = 'https://rest.api/server/api/discover/browses';
|
||||
const hrefSingle = 'https://rest.api/server/api/discover/browses/author';
|
||||
const id = 'author';
|
||||
const options = new FindListOptions();
|
||||
const linksToFollow = [
|
||||
followLink('entries'),
|
||||
@@ -44,4 +46,10 @@ describe(`BrowseDefinitionDataService`, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe(`findById`, () => {
|
||||
it(`should call findById on DataServiceImpl`, () => {
|
||||
service.findAllByHref(id, options, true, false, ...linksToFollow);
|
||||
expect(dataServiceImplSpy.findAllByHref).toHaveBeenCalledWith(id, options, true, false, ...linksToFollow);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -106,6 +106,21 @@ export class BrowseDefinitionDataService {
|
||||
findAllByHref(href: string, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
|
||||
return this.dataService.findAllByHref(href, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable of {@link RemoteData} of an object, based on its ID, with a list of
|
||||
* {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
|
||||
* @param id ID of object we want to retrieve
|
||||
* @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
|
||||
*/
|
||||
findById(id: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<BrowseDefinition>> {
|
||||
return this.dataService.findById(id, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
}
|
||||
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
@@ -129,6 +129,7 @@ describe('BrowseService', () => {
|
||||
describe('getBrowseEntriesFor and findList', () => {
|
||||
// should contain special characters such that url encoding can be tested as well
|
||||
const mockAuthorName = 'Donald Smith & Sons';
|
||||
const mockAuthorityKey = 'some authority key ?=;';
|
||||
|
||||
beforeEach(() => {
|
||||
requestService = getMockRequestService(getRequestEntry$(true));
|
||||
@@ -155,7 +156,7 @@ describe('BrowseService', () => {
|
||||
it('should call hrefOnlyDataService.findAllByHref with the expected href', () => {
|
||||
const expected = browseDefinitions[1]._links.items.href + '?filterValue=' + encodeURIComponent(mockAuthorName);
|
||||
|
||||
scheduler.schedule(() => service.getBrowseItemsFor(mockAuthorName, new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe());
|
||||
scheduler.schedule(() => service.getBrowseItemsFor(mockAuthorName, undefined, new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(getFirstUsedArgumentOfSpyMethod(hrefOnlyDataService.findAllByHref)).toBeObservable(cold('(a|)', {
|
||||
@@ -164,6 +165,20 @@ describe('BrowseService', () => {
|
||||
});
|
||||
|
||||
});
|
||||
describe('when getBrowseItemsFor is called with a valid filter value and authority key', () => {
|
||||
it('should call hrefOnlyDataService.findAllByHref with the expected href', () => {
|
||||
const expected = browseDefinitions[1]._links.items.href +
|
||||
'?filterValue=' + encodeURIComponent(mockAuthorName) +
|
||||
'&filterAuthority=' + encodeURIComponent(mockAuthorityKey);
|
||||
|
||||
scheduler.schedule(() => service.getBrowseItemsFor(mockAuthorName, mockAuthorityKey, new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(getFirstUsedArgumentOfSpyMethod(hrefOnlyDataService.findAllByHref)).toBeObservable(cold('(a|)', {
|
||||
a: expected
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getBrowseURLFor', () => {
|
||||
|
@@ -105,7 +105,7 @@ export class BrowseService {
|
||||
* @param options Options to narrow down your search
|
||||
* @returns {Observable<RemoteData<PaginatedList<Item>>>}
|
||||
*/
|
||||
getBrowseItemsFor(filterValue: string, options: BrowseEntrySearchOptions): Observable<RemoteData<PaginatedList<Item>>> {
|
||||
getBrowseItemsFor(filterValue: string, filterAuthority: string, options: BrowseEntrySearchOptions): Observable<RemoteData<PaginatedList<Item>>> {
|
||||
const href$ = this.getBrowseDefinitions().pipe(
|
||||
getBrowseDefinitionLinks(options.metadataDefinition),
|
||||
hasValueOperator(),
|
||||
@@ -132,6 +132,9 @@ export class BrowseService {
|
||||
if (isNotEmpty(filterValue)) {
|
||||
args.push(`filterValue=${encodeURIComponent(filterValue)}`);
|
||||
}
|
||||
if (isNotEmpty(filterAuthority)) {
|
||||
args.push(`filterAuthority=${encodeURIComponent(filterAuthority)}`);
|
||||
}
|
||||
if (isNotEmpty(args)) {
|
||||
href = new URLCombiner(href, `?${args.join('&')}`).toString();
|
||||
}
|
||||
|
@@ -159,6 +159,7 @@ import { RootDataService } from './data/root-data.service';
|
||||
import { Root } from './data/root.model';
|
||||
import { SearchConfig } from './shared/search/search-filters/search-config.model';
|
||||
import { SequenceService } from './shared/sequence.service';
|
||||
import { GroupDataService } from './eperson/group-data.service';
|
||||
import { SubmissionAccessesModel } from './config/models/config-submission-accesses.model';
|
||||
|
||||
/**
|
||||
@@ -282,6 +283,7 @@ const PROVIDERS = [
|
||||
VocabularyService,
|
||||
VocabularyTreeviewService,
|
||||
SequenceService,
|
||||
GroupDataService
|
||||
];
|
||||
|
||||
/**
|
||||
|
@@ -20,7 +20,7 @@ import { PaginatedList } from './paginated-list.model';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { FindListOptions, GetRequest } from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||
import { Bitstream } from '../shared/bitstream.model';
|
||||
import { RequestEntryState } from './request.reducer';
|
||||
|
||||
|
@@ -12,7 +12,7 @@ import { HttpClient } from '@angular/common/http';
|
||||
import { FindListOptions } from './request.models';
|
||||
import { Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, map, switchMap, take } from 'rxjs/operators';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||
import { hasValue, isNotEmptyOperator } from '../../shared/empty.util';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { PaginatedList } from './paginated-list.model';
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { SearchFilterConfig } from '../../shared/search/search-filter-config.model';
|
||||
import { SearchFilterConfig } from '../../shared/search/models/search-filter-config.model';
|
||||
import { ParsedResponse } from '../cache/response.models';
|
||||
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
||||
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
||||
import { RestRequest } from './request.models';
|
||||
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
||||
import { FacetConfigResponse } from '../../shared/search/facet-config-response.model';
|
||||
import { FacetConfigResponse } from '../../shared/search/models/facet-config-response.model';
|
||||
|
||||
@Injectable()
|
||||
export class FacetConfigResponseParsingService extends DspaceRestResponseParsingService {
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { FacetValue } from '../../shared/search/facet-value.model';
|
||||
import { FacetValue } from '../../shared/search/models/facet-value.model';
|
||||
import { ParsedResponse } from '../cache/response.models';
|
||||
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
||||
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
||||
import { RestRequest } from './request.models';
|
||||
import { FacetValues } from '../../shared/search/facet-values.model';
|
||||
import { FacetValues } from '../../shared/search/models/facet-values.model';
|
||||
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
||||
|
||||
@Injectable()
|
||||
|
@@ -0,0 +1,27 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { SingleFeatureAuthorizationGuard } from './single-feature-authorization.guard';
|
||||
import { AuthorizationDataService } from '../authorization-data.service';
|
||||
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { AuthService } from '../../../auth/auth.service';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { FeatureID } from '../feature-id';
|
||||
|
||||
/**
|
||||
* Prevent unauthorized activating and loading of routes when the current authenticated user doesn't have group
|
||||
* management rights
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class StatisticsAdministratorGuard extends SingleFeatureAuthorizationGuard {
|
||||
constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) {
|
||||
super(authorizationService, router, authService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check group management rights
|
||||
*/
|
||||
getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID> {
|
||||
return observableOf(FeatureID.CanViewUsageStatistics);
|
||||
}
|
||||
}
|
@@ -13,6 +13,7 @@ export enum FeatureID {
|
||||
CanManageGroup = 'canManageGroup',
|
||||
IsCollectionAdmin = 'isCollectionAdmin',
|
||||
IsCommunityAdmin = 'isCommunityAdmin',
|
||||
CanChangePassword = 'canChangePassword',
|
||||
CanDownload = 'canDownload',
|
||||
CanRequestACopy = 'canRequestACopy',
|
||||
CanManageVersions = 'canManageVersions',
|
||||
@@ -25,4 +26,5 @@ export enum FeatureID {
|
||||
CanEditVersion = 'canEditVersion',
|
||||
CanDeleteVersion = 'canDeleteVersion',
|
||||
CanCreateVersion = 'canCreateVersion',
|
||||
CanViewUsageStatistics = 'canViewUsageStatistics',
|
||||
}
|
||||
|
@@ -25,7 +25,7 @@ import { PaginatedList } from './paginated-list.model';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { DeleteRequest, FindListOptions, GetRequest, PostRequest, PutRequest, RestRequest } from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||
import { Bundle } from '../shared/bundle.model';
|
||||
import { MetadataMap } from '../shared/metadata.models';
|
||||
import { BundleDataService } from './bundle-data.service';
|
||||
|
@@ -5,9 +5,9 @@ import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.util
|
||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||
import { buildPaginatedList } from './paginated-list.model';
|
||||
import { PageInfo } from '../shared/page-info.model';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||
import { RelationshipOptions } from '../../shared/form/builder/models/relationship-options.model';
|
||||
import { SearchResult } from '../../shared/search/search-result.model';
|
||||
import { SearchResult } from '../../shared/search/models/search-result.model';
|
||||
import { Item } from '../shared/item.model';
|
||||
import { skip, take } from 'rxjs/operators';
|
||||
import { ExternalSource } from '../shared/external-source.model';
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { ExternalSourceService } from './external-source.service';
|
||||
import { SearchService } from '../shared/search/search.service';
|
||||
import { concat, distinctUntilChanged, map, multicast, startWith, take, takeWhile } from 'rxjs/operators';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||
import { Observable, ReplaySubject } from 'rxjs';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { PaginatedList } from './paginated-list.model';
|
||||
import { SearchResult } from '../../shared/search/search-result.model';
|
||||
import { SearchResult } from '../../shared/search/models/search-result.model';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { RelationshipOptions } from '../../shared/form/builder/models/relationship-options.model';
|
||||
import { Item } from '../shared/item.model';
|
||||
|
@@ -4,7 +4,7 @@ import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
||||
import { RestRequest } from './request.models';
|
||||
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { SearchObjects } from '../../shared/search/search-objects.model';
|
||||
import { SearchObjects } from '../../shared/search/models/search-objects.model';
|
||||
import { MetadataMap, MetadataValue } from '../shared/metadata.models';
|
||||
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
||||
|
||||
|
@@ -20,7 +20,7 @@ export enum IdentifierType {
|
||||
}
|
||||
|
||||
export abstract class RestRequest {
|
||||
public responseMsToLive = environment.cache.msToLive.default;
|
||||
public responseMsToLive;
|
||||
public isMultipart = false;
|
||||
|
||||
constructor(
|
||||
@@ -30,6 +30,7 @@ export abstract class RestRequest {
|
||||
public body?: any,
|
||||
public options?: HttpOptions,
|
||||
) {
|
||||
this.responseMsToLive = environment.cache.msToLive.default;
|
||||
}
|
||||
|
||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { SearchObjects } from '../../shared/search/search-objects.model';
|
||||
import { SearchObjects } from '../../shared/search/models/search-objects.model';
|
||||
import { ParsedResponse } from '../cache/response.models';
|
||||
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
||||
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
||||
|
@@ -12,7 +12,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||
import { FindListOptions, PostRequest, RestRequest } from './request.models';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { PaginatedList } from './paginated-list.model';
|
||||
import { Version } from '../shared/version.model';
|
||||
|
@@ -40,9 +40,7 @@ const editGroupSelector = createSelector(groupRegistryStateSelector, (groupRegis
|
||||
/**
|
||||
* Provides methods to retrieve eperson group resources from the REST API & Group related CRUD actions.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
@Injectable()
|
||||
@dataService(GROUP)
|
||||
export class GroupDataService extends DataService<Group> {
|
||||
protected linkPath = 'groups';
|
||||
|
@@ -9,12 +9,17 @@ import { RestRequestMethod } from '../data/rest-request-method';
|
||||
import { CookieService } from '../services/cookie.service';
|
||||
import { CookieServiceMock } from '../../shared/mocks/cookie.service.mock';
|
||||
import { RouterStub } from '../../shared/testing/router.stub';
|
||||
import { CorrelationIdService } from '../../correlation-id/correlation-id.service';
|
||||
import { UUIDService } from '../shared/uuid.service';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { appReducers, storeModuleConfig } from '../../app.reducer';
|
||||
|
||||
|
||||
describe('LogInterceptor', () => {
|
||||
let service: DspaceRestService;
|
||||
let httpMock: HttpTestingController;
|
||||
let cookieService: CookieService;
|
||||
let correlationIdService: CorrelationIdService;
|
||||
const router = Object.assign(new RouterStub(),{url : '/statistics'});
|
||||
|
||||
// Mock payload/statuses are dummy content as we are not testing the results
|
||||
@@ -28,7 +33,10 @@ describe('LogInterceptor', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [HttpClientTestingModule],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
StoreModule.forRoot(appReducers, storeModuleConfig),
|
||||
],
|
||||
providers: [
|
||||
DspaceRestService,
|
||||
// LogInterceptor,
|
||||
@@ -39,14 +47,18 @@ describe('LogInterceptor', () => {
|
||||
},
|
||||
{ provide: CookieService, useValue: new CookieServiceMock() },
|
||||
{ provide: Router, useValue: router },
|
||||
{ provide: CorrelationIdService, useClass: CorrelationIdService },
|
||||
{ provide: UUIDService, useClass: UUIDService },
|
||||
],
|
||||
});
|
||||
|
||||
service = TestBed.get(DspaceRestService);
|
||||
httpMock = TestBed.get(HttpTestingController);
|
||||
cookieService = TestBed.get(CookieService);
|
||||
service = TestBed.inject(DspaceRestService);
|
||||
httpMock = TestBed.inject(HttpTestingController);
|
||||
cookieService = TestBed.inject(CookieService);
|
||||
correlationIdService = TestBed.inject(CorrelationIdService);
|
||||
|
||||
cookieService.set('CORRELATION-ID','123455');
|
||||
correlationIdService.initCorrelationId();
|
||||
});
|
||||
|
||||
|
||||
|
@@ -3,9 +3,8 @@ import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/c
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { CookieService } from '../services/cookie.service';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { CorrelationIdService } from '../../correlation-id/correlation-id.service';
|
||||
|
||||
/**
|
||||
* Log Interceptor intercepting Http Requests & Responses to
|
||||
@@ -15,12 +14,12 @@ import { hasValue } from '../../shared/empty.util';
|
||||
@Injectable()
|
||||
export class LogInterceptor implements HttpInterceptor {
|
||||
|
||||
constructor(private cookieService: CookieService, private router: Router) {}
|
||||
constructor(private cidService: CorrelationIdService, private router: Router) {}
|
||||
|
||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
|
||||
// Get Unique id of the user from the cookies
|
||||
const correlationId = this.cookieService.get('CORRELATION-ID');
|
||||
// Get the correlation id for the user from the store
|
||||
const correlationId = this.cidService.getCorrelationId();
|
||||
|
||||
// Add headers from the intercepted request
|
||||
let headers = request.headers;
|
||||
|
@@ -6,6 +6,7 @@ import { BROWSE_DEFINITION } from './browse-definition.resource-type';
|
||||
import { HALLink } from './hal-link.model';
|
||||
import { ResourceType } from './resource-type';
|
||||
import { SortOption } from './sort-option.model';
|
||||
import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator';
|
||||
|
||||
@typedObject
|
||||
export class BrowseDefinition extends CacheableObject {
|
||||
@@ -33,6 +34,9 @@ export class BrowseDefinition extends CacheableObject {
|
||||
@autoserializeAs('metadata')
|
||||
metadataKeys: string[];
|
||||
|
||||
@autoserialize
|
||||
dataType: BrowseByDataType;
|
||||
|
||||
get self(): string {
|
||||
return this._links.self.href;
|
||||
}
|
||||
|
@@ -89,7 +89,7 @@ describe('HALEndpointService', () => {
|
||||
describe('getRootEndpointMap', () => {
|
||||
it('should send a new EndpointMapRequest', () => {
|
||||
(service as any).getRootEndpointMap();
|
||||
const expected = new EndpointMapRequest(requestService.generateRequestId(), environment.rest.baseUrl + 'api');
|
||||
const expected = new EndpointMapRequest(requestService.generateRequestId(), `${environment.rest.baseUrl}/api`);
|
||||
expect(requestService.send).toHaveBeenCalledWith(expected, true);
|
||||
});
|
||||
|
||||
|
@@ -13,7 +13,7 @@ import {
|
||||
withLatestFrom
|
||||
} from 'rxjs/operators';
|
||||
import { hasNoValue, hasValue, hasValueOperator, isNotEmpty } from '../../shared/empty.util';
|
||||
import { SearchResult } from '../../shared/search/search-result.model';
|
||||
import { SearchResult } from '../../shared/search/models/search-result.model';
|
||||
import { PaginatedList } from '../data/paginated-list.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { RestRequest } from '../data/request.models';
|
||||
|
@@ -2,8 +2,8 @@ import { SearchConfigurationService } from './search-configuration.service';
|
||||
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
||||
import { SearchFilter } from '../../../shared/search/search-filter.model';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||
import { SearchFilter } from '../../../shared/search/models/search-filter.model';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||
|
||||
|
@@ -3,30 +3,25 @@ import { ActivatedRoute, Params } from '@angular/router';
|
||||
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
combineLatest as observableCombineLatest,
|
||||
merge as observableMerge,
|
||||
Observable,
|
||||
Subscription
|
||||
} from 'rxjs';
|
||||
import { distinctUntilChanged, filter, map, startWith, switchMap, take } from 'rxjs/operators';
|
||||
import { filter, map, startWith } from 'rxjs/operators';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
import { SearchOptions } from '../../../shared/search/search-options.model';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
||||
import { SearchFilter } from '../../../shared/search/search-filter.model';
|
||||
import { SearchOptions } from '../../../shared/search/models/search-options.model';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||
import { SearchFilter } from '../../../shared/search/models/search-filter.model';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { DSpaceObjectType } from '../dspace-object-type.model';
|
||||
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
||||
import { RouteService } from '../../services/route.service';
|
||||
import {
|
||||
getAllSucceededRemoteDataPayload,
|
||||
getFirstSucceededRemoteData
|
||||
} from '../operators';
|
||||
import { getAllSucceededRemoteDataPayload, getFirstSucceededRemoteData } from '../operators';
|
||||
import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||
import { SearchConfig } from './search-filters/search-config.model';
|
||||
import { SearchConfig, SortConfig } from './search-filters/search-config.model';
|
||||
import { SearchService } from './search.service';
|
||||
import { of } from 'rxjs';
|
||||
import { PaginationService } from '../../pagination/pagination.service';
|
||||
|
||||
/**
|
||||
@@ -35,7 +30,21 @@ import { PaginationService } from '../../pagination/pagination.service';
|
||||
@Injectable()
|
||||
export class SearchConfigurationService implements OnDestroy {
|
||||
|
||||
/**
|
||||
* Default pagination id
|
||||
*/
|
||||
public paginationID = 'spc';
|
||||
|
||||
/**
|
||||
* Emits the current search options
|
||||
*/
|
||||
public searchOptions: BehaviorSubject<SearchOptions>;
|
||||
|
||||
/**
|
||||
* Emits the current search options including pagination and sort
|
||||
*/
|
||||
public paginatedSearchOptions: BehaviorSubject<PaginatedSearchOptions>;
|
||||
|
||||
/**
|
||||
* Default pagination settings
|
||||
*/
|
||||
@@ -45,16 +54,6 @@ export class SearchConfigurationService implements OnDestroy {
|
||||
currentPage: 1
|
||||
});
|
||||
|
||||
/**
|
||||
* Default sort settings
|
||||
*/
|
||||
protected defaultSort = new SortOptions('score', SortDirection.DESC);
|
||||
|
||||
/**
|
||||
* Default configuration parameter setting
|
||||
*/
|
||||
protected defaultConfiguration;
|
||||
|
||||
/**
|
||||
* Default scope setting
|
||||
*/
|
||||
@@ -71,23 +70,14 @@ export class SearchConfigurationService implements OnDestroy {
|
||||
protected _defaults: Observable<RemoteData<PaginatedSearchOptions>>;
|
||||
|
||||
/**
|
||||
* Emits the current search options
|
||||
* A map of subscriptions to unsubscribe from on destroy
|
||||
*/
|
||||
public searchOptions: BehaviorSubject<SearchOptions>;
|
||||
|
||||
/**
|
||||
* Emits the current search options including pagination and sort
|
||||
*/
|
||||
public paginatedSearchOptions: BehaviorSubject<PaginatedSearchOptions>;
|
||||
|
||||
/**
|
||||
* List of subscriptions to unsubscribe from on destroy
|
||||
*/
|
||||
protected subs: Subscription[] = [];
|
||||
protected subs: Map<string, Subscription[]> = new Map<string, Subscription[]>(null);
|
||||
|
||||
/**
|
||||
* Initialize the search options
|
||||
* @param {RouteService} routeService
|
||||
* @param {PaginationService} paginationService
|
||||
* @param {ActivatedRoute} route
|
||||
*/
|
||||
constructor(protected routeService: RouteService,
|
||||
@@ -98,29 +88,28 @@ export class SearchConfigurationService implements OnDestroy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the search options
|
||||
* Default values for the Search Options
|
||||
*/
|
||||
protected initDefaults() {
|
||||
this.defaults
|
||||
.pipe(getFirstSucceededRemoteData())
|
||||
.subscribe((defRD: RemoteData<PaginatedSearchOptions>) => {
|
||||
const defs = defRD.payload;
|
||||
this.paginatedSearchOptions = new BehaviorSubject<PaginatedSearchOptions>(defs);
|
||||
this.searchOptions = new BehaviorSubject<SearchOptions>(defs);
|
||||
this.subs.push(this.subscribeToSearchOptions(defs));
|
||||
this.subs.push(this.subscribeToPaginatedSearchOptions(defs.pagination.id, defs));
|
||||
}
|
||||
);
|
||||
get defaults(): Observable<RemoteData<PaginatedSearchOptions>> {
|
||||
if (hasNoValue(this._defaults)) {
|
||||
const options = new PaginatedSearchOptions({
|
||||
pagination: this.defaultPagination,
|
||||
scope: this.defaultScope,
|
||||
query: this.defaultQuery
|
||||
});
|
||||
this._defaults = createSuccessfulRemoteDataObject$(options, new Date().getTime());
|
||||
}
|
||||
return this._defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Observable<string>} Emits the current configuration string
|
||||
*/
|
||||
getCurrentConfiguration(defaultConfiguration: string) {
|
||||
return observableCombineLatest(
|
||||
return observableCombineLatest([
|
||||
this.routeService.getQueryParameterValue('configuration').pipe(startWith(undefined)),
|
||||
this.routeService.getRouteParameterValue('configuration').pipe(startWith(undefined))
|
||||
).pipe(
|
||||
]).pipe(
|
||||
map(([queryConfig, routeConfig]) => {
|
||||
return queryConfig || routeConfig || defaultConfiguration;
|
||||
})
|
||||
@@ -208,59 +197,82 @@ export class SearchConfigurationService implements OnDestroy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an observable of SearchConfig every time the configuration$ stream emits.
|
||||
* @param configuration$
|
||||
* @param service
|
||||
* Creates an observable of SearchConfig every time the configuration stream emits.
|
||||
* @param configuration The search configuration
|
||||
* @param service The search service to use
|
||||
* @param scope The search scope if exists
|
||||
*/
|
||||
getConfigurationSearchConfigObservable(configuration$: Observable<string>, service: SearchService): Observable<SearchConfig> {
|
||||
return configuration$.pipe(
|
||||
distinctUntilChanged(),
|
||||
switchMap((configuration) => service.getSearchConfigurationFor(null, configuration)),
|
||||
getAllSucceededRemoteDataPayload());
|
||||
getConfigurationSearchConfig(configuration: string, service: SearchService, scope?: string): Observable<SearchConfig> {
|
||||
return service.getSearchConfigurationFor(scope, configuration).pipe(
|
||||
getAllSucceededRemoteDataPayload()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Every time searchConfig change (after a configuration change) it update the navigation with the default sort option
|
||||
* and emit the new paginateSearchOptions value.
|
||||
* @param configuration$
|
||||
* @param service
|
||||
* Return the SortOptions list available for the given SearchConfig
|
||||
* @param searchConfig The SearchConfig object
|
||||
*/
|
||||
initializeSortOptionsFromConfiguration(searchConfig$: Observable<SearchConfig>) {
|
||||
const subscription = searchConfig$.pipe(switchMap((searchConfig) => combineLatest([
|
||||
of(searchConfig),
|
||||
this.paginatedSearchOptions.pipe(take(1))
|
||||
]))).subscribe(([searchConfig, searchOptions]) => {
|
||||
const field = searchConfig.sortOptions[0].name;
|
||||
const direction = searchConfig.sortOptions[0].sortOrder.toLowerCase() === SortDirection.ASC.toLowerCase() ? SortDirection.ASC : SortDirection.DESC;
|
||||
const updateValue = Object.assign(new PaginatedSearchOptions({}), searchOptions, {
|
||||
sort: new SortOptions(field, direction)
|
||||
});
|
||||
this.paginationService.updateRoute(this.paginationID,
|
||||
{
|
||||
sortDirection: updateValue.sort.direction,
|
||||
sortField: updateValue.sort.field,
|
||||
});
|
||||
this.paginatedSearchOptions.next(updateValue);
|
||||
});
|
||||
this.subs.push(subscription);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an observable of available SortOptions[] every time the searchConfig$ stream emits.
|
||||
* @param searchConfig$
|
||||
* @param service
|
||||
*/
|
||||
getConfigurationSortOptionsObservable(searchConfig$: Observable<SearchConfig>): Observable<SortOptions[]> {
|
||||
return searchConfig$.pipe(map((searchConfig) => {
|
||||
const sortOptions = [];
|
||||
searchConfig.sortOptions.forEach(sortOption => {
|
||||
sortOptions.push(new SortOptions(sortOption.name, SortDirection.ASC));
|
||||
sortOptions.push(new SortOptions(sortOption.name, SortDirection.DESC));
|
||||
});
|
||||
return sortOptions;
|
||||
getConfigurationSortOptions(searchConfig: SearchConfig): SortOptions[] {
|
||||
return searchConfig.sortOptions.map((entry: SortConfig) => ({
|
||||
field: entry.name,
|
||||
direction: entry.sortOrder.toLowerCase() === SortDirection.ASC.toLowerCase() ? SortDirection.ASC : SortDirection.DESC
|
||||
}));
|
||||
}
|
||||
|
||||
setPaginationId(paginationId): void {
|
||||
if (isNotEmpty(paginationId)) {
|
||||
const currentValue: PaginatedSearchOptions = this.paginatedSearchOptions.getValue();
|
||||
const updatedValue: PaginatedSearchOptions = Object.assign(new PaginatedSearchOptions({}), currentValue, {
|
||||
pagination: Object.assign({}, currentValue.pagination, {
|
||||
id: paginationId
|
||||
})
|
||||
});
|
||||
// unsubscribe from subscription related to old pagination id
|
||||
this.unsubscribeFromSearchOptions(this.paginationID);
|
||||
|
||||
// change to the new pagination id
|
||||
this.paginationID = paginationId;
|
||||
this.paginatedSearchOptions.next(updatedValue);
|
||||
this.setSearchSubscription(this.paginationID, this.paginatedSearchOptions.value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure to unsubscribe from all existing subscription to prevent memory leaks
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.subs
|
||||
.forEach((subs: Subscription[]) => subs
|
||||
.filter((sub) => hasValue(sub))
|
||||
.forEach((sub) => sub.unsubscribe())
|
||||
);
|
||||
|
||||
this.subs = new Map<string, Subscription[]>(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the search options
|
||||
*/
|
||||
protected initDefaults() {
|
||||
this.defaults
|
||||
.pipe(getFirstSucceededRemoteData())
|
||||
.subscribe((defRD: RemoteData<PaginatedSearchOptions>) => {
|
||||
const defs = defRD.payload;
|
||||
this.paginatedSearchOptions = new BehaviorSubject<PaginatedSearchOptions>(defs);
|
||||
this.searchOptions = new BehaviorSubject<SearchOptions>(defs);
|
||||
this.setSearchSubscription(this.paginationID, defs);
|
||||
});
|
||||
}
|
||||
|
||||
private setSearchSubscription(paginationID: string, defaults: PaginatedSearchOptions) {
|
||||
this.unsubscribeFromSearchOptions(paginationID);
|
||||
const subs = [
|
||||
this.subscribeToSearchOptions(defaults),
|
||||
this.subscribeToPaginatedSearchOptions(paginationID || defaults.pagination.id, defaults)
|
||||
];
|
||||
this.subs.set(this.paginationID, subs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a subscription to all necessary parameters to make sure the searchOptions emits a new value every time they update
|
||||
* @param {SearchOptions} defaults Default values for when no parameters are available
|
||||
@@ -283,14 +295,15 @@ export class SearchConfigurationService implements OnDestroy {
|
||||
|
||||
/**
|
||||
* Sets up a subscription to all necessary parameters to make sure the paginatedSearchOptions emits a new value every time they update
|
||||
* @param {string} paginationId The pagination ID
|
||||
* @param {PaginatedSearchOptions} defaults Default values for when no parameters are available
|
||||
* @returns {Subscription} The subscription to unsubscribe from
|
||||
*/
|
||||
private subscribeToPaginatedSearchOptions(paginationId: string, defaults: PaginatedSearchOptions): Subscription {
|
||||
return observableMerge(
|
||||
this.getConfigurationPart(defaults.configuration),
|
||||
this.getPaginationPart(paginationId, defaults.pagination),
|
||||
this.getSortPart(paginationId, defaults.sort),
|
||||
this.getConfigurationPart(defaults.configuration),
|
||||
this.getScopePart(defaults.scope),
|
||||
this.getQueryPart(defaults.query),
|
||||
this.getDSOTypePart(),
|
||||
@@ -304,30 +317,16 @@ export class SearchConfigurationService implements OnDestroy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Default values for the Search Options
|
||||
* Unsubscribe from all subscriptions related to the given paginationID
|
||||
* @param paginationId The pagination id
|
||||
*/
|
||||
get defaults(): Observable<RemoteData<PaginatedSearchOptions>> {
|
||||
if (hasNoValue(this._defaults)) {
|
||||
const options = new PaginatedSearchOptions({
|
||||
pagination: this.defaultPagination,
|
||||
configuration: this.defaultConfiguration,
|
||||
sort: this.defaultSort,
|
||||
scope: this.defaultScope,
|
||||
query: this.defaultQuery
|
||||
});
|
||||
this._defaults = createSuccessfulRemoteDataObject$(options, new Date().getTime());
|
||||
private unsubscribeFromSearchOptions(paginationId: string): void {
|
||||
if (this.subs.has(this.paginationID)) {
|
||||
this.subs.get(this.paginationID)
|
||||
.filter((sub) => hasValue(sub))
|
||||
.forEach((sub) => sub.unsubscribe());
|
||||
this.subs.delete(paginationId);
|
||||
}
|
||||
return this._defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure to unsubscribe from all existing subscription to prevent memory leaks
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.subs.forEach((sub) => {
|
||||
sub.unsubscribe();
|
||||
});
|
||||
this.subs = [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -10,8 +10,8 @@ import {
|
||||
SearchFilterToggleAction
|
||||
} from '../../../shared/search/search-filters/search-filter/search-filter.actions';
|
||||
import { SearchFiltersState } from '../../../shared/search/search-filters/search-filter/search-filter.reducer';
|
||||
import { SearchFilterConfig } from '../../../shared/search/search-filter-config.model';
|
||||
import { FilterType } from '../../../shared/search/filter-type.model';
|
||||
import { SearchFilterConfig } from '../../../shared/search/models/search-filter-config.model';
|
||||
import { FilterType } from '../../../shared/search/models/filter-type.model';
|
||||
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
||||
|
@@ -16,7 +16,7 @@ import {
|
||||
SearchFilterToggleAction
|
||||
} from '../../../shared/search/search-filters/search-filter/search-filter.actions';
|
||||
import { hasValue, isNotEmpty, } from '../../../shared/empty.util';
|
||||
import { SearchFilterConfig } from '../../../shared/search/search-filter-config.model';
|
||||
import { SearchFilterConfig } from '../../../shared/search/models/search-filter-config.model';
|
||||
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
||||
import { RouteService } from '../../services/route.service';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
|
@@ -29,7 +29,7 @@ export class SearchConfig implements CacheableObject {
|
||||
* The configured sort options.
|
||||
*/
|
||||
@autoserialize
|
||||
sortOptions: SortOption[];
|
||||
sortOptions: SortConfig[];
|
||||
|
||||
/**
|
||||
* The object type.
|
||||
@@ -63,7 +63,7 @@ export interface FilterConfig {
|
||||
/**
|
||||
* Interface to model sort option's configuration.
|
||||
*/
|
||||
export interface SortOption {
|
||||
export interface SortConfig {
|
||||
name: string;
|
||||
sortOrder: string;
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||
import { RouterStub } from '../../../shared/testing/router.stub';
|
||||
import { HALEndpointService } from '../hal-endpoint.service';
|
||||
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { RequestEntry } from '../../data/request.reducer';
|
||||
import { getMockRequestService } from '../../../shared/mocks/request.service.mock';
|
||||
@@ -21,11 +21,8 @@ import { RouteService } from '../../services/route.service';
|
||||
import { routeServiceStub } from '../../../shared/testing/route-service.stub';
|
||||
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||
import { SearchObjects } from '../../../shared/search/search-objects.model';
|
||||
import { SearchObjects } from '../../../shared/search/models/search-objects.model';
|
||||
import { PaginationService } from '../../pagination/pagination.service';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
||||
import { FindListOptions } from '../../data/request.models';
|
||||
import { SearchConfigurationService } from './search-configuration.service';
|
||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||
|
||||
|
@@ -14,24 +14,24 @@ import { GenericConstructor } from '../generic-constructor';
|
||||
import { HALEndpointService } from '../hal-endpoint.service';
|
||||
import { URLCombiner } from '../../url-combiner/url-combiner';
|
||||
import { hasValue, hasValueOperator, isNotEmpty } from '../../../shared/empty.util';
|
||||
import { SearchOptions } from '../../../shared/search/search-options.model';
|
||||
import { SearchFilterConfig } from '../../../shared/search/search-filter-config.model';
|
||||
import { SearchOptions } from '../../../shared/search/models/search-options.model';
|
||||
import { SearchFilterConfig } from '../../../shared/search/models/search-filter-config.model';
|
||||
import { SearchResponseParsingService } from '../../data/search-response-parsing.service';
|
||||
import { SearchObjects } from '../../../shared/search/search-objects.model';
|
||||
import { SearchObjects } from '../../../shared/search/models/search-objects.model';
|
||||
import { FacetValueResponseParsingService } from '../../data/facet-value-response-parsing.service';
|
||||
import { FacetConfigResponseParsingService } from '../../data/facet-config-response-parsing.service';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||
import { CommunityDataService } from '../../data/community-data.service';
|
||||
import { ViewMode } from '../view-mode.model';
|
||||
import { DSpaceObjectDataService } from '../../data/dspace-object-data.service';
|
||||
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
||||
import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../operators';
|
||||
import { RouteService } from '../../services/route.service';
|
||||
import { SearchResult } from '../../../shared/search/search-result.model';
|
||||
import { SearchResult } from '../../../shared/search/models/search-result.model';
|
||||
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
|
||||
import { getSearchResultFor } from '../../../shared/search/search-result-element-decorator';
|
||||
import { FacetConfigResponse } from '../../../shared/search/facet-config-response.model';
|
||||
import { FacetValues } from '../../../shared/search/facet-values.model';
|
||||
import { FacetConfigResponse } from '../../../shared/search/models/facet-config-response.model';
|
||||
import { FacetValues } from '../../../shared/search/models/facet-values.model';
|
||||
import { SearchConfig } from './search-filters/search-config.model';
|
||||
import { PaginationService } from '../../pagination/pagination.service';
|
||||
import { SearchConfigurationService } from './search-configuration.service';
|
||||
@@ -407,6 +407,7 @@ export class SearchService implements OnDestroy {
|
||||
/**
|
||||
* Changes the current view mode in the current URL
|
||||
* @param {ViewMode} viewMode Mode to switch to
|
||||
* @param {string[]} searchLinkParts
|
||||
*/
|
||||
setViewMode(viewMode: ViewMode, searchLinkParts?: string[]) {
|
||||
this.paginationService.getCurrentPagination(this.searchConfigurationService.paginationID, new PaginationComponentOptions()).pipe(take(1))
|
||||
|
@@ -47,6 +47,12 @@ export class Version extends DSpaceObject {
|
||||
@autoserialize
|
||||
summary: string;
|
||||
|
||||
/**
|
||||
* The name of the submitter of this version
|
||||
*/
|
||||
@autoserialize
|
||||
submitterName: string;
|
||||
|
||||
/**
|
||||
* The Date this version was created
|
||||
*/
|
||||
|
@@ -3,8 +3,8 @@
|
||||
*/
|
||||
|
||||
export enum ViewMode {
|
||||
ListElement = 'listElement',
|
||||
GridElement = 'gridElement',
|
||||
DetailedListElement = 'detailedListElement',
|
||||
StandalonePage = 'standalonePage',
|
||||
ListElement = 'list',
|
||||
GridElement = 'grid',
|
||||
DetailedListElement = 'detailed',
|
||||
StandalonePage = 'standalone',
|
||||
}
|
||||
|
21
src/app/correlation-id/correlation-id.actions.ts
Normal file
21
src/app/correlation-id/correlation-id.actions.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { type } from '../shared/ngrx/type';
|
||||
import { Action } from '@ngrx/store';
|
||||
|
||||
export const CorrelationIDActionTypes = {
|
||||
SET: type('dspace/core/correlationId/SET')
|
||||
};
|
||||
|
||||
/**
|
||||
* Action for setting a new correlation ID
|
||||
*/
|
||||
export class SetCorrelationIdAction implements Action {
|
||||
type = CorrelationIDActionTypes.SET;
|
||||
|
||||
constructor(public payload: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type alias for all correlation ID actions
|
||||
*/
|
||||
export type CorrelationIdAction = SetCorrelationIdAction;
|
23
src/app/correlation-id/correlation-id.reducer.spec.ts
Normal file
23
src/app/correlation-id/correlation-id.reducer.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { correlationIdReducer } from './correlation-id.reducer';
|
||||
import { SetCorrelationIdAction } from './correlation-id.actions';
|
||||
|
||||
describe('correlationIdReducer', () => {
|
||||
it('should set the correlatinId with SET action', () => {
|
||||
const initialState = null;
|
||||
const currentState = correlationIdReducer(initialState, new SetCorrelationIdAction('new ID'));
|
||||
|
||||
expect(currentState).toBe('new ID');
|
||||
});
|
||||
|
||||
it('should leave correlatinId unchanged otherwise', () => {
|
||||
const initialState = null;
|
||||
|
||||
let currentState = correlationIdReducer(initialState, { type: 'unknown' } as any);
|
||||
expect(currentState).toBe(null);
|
||||
|
||||
currentState = correlationIdReducer(currentState, new SetCorrelationIdAction('new ID'));
|
||||
currentState = correlationIdReducer(currentState, { type: 'unknown' } as any);
|
||||
|
||||
expect(currentState).toBe('new ID');
|
||||
});
|
||||
});
|
27
src/app/correlation-id/correlation-id.reducer.ts
Normal file
27
src/app/correlation-id/correlation-id.reducer.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
CorrelationIdAction,
|
||||
CorrelationIDActionTypes,
|
||||
SetCorrelationIdAction
|
||||
} from './correlation-id.actions';
|
||||
import { AppState } from '../app.reducer';
|
||||
|
||||
const initialState = null;
|
||||
|
||||
export const correlationIdSelector = (state: AppState) => state.correlationId;
|
||||
|
||||
/**
|
||||
* Reducer that handles actions to update the correlation ID
|
||||
* @param {string} state the previous correlation ID (null if unset)
|
||||
* @param {CorrelationIdAction} action the action to perform
|
||||
* @return {string} the new correlation ID
|
||||
*/
|
||||
export const correlationIdReducer = (state = initialState, action: CorrelationIdAction): string => {
|
||||
switch (action.type) {
|
||||
case CorrelationIDActionTypes.SET: {
|
||||
return (action as SetCorrelationIdAction).payload;
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
83
src/app/correlation-id/correlation-id.service.spec.ts
Normal file
83
src/app/correlation-id/correlation-id.service.spec.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { CorrelationIdService } from './correlation-id.service';
|
||||
import { CookieServiceMock } from '../shared/mocks/cookie.service.mock';
|
||||
import { UUIDService } from '../core/shared/uuid.service';
|
||||
import { MockStore, provideMockStore } from '@ngrx/store/testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { Store, StoreModule } from '@ngrx/store';
|
||||
import { appReducers, AppState, storeModuleConfig } from '../app.reducer';
|
||||
import { SetCorrelationIdAction } from './correlation-id.actions';
|
||||
|
||||
describe('CorrelationIdService', () => {
|
||||
let service: CorrelationIdService;
|
||||
|
||||
let cookieService;
|
||||
let uuidService;
|
||||
let store;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
StoreModule.forRoot(appReducers, storeModuleConfig),
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cookieService = new CookieServiceMock();
|
||||
uuidService = new UUIDService();
|
||||
store = TestBed.inject(Store) as MockStore<AppState>;
|
||||
service = new CorrelationIdService(cookieService, uuidService, store);
|
||||
});
|
||||
|
||||
describe('getCorrelationId', () => {
|
||||
it('should get from from store', () => {
|
||||
expect(service.getCorrelationId()).toBe(null);
|
||||
store.dispatch(new SetCorrelationIdAction('some value'));
|
||||
expect(service.getCorrelationId()).toBe('some value');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('initCorrelationId', () => {
|
||||
const cookieCID = 'cookie CID';
|
||||
const storeCID = 'store CID';
|
||||
|
||||
it('should set cookie and store values to a newly generated value if neither ex', () => {
|
||||
service.initCorrelationId();
|
||||
|
||||
expect(cookieService.get('CORRELATION-ID')).toBeTruthy();
|
||||
expect(service.getCorrelationId()).toBeTruthy();
|
||||
expect(cookieService.get('CORRELATION-ID')).toEqual(service.getCorrelationId());
|
||||
});
|
||||
|
||||
it('should set store value to cookie value if present', () => {
|
||||
expect(service.getCorrelationId()).toBe(null);
|
||||
|
||||
cookieService.set('CORRELATION-ID', cookieCID);
|
||||
|
||||
service.initCorrelationId();
|
||||
|
||||
expect(cookieService.get('CORRELATION-ID')).toBe(cookieCID);
|
||||
expect(service.getCorrelationId()).toBe(cookieCID);
|
||||
});
|
||||
|
||||
it('should set cookie value to store value if present', () => {
|
||||
store.dispatch(new SetCorrelationIdAction(storeCID));
|
||||
|
||||
service.initCorrelationId();
|
||||
|
||||
expect(cookieService.get('CORRELATION-ID')).toBe(storeCID);
|
||||
expect(service.getCorrelationId()).toBe(storeCID);
|
||||
});
|
||||
|
||||
it('should set store value to cookie value if both are present', () => {
|
||||
cookieService.set('CORRELATION-ID', cookieCID);
|
||||
store.dispatch(new SetCorrelationIdAction(storeCID));
|
||||
|
||||
service.initCorrelationId();
|
||||
|
||||
expect(cookieService.get('CORRELATION-ID')).toBe(cookieCID);
|
||||
expect(service.getCorrelationId()).toBe(cookieCID);
|
||||
});
|
||||
});
|
||||
});
|
64
src/app/correlation-id/correlation-id.service.ts
Normal file
64
src/app/correlation-id/correlation-id.service.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { CookieService } from '../core/services/cookie.service';
|
||||
import { UUIDService } from '../core/shared/uuid.service';
|
||||
import { Store, select } from '@ngrx/store';
|
||||
import { AppState } from '../app.reducer';
|
||||
import { isEmpty } from '../shared/empty.util';
|
||||
import { correlationIdSelector } from './correlation-id.reducer';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { SetCorrelationIdAction } from './correlation-id.actions';
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Service to manage the correlation id, an id used to give context to server side logs
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CorrelationIdService {
|
||||
|
||||
constructor(
|
||||
protected cookieService: CookieService,
|
||||
protected uuidService: UUIDService,
|
||||
protected store: Store<AppState>,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the correlation id based on the cookie or the ngrx store
|
||||
*/
|
||||
initCorrelationId(): void {
|
||||
// first see of there's a cookie with a correlation-id
|
||||
let correlationId = this.cookieService.get('CORRELATION-ID');
|
||||
|
||||
// if there isn't see if there's an ID in the store
|
||||
if (isEmpty(correlationId)) {
|
||||
correlationId = this.getCorrelationId();
|
||||
}
|
||||
|
||||
// if no id was found, create a new id
|
||||
if (isEmpty(correlationId)) {
|
||||
correlationId = this.uuidService.generate();
|
||||
}
|
||||
|
||||
// Store the correct id both in the store and as a cookie to ensure they're in sync
|
||||
this.store.dispatch(new SetCorrelationIdAction(correlationId));
|
||||
this.cookieService.set('CORRELATION-ID', correlationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correlation id from the store
|
||||
*/
|
||||
getCorrelationId(): string {
|
||||
let correlationId;
|
||||
|
||||
this.store.pipe(
|
||||
select(correlationIdSelector),
|
||||
take(1)
|
||||
).subscribe((storeId: string) => {
|
||||
// we can do this because ngrx selects are synchronous
|
||||
correlationId = storeId;
|
||||
});
|
||||
|
||||
return correlationId;
|
||||
}
|
||||
}
|
@@ -19,6 +19,7 @@ import { JournalVolumeSearchResultGridElementComponent } from './item-grid-eleme
|
||||
import { JournalVolumeSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal-volume/journal-volume-sidebar-search-list-element.component';
|
||||
import { JournalIssueSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal-issue/journal-issue-sidebar-search-list-element.component';
|
||||
import { JournalSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal/journal-sidebar-search-list-element.component';
|
||||
import { ItemSharedModule } from '../../item-page/item-shared.module';
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
// put only entry components that use custom decorator
|
||||
@@ -45,6 +46,7 @@ const ENTRY_COMPONENTS = [
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
ItemSharedModule,
|
||||
SharedModule
|
||||
],
|
||||
declarations: [
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { OrgUnitComponent } from './item-pages/org-unit/org-unit.component';
|
||||
import { PersonComponent } from './item-pages/person/person.component';
|
||||
@@ -27,6 +28,7 @@ import { ExternalSourceEntryListSubmissionElementComponent } from './submission/
|
||||
import { OrgUnitSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/org-unit/org-unit-sidebar-search-list-element.component';
|
||||
import { PersonSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/person/person-sidebar-search-list-element.component';
|
||||
import { ProjectSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/project/project-sidebar-search-list-element.component';
|
||||
import { ItemSharedModule } from '../../item-page/item-shared.module';
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
// put only entry components that use custom decorator
|
||||
@@ -65,7 +67,9 @@ const COMPONENTS = [
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule
|
||||
ItemSharedModule,
|
||||
SharedModule,
|
||||
NgbTooltipModule
|
||||
],
|
||||
declarations: [
|
||||
...COMPONENTS,
|
||||
@@ -79,7 +83,7 @@ export class ResearchEntitiesModule {
|
||||
static withEntryComponents() {
|
||||
return {
|
||||
ngModule: ResearchEntitiesModule,
|
||||
providers: ENTRY_COMPONENTS.map((component) => ({provide: component}))
|
||||
providers: ENTRY_COMPONENTS.map((component) => ({ provide: component }))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@
|
||||
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}">
|
||||
<div class="dropdown-list">
|
||||
<div *ngFor="let suggestionOption of suggestions">
|
||||
<a href="#" class="d-block dropdown-item" (click)="onClickSuggestion(suggestionOption)" #suggestion>
|
||||
<a href="javascript:void(0);" class="d-block dropdown-item" (click)="onClickSuggestion(suggestionOption)" #suggestion>
|
||||
<span [innerHTML]="suggestionOption"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
@@ -16,7 +16,7 @@
|
||||
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}">
|
||||
<div class="dropdown-list">
|
||||
<div *ngFor="let suggestionOption of suggestions">
|
||||
<a href="#" class="d-block dropdown-item" (click)="onClickSuggestion(suggestionOption)" #suggestion>
|
||||
<a href="javascript:void(0);" class="d-block dropdown-item" (click)="onClickSuggestion(suggestionOption)" #suggestion>
|
||||
<span [innerHTML]="suggestionOption"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
@@ -64,7 +64,7 @@
|
||||
</p>
|
||||
<ul class="footer-info list-unstyled small d-flex justify-content-center mb-0">
|
||||
<li>
|
||||
<a class="text-white" href="#"
|
||||
<a class="text-white" href="javascript:void(0);"
|
||||
(click)="showCookieSettings()">{{ 'footer.link.cookies' | translate}}</a>
|
||||
</li>
|
||||
<li>
|
||||
|
@@ -1,5 +1,8 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { EditItemPageRoutingModule } from './edit-item-page.routing.module';
|
||||
import { EditItemPageComponent } from './edit-item-page.component';
|
||||
@@ -31,6 +34,8 @@ import { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.co
|
||||
import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component';
|
||||
import { ItemAuthorizationsComponent } from './item-authorizations/item-authorizations.component';
|
||||
import { ObjectValuesPipe } from '../../shared/utils/object-values-pipe';
|
||||
import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-policies.module';
|
||||
|
||||
|
||||
/**
|
||||
* Module that contains all components related to the Edit Item page administrator functionality
|
||||
@@ -39,9 +44,11 @@ import { ObjectValuesPipe } from '../../shared/utils/object-values-pipe';
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
NgbTooltipModule,
|
||||
EditItemPageRoutingModule,
|
||||
SearchPageModule,
|
||||
DragDropModule
|
||||
DragDropModule,
|
||||
ResourcePoliciesModule
|
||||
],
|
||||
declarations: [
|
||||
EditItemPageComponent,
|
||||
|
@@ -15,14 +15,11 @@ import { getFirstSucceededRemoteData, getRemoteDataPayload } from '../../../core
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||
import { Bundle } from '../../../core/shared/bundle.model';
|
||||
import {
|
||||
FieldUpdate,
|
||||
FieldUpdates
|
||||
} from '../../../core/data/object-updates/object-updates.reducer';
|
||||
import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
|
||||
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||
import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions';
|
||||
import { BundleDataService } from '../../../core/data/bundle-data.service';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||
import { ResponsiveColumnSizes } from '../../../shared/responsive-table-sizes/responsive-column-sizes';
|
||||
import { ResponsiveTableSizes } from '../../../shared/responsive-table-sizes/responsive-table-sizes';
|
||||
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||
|
@@ -5,7 +5,7 @@ import { Bitstream } from '../../../../../core/shared/bitstream.model';
|
||||
import { ObjectUpdatesService } from '../../../../../core/data/object-updates/object-updates.service';
|
||||
import { BundleDataService } from '../../../../../core/data/bundle-data.service';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { PaginatedSearchOptions } from '../../../../../shared/search/paginated-search-options.model';
|
||||
import { PaginatedSearchOptions } from '../../../../../shared/search/models/paginated-search-options.model';
|
||||
import { ResponsiveTableSizes } from '../../../../../shared/responsive-table-sizes/responsive-table-sizes';
|
||||
import { followLink } from '../../../../../shared/utils/follow-link-config.model';
|
||||
import { ObjectValuesPipe } from '../../../../../shared/utils/object-values-pipe';
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { EventEmitter } from '@angular/core';
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
@@ -25,7 +25,7 @@ import { ObjectSelectService } from '../../../shared/object-select/object-select
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
import { PaginationComponent } from '../../../shared/pagination/pagination.component';
|
||||
import { SearchFormComponent } from '../../../shared/search-form/search-form.component';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||
import { HostWindowServiceStub } from '../../../shared/testing/host-window-service.stub';
|
||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
||||
import { ObjectSelectServiceStub } from '../../../shared/testing/object-select-service.stub';
|
||||
|
@@ -9,11 +9,12 @@ import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import {
|
||||
getAllSucceededRemoteData,
|
||||
getFirstCompletedRemoteData,
|
||||
getFirstSucceededRemoteData,
|
||||
getFirstSucceededRemoteDataPayload,
|
||||
getRemoteDataPayload,
|
||||
getFirstSucceededRemoteData,
|
||||
toDSpaceObjectListRD,
|
||||
getAllSucceededRemoteData, getFirstCompletedRemoteData
|
||||
toDSpaceObjectListRD
|
||||
} from '../../../core/shared/operators';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { filter, map, startWith, switchMap, take } from 'rxjs/operators';
|
||||
@@ -22,7 +23,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
||||
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
||||
import { SearchService } from '../../../core/shared/search/search.service';
|
||||
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||
|
@@ -3,7 +3,13 @@ import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { LinkService } from '../../../../core/cache/builders/link.service';
|
||||
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
|
||||
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
||||
import { combineLatest as observableCombineLatest, from as observableFrom, BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest as observableCombineLatest,
|
||||
from as observableFrom,
|
||||
Observable,
|
||||
Subscription
|
||||
} from 'rxjs';
|
||||
import {
|
||||
FieldUpdate,
|
||||
FieldUpdates,
|
||||
@@ -25,7 +31,7 @@ import { ItemType } from '../../../../core/shared/item-relationships/item-type.m
|
||||
import { DsDynamicLookupRelationModalComponent } from '../../../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component';
|
||||
import { RelationshipOptions } from '../../../../shared/form/builder/models/relationship-options.model';
|
||||
import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service';
|
||||
import { SearchResult } from '../../../../shared/search/search-result.model';
|
||||
import { SearchResult } from '../../../../shared/search/models/search-result.model';
|
||||
import { followLink } from '../../../../shared/utils/follow-link-config.model';
|
||||
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
|
@@ -14,7 +14,7 @@
|
||||
(click)="$event.preventDefault(); handleLoadMore()"
|
||||
class="load-more-btn btn btn-sm btn-outline-secondary"
|
||||
role="button"
|
||||
href="#"
|
||||
href="javascript:void(0);"
|
||||
>
|
||||
{{'item.page.collections.load-more' | translate}}
|
||||
</a>
|
||||
|
36
src/app/item-page/item-shared.module.ts
Normal file
36
src/app/item-page/item-shared.module.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { RelatedEntitiesSearchComponent } from './simple/related-entities/related-entities-search/related-entities-search.component';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SearchModule } from '../shared/search/search.module';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DYNAMIC_FORM_CONTROL_MAP_FN } from '@ng-dynamic-forms/core';
|
||||
import { dsDynamicFormControlMapFn } from '../shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component';
|
||||
import { TabbedRelatedEntitiesSearchComponent } from './simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component';
|
||||
|
||||
const COMPONENTS = [
|
||||
RelatedEntitiesSearchComponent,
|
||||
TabbedRelatedEntitiesSearchComponent
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
...COMPONENTS
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SearchModule,
|
||||
SharedModule,
|
||||
TranslateModule
|
||||
],
|
||||
exports: [
|
||||
...COMPONENTS
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: DYNAMIC_FORM_CONTROL_MAP_FN,
|
||||
useValue: dsDynamicFormControlMapFn
|
||||
}
|
||||
]
|
||||
})
|
||||
export class ItemSharedModule { }
|
@@ -36,9 +36,10 @@ export class ItemComponent implements OnInit {
|
||||
*/
|
||||
iiifQuery$: Observable<string>;
|
||||
|
||||
mediaViewer = environment.mediaViewer;
|
||||
mediaViewer;
|
||||
|
||||
constructor(protected routeService: RouteService) {
|
||||
this.mediaViewer = environment.mediaViewer;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user