mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-14 13:33:03 +00:00
Merge remote-tracking branch 'origin/main' into CST-4633-search-refactoring
This commit is contained in:
@@ -359,7 +359,7 @@ dspace-angular
|
||||
│ ├── plugins * Folder for Cypress plugins (if any)
|
||||
│ ├── support * Folder for global e2e test actions/commands (run for all tests)
|
||||
│ └── tsconfig.json * TypeScript configuration file for e2e tests
|
||||
├── docker *
|
||||
├── docker * See docker/README.md for details
|
||||
│ ├── cli.assetstore.yml *
|
||||
│ ├── cli.ingest.yml *
|
||||
│ ├── cli.yml *
|
||||
@@ -367,8 +367,6 @@ dspace-angular
|
||||
│ ├── docker-compose-ci.yml *
|
||||
│ ├── docker-compose-rest.yml *
|
||||
│ ├── docker-compose.yml *
|
||||
│ ├── environment.dev.ts *
|
||||
│ ├── local.cfg *
|
||||
│ └── README.md *
|
||||
├── docs * Folder for documentation
|
||||
│ └── Configuration.md * Configuration documentation
|
||||
|
@@ -29,10 +29,6 @@ docker push dspace/dspace-angular:dspace-7_x
|
||||
- Docker compose file that provides a DSpace CLI container to work with a running DSpace REST container.
|
||||
- cli.assetstore.yml
|
||||
- Docker compose file that will download and install data into a DSpace REST assetstore. This script points to a default dataset that will be utilized for CI testing.
|
||||
- environment.dev.ts
|
||||
- Environment file for running DSpace Angular in Docker
|
||||
- local.cfg
|
||||
- Environment file for running the DSpace 7 REST API in Docker.
|
||||
|
||||
|
||||
## To refresh / pull DSpace images from Dockerhub
|
||||
|
@@ -18,10 +18,19 @@ services:
|
||||
dspace-cli:
|
||||
image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}"
|
||||
container_name: dspace-cli
|
||||
#environment:
|
||||
environment:
|
||||
# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
|
||||
# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
|
||||
# __P__ => "." (e.g. dspace__P__dir => dspace.dir)
|
||||
# __D__ => "-" (e.g. google__D__metadata => google-metadata)
|
||||
# dspace.dir
|
||||
dspace__P__dir: /dspace
|
||||
# db.url: Ensure we are using the 'dspacedb' image for our database
|
||||
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
|
||||
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
|
||||
solr__P__server: http://dspacesolr:8983/solr
|
||||
volumes:
|
||||
- "assetstore:/dspace/assetstore"
|
||||
- "./local.cfg:/dspace/config/local.cfg"
|
||||
entrypoint: /dspace/bin/dspace
|
||||
command: help
|
||||
networks:
|
||||
|
@@ -17,6 +17,19 @@ services:
|
||||
# DSpace (backend) webapp container
|
||||
dspace:
|
||||
container_name: dspace
|
||||
environment:
|
||||
# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
|
||||
# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
|
||||
# __P__ => "." (e.g. dspace__P__dir => dspace.dir)
|
||||
# __D__ => "-" (e.g. google__D__metadata => google-metadata)
|
||||
# dspace.dir, dspace.server.url and dspace.ui.url
|
||||
dspace__P__dir: /dspace
|
||||
dspace__P__server__P__url: http://localhost:8080/server
|
||||
dspace__P__ui__P__url: http://localhost:4000
|
||||
# db.url: Ensure we are using the 'dspacedb' image for our database
|
||||
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
|
||||
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
|
||||
solr__P__server: http://dspacesolr:8983/solr
|
||||
depends_on:
|
||||
- dspacedb
|
||||
image: dspace/dspace:dspace-7_x-test
|
||||
@@ -29,7 +42,6 @@ services:
|
||||
tty: true
|
||||
volumes:
|
||||
- assetstore:/dspace/assetstore
|
||||
- "./local.cfg:/dspace/config/local.cfg"
|
||||
# Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below)
|
||||
- solr_configs:/dspace/solr
|
||||
# Ensure that the database is ready BEFORE starting tomcat
|
||||
|
@@ -13,10 +13,32 @@
|
||||
version: '3.7'
|
||||
networks:
|
||||
dspacenet:
|
||||
ipam:
|
||||
config:
|
||||
# Define a custom subnet for our DSpace network, so that we can easily trust requests from host to container.
|
||||
# If you customize this value, be sure to customize the 'proxies.trusted.ipranges' env variable below.
|
||||
- subnet: 172.23.0.0/16
|
||||
services:
|
||||
# DSpace (backend) webapp container
|
||||
dspace:
|
||||
container_name: dspace
|
||||
environment:
|
||||
# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
|
||||
# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
|
||||
# __P__ => "." (e.g. dspace__P__dir => dspace.dir)
|
||||
# __D__ => "-" (e.g. google__D__metadata => google-metadata)
|
||||
# dspace.dir, dspace.server.url, dspace.ui.url and dspace.name
|
||||
dspace__P__dir: /dspace
|
||||
dspace__P__server__P__url: http://localhost:8080/server
|
||||
dspace__P__ui__P__url: http://localhost:4000
|
||||
dspace__P__name: 'DSpace Started with Docker Compose'
|
||||
# db.url: Ensure we are using the 'dspacedb' image for our database
|
||||
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
|
||||
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
|
||||
solr__P__server: http://dspacesolr:8983/solr
|
||||
# proxies.trusted.ipranges: This setting is required for a REST API running in Docker to trust requests
|
||||
# from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above.
|
||||
proxies__P__trusted__P__ipranges: '172.23.0'
|
||||
image: dspace/dspace:dspace-7_x-test
|
||||
depends_on:
|
||||
- dspacedb
|
||||
@@ -29,7 +51,6 @@ services:
|
||||
tty: true
|
||||
volumes:
|
||||
- assetstore:/dspace/assetstore
|
||||
- "./local.cfg:/dspace/config/local.cfg"
|
||||
# Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below)
|
||||
- solr_configs:/dspace/solr
|
||||
# Ensure that the database is ready BEFORE starting tomcat
|
||||
|
@@ -16,10 +16,14 @@ services:
|
||||
dspace-angular:
|
||||
container_name: dspace-angular
|
||||
environment:
|
||||
DSPACE_HOST: dspace-angular
|
||||
DSPACE_NAMESPACE: /
|
||||
DSPACE_PORT: '4000'
|
||||
DSPACE_SSL: "false"
|
||||
DSPACE_UI_SSL: false
|
||||
DSPACE_UI_HOST: dspace-angular
|
||||
DSPACE_UI_PORT: '4000'
|
||||
DSPACE_UI_NAMESPACE: /
|
||||
DSPACE_REST_SSL: false
|
||||
DSPACE_REST_HOST: localhost
|
||||
DSPACE_REST_PORT: 8080
|
||||
DSPACE_REST_NAMESPACE: /server
|
||||
image: dspace/dspace-angular:dspace-7_x
|
||||
build:
|
||||
context: ..
|
||||
@@ -33,5 +37,3 @@ services:
|
||||
target: 9876
|
||||
stdin_open: true
|
||||
tty: true
|
||||
volumes:
|
||||
- ./environment.dev.ts:/app/src/environments/environment.dev.ts
|
||||
|
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
// This file is based on environment.template.ts provided by Angular UI
|
||||
export const environment = {
|
||||
// Default to using the local REST API (running in Docker)
|
||||
rest: {
|
||||
ssl: false,
|
||||
host: 'localhost',
|
||||
port: 8080,
|
||||
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
|
||||
nameSpace: '/server'
|
||||
}
|
||||
};
|
@@ -1,6 +0,0 @@
|
||||
dspace.dir=/dspace
|
||||
db.url=jdbc:postgresql://dspacedb:5432/dspace
|
||||
dspace.server.url=http://localhost:8080/server
|
||||
dspace.ui.url=http://localhost:4000
|
||||
dspace.name=DSpace Started with Docker Compose
|
||||
solr.server=http://dspacesolr:8983/solr
|
@@ -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
|
||||
*/
|
||||
|
@@ -13,6 +13,7 @@ export enum FeatureID {
|
||||
CanManageGroup = 'canManageGroup',
|
||||
IsCollectionAdmin = 'isCollectionAdmin',
|
||||
IsCommunityAdmin = 'isCommunityAdmin',
|
||||
CanChangePassword = 'canChangePassword',
|
||||
CanDownload = 'canDownload',
|
||||
CanRequestACopy = 'canRequestACopy',
|
||||
CanManageVersions = 'canManageVersions',
|
||||
|
@@ -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
|
||||
*/
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<ds-profile-page-metadata-form [user]="user"></ds-profile-page-metadata-form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-4">
|
||||
<div *ngIf="canChangePassword$ | async" class="card mb-4 security-section">
|
||||
<div class="card-header">{{'profile.card.security' | translate}}</div>
|
||||
<div class="card-body">
|
||||
<ds-profile-page-security-form
|
||||
|
@@ -13,10 +13,13 @@ import { NotificationsService } from '../shared/notifications/notifications.serv
|
||||
import { authReducer } from '../core/auth/auth.reducer';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||
import { createPaginatedList } from '../shared/testing/utils.test';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { BehaviorSubject, of as observableOf } from 'rxjs';
|
||||
import { AuthService } from '../core/auth/auth.service';
|
||||
import { RestResponse } from '../core/cache/response.models';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
|
||||
import { getTestScheduler } from 'jasmine-marbles';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
describe('ProfilePageComponent', () => {
|
||||
let component: ProfilePageComponent;
|
||||
@@ -28,10 +31,13 @@ describe('ProfilePageComponent', () => {
|
||||
let epersonService;
|
||||
let notificationsService;
|
||||
|
||||
const canChangePassword = new BehaviorSubject(true);
|
||||
|
||||
function init() {
|
||||
user = Object.assign(new EPerson(), {
|
||||
id: 'userId',
|
||||
groups: createSuccessfulRemoteDataObject$(createPaginatedList([]))
|
||||
groups: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
||||
_links: {self: {href: 'test.com/uuid/1234567654321'}}
|
||||
});
|
||||
initialState = {
|
||||
core: {
|
||||
@@ -74,6 +80,7 @@ describe('ProfilePageComponent', () => {
|
||||
{ provide: EPersonDataService, useValue: epersonService },
|
||||
{ provide: NotificationsService, useValue: notificationsService },
|
||||
{ provide: AuthService, useValue: authService },
|
||||
{ provide: AuthorizationDataService, useValue: jasmine.createSpyObj('authorizationService', { isAuthorized: canChangePassword }) },
|
||||
provideMockStore({ initialState }),
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
@@ -183,7 +190,7 @@ describe('ProfilePageComponent', () => {
|
||||
component.setPasswordValue('testest');
|
||||
component.setInvalid(false);
|
||||
|
||||
operations = [{op: 'add', path: '/password', value: 'testest'}];
|
||||
operations = [{ op: 'add', path: '/password', value: 'testest' }];
|
||||
result = component.updateSecurity();
|
||||
});
|
||||
|
||||
@@ -196,4 +203,36 @@ describe('ProfilePageComponent', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('canChangePassword$', () => {
|
||||
describe('when the user is allowed to change their password', () => {
|
||||
beforeEach(() => {
|
||||
canChangePassword.next(true);
|
||||
});
|
||||
|
||||
it('should contain true', () => {
|
||||
getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: true });
|
||||
});
|
||||
|
||||
it('should show the security section on the page', () => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(By.css('.security-section'))).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the user is not allowed to change their password', () => {
|
||||
beforeEach(() => {
|
||||
canChangePassword.next(false);
|
||||
});
|
||||
|
||||
it('should contain false', () => {
|
||||
getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: false });
|
||||
});
|
||||
|
||||
it('should not show the security section on the page', () => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(By.css('.security-section'))).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -18,6 +18,8 @@ import { hasValue, isNotEmpty } from '../shared/empty.util';
|
||||
import { followLink } from '../shared/utils/follow-link-config.model';
|
||||
import { AuthService } from '../core/auth/auth.service';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-profile-page',
|
||||
@@ -67,11 +69,13 @@ export class ProfilePageComponent implements OnInit {
|
||||
* The authenticated user
|
||||
*/
|
||||
private currentUser: EPerson;
|
||||
canChangePassword$: Observable<boolean>;
|
||||
|
||||
constructor(private authService: AuthService,
|
||||
private notificationsService: NotificationsService,
|
||||
private translate: TranslateService,
|
||||
private epersonService: EPersonDataService) {
|
||||
private epersonService: EPersonDataService,
|
||||
private authorizationService: AuthorizationDataService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -83,6 +87,7 @@ export class ProfilePageComponent implements OnInit {
|
||||
tap((user: EPerson) => this.currentUser = user)
|
||||
);
|
||||
this.groupsRD$ = this.user$.pipe(switchMap((user: EPerson) => user.groups));
|
||||
this.canChangePassword$ = this.user$.pipe(switchMap((user: EPerson) => this.authorizationService.isAuthorized(FeatureID.CanChangePassword, user._links.self.href)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -17,7 +17,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{"item.version.history.table.version" | translate}}</th>
|
||||
<th scope="col" *ngIf="(hasEpersons$ | async)">{{"item.version.history.table.editor" | translate}}</th>
|
||||
<th scope="col" *ngIf="(showSubmitter() | async)">{{"item.version.history.table.editor" | translate}}</th>
|
||||
<th scope="col">{{"item.version.history.table.date" | translate}}</th>
|
||||
<th scope="col">{{"item.version.history.table.summary" | translate}}</th>
|
||||
</tr>
|
||||
@@ -87,10 +87,8 @@
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td *ngIf="(hasEpersons$ | async)" class="version-row-element-editor">
|
||||
<span *ngVar="(version?.eperson | async)?.payload as eperson">
|
||||
<a *ngIf="eperson" [href]="'mailto:' + eperson?.email">{{eperson?.name}}</a>
|
||||
</span>
|
||||
<td class="version-row-element-editor" *ngIf="(showSubmitter() | async)">
|
||||
{{version?.submitterName}}
|
||||
</td>
|
||||
<td class="version-row-element-date">
|
||||
{{version?.created | date : 'yyyy-MM-dd HH:mm:ss'}}
|
||||
|
@@ -24,6 +24,7 @@ import { AuthorizationDataService } from '../../../core/data/feature-authorizati
|
||||
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||
import { WorkspaceitemDataService } from '../../../core/submission/workspaceitem-data.service';
|
||||
import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service';
|
||||
import { ConfigurationDataService } from '../../../core/data/configuration-data.service';
|
||||
|
||||
describe('ItemVersionsComponent', () => {
|
||||
let component: ItemVersionsComponent;
|
||||
@@ -34,6 +35,7 @@ describe('ItemVersionsComponent', () => {
|
||||
let workspaceItemDataService: WorkspaceitemDataService;
|
||||
let workflowItemDataService: WorkflowItemDataService;
|
||||
let versionService: VersionDataService;
|
||||
let configurationService: ConfigurationDataService;
|
||||
|
||||
const versionHistory = Object.assign(new VersionHistory(), {
|
||||
id: '1',
|
||||
@@ -109,6 +111,10 @@ describe('ItemVersionsComponent', () => {
|
||||
findById: EMPTY,
|
||||
});
|
||||
|
||||
const configurationServiceSpy = jasmine.createSpyObj('configurationService', {
|
||||
findByPropertyName: of(true),
|
||||
});
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
@@ -125,6 +131,7 @@ describe('ItemVersionsComponent', () => {
|
||||
{provide: VersionDataService, useValue: versionServiceSpy},
|
||||
{provide: WorkspaceitemDataService, useValue: workspaceItemDataServiceSpy},
|
||||
{provide: WorkflowItemDataService, useValue: workflowItemDataServiceSpy},
|
||||
{provide: ConfigurationDataService, useValue: configurationServiceSpy},
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
@@ -135,6 +142,7 @@ describe('ItemVersionsComponent', () => {
|
||||
workspaceItemDataService = TestBed.inject(WorkspaceitemDataService);
|
||||
workflowItemDataService = TestBed.inject(WorkflowItemDataService);
|
||||
versionService = TestBed.inject(VersionDataService);
|
||||
configurationService = TestBed.inject(ConfigurationDataService);
|
||||
|
||||
}));
|
||||
|
||||
|
@@ -5,7 +5,6 @@ import { RemoteData } from '../../../core/data/remote-data';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
combineLatest as observableCombineLatest,
|
||||
Observable,
|
||||
of,
|
||||
Subscription,
|
||||
@@ -48,6 +47,7 @@ import { ItemVersionsSharedService } from './item-versions-shared.service';
|
||||
import { WorkspaceItem } from '../../../core/submission/models/workspaceitem.model';
|
||||
import { WorkspaceitemDataService } from '../../../core/submission/workspaceitem-data.service';
|
||||
import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service';
|
||||
import { ConfigurationDataService } from '../../../core/data/configuration-data.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-versions',
|
||||
@@ -180,6 +180,7 @@ export class ItemVersionsComponent implements OnInit {
|
||||
private authorizationService: AuthorizationDataService,
|
||||
private workspaceItemDataService: WorkspaceitemDataService,
|
||||
private workflowItemDataService: WorkflowItemDataService,
|
||||
private configurationService: ConfigurationDataService,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -375,6 +376,36 @@ export class ItemVersionsComponent implements OnInit {
|
||||
return this.authorizationService.isAuthorized(FeatureID.CanEditVersion, version.self);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show submitter in version history table
|
||||
*/
|
||||
showSubmitter() {
|
||||
|
||||
const includeSubmitter$ = this.configurationService.findByPropertyName('versioning.item.history.include.submitter').pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
map((configurationProperty) => configurationProperty.values[0]),
|
||||
startWith(false),
|
||||
);
|
||||
|
||||
const isAdmin$ = combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.IsCollectionAdmin),
|
||||
this.authorizationService.isAuthorized(FeatureID.IsCommunityAdmin),
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
]).pipe(
|
||||
map(([isCollectionAdmin, isCommunityAdmin, isSiteAdmin]) => {
|
||||
return isCollectionAdmin || isCommunityAdmin || isSiteAdmin;
|
||||
}),
|
||||
take(1),
|
||||
);
|
||||
|
||||
return combineLatest([includeSubmitter$, isAdmin$]).pipe(
|
||||
map(([includeSubmitter, isAdmin]) => {
|
||||
return includeSubmitter && isAdmin;
|
||||
})
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user can delete the version
|
||||
* @param version
|
||||
@@ -389,7 +420,7 @@ export class ItemVersionsComponent implements OnInit {
|
||||
*/
|
||||
getAllVersions(versionHistory$: Observable<VersionHistory>): void {
|
||||
const currentPagination = this.paginationService.getCurrentPagination(this.options.id, this.options);
|
||||
observableCombineLatest([versionHistory$, currentPagination]).pipe(
|
||||
combineLatest([versionHistory$, currentPagination]).pipe(
|
||||
switchMap(([versionHistory, options]: [VersionHistory, PaginationComponentOptions]) => {
|
||||
return this.versionHistoryService.getVersions(versionHistory.id,
|
||||
new PaginatedSearchOptions({pagination: Object.assign({}, options, {currentPage: options.currentPage})}),
|
||||
@@ -486,7 +517,7 @@ export class ItemVersionsComponent implements OnInit {
|
||||
);
|
||||
this.itemPageRoutes$ = this.versionsRD$.pipe(
|
||||
getAllSucceededRemoteDataPayload(),
|
||||
switchMap((versions) => observableCombineLatest(...versions.page.map((version) => version.item.pipe(getAllSucceededRemoteDataPayload())))),
|
||||
switchMap((versions) => combineLatest(versions.page.map((version) => version.item.pipe(getAllSucceededRemoteDataPayload())))),
|
||||
map((versions) => {
|
||||
const itemPageRoutes = {};
|
||||
versions.forEach((item) => itemPageRoutes[item.uuid] = getItemPageRoute(item));
|
||||
|
Reference in New Issue
Block a user