mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-17 15:03:07 +00:00
Merge remote-tracking branch 'origin/main' into w2p-92900_Admin_options_dont_appear_after_Shibboleth_authentication_PR
This commit is contained in:
@@ -179,7 +179,7 @@ If needing to update default configurations values for production, update local
|
||||
|
||||
- Update `environment.production.ts` file in `src/environment/` for a `production` environment;
|
||||
|
||||
The environment object is provided for use as import in code and is extended with he runtime configuration on bootstrap of the application.
|
||||
The environment object is provided for use as import in code and is extended with the runtime configuration on bootstrap of the application.
|
||||
|
||||
> Take caution moving runtime configs into the buildtime configuration. They will be overwritten by what is defined in the runtime config on bootstrap.
|
||||
|
||||
|
@@ -150,6 +150,9 @@ languages:
|
||||
- code: fi
|
||||
label: Suomi
|
||||
active: true
|
||||
- code: sv
|
||||
label: Svenska
|
||||
active: true
|
||||
- code: tr
|
||||
label: Türkçe
|
||||
active: true
|
||||
@@ -248,3 +251,10 @@ bundle:
|
||||
mediaViewer:
|
||||
image: false
|
||||
video: false
|
||||
|
||||
# Whether the end user agreement is required before users use the repository.
|
||||
# If enabled, the user will be required to accept the agreement before they can use the repository.
|
||||
# And whether the privacy statement should exist or not.
|
||||
info:
|
||||
enableEndUserAgreement: true
|
||||
enablePrivacyStatement: true
|
||||
|
@@ -107,7 +107,7 @@
|
||||
"mirador": "^3.3.0",
|
||||
"mirador-dl-plugin": "^0.13.0",
|
||||
"mirador-share-plugin": "^0.11.0",
|
||||
"moment": "^2.29.2",
|
||||
"moment": "^2.29.4",
|
||||
"morgan": "^1.10.0",
|
||||
"ng-mocks": "^13.1.1",
|
||||
"ng2-file-upload": "1.4.0",
|
||||
|
@@ -18,6 +18,8 @@ import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-
|
||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
export const BBM_PAGINATION_ID = 'bbm';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-browse-by-metadata-page',
|
||||
styleUrls: ['./browse-by-metadata-page.component.scss'],
|
||||
@@ -50,7 +52,7 @@ export class BrowseByMetadataPageComponent implements OnInit {
|
||||
* The pagination config used to display the values
|
||||
*/
|
||||
paginationConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||
id: 'bbm',
|
||||
id: BBM_PAGINATION_ID,
|
||||
currentPage: 1,
|
||||
pageSize: 20
|
||||
});
|
||||
|
@@ -19,7 +19,7 @@ export abstract class SomeFeatureAuthorizationGuard implements CanActivate {
|
||||
|
||||
/**
|
||||
* True when user has authorization rights for the feature and object provided
|
||||
* Redirect the user to the unauthorized page when he/she's not authorized for the given feature
|
||||
* Redirect the user to the unauthorized page when they are not authorized for the given feature
|
||||
*/
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
|
||||
return observableCombineLatest(this.getFeatureIDs(route, state), this.getObjectUrl(route, state), this.getEPersonUuid(route, state)).pipe(
|
||||
|
@@ -38,7 +38,7 @@ export class ProcessDataService extends DataService<Process> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the endpoint for a process his files
|
||||
* Get the endpoint for the files of the process
|
||||
* @param processId The ID of the process
|
||||
*/
|
||||
getFilesEndpoint(processId: string): Observable<string> {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { returnEndUserAgreementUrlTreeOnFalse } from '../shared/authorized.operators';
|
||||
import { environment } from '../../../environments/environment';
|
||||
|
||||
/**
|
||||
* An abstract guard for redirecting users to the user agreement page if a certain condition is met
|
||||
@@ -18,6 +19,9 @@ export abstract class AbstractEndUserAgreementGuard implements CanActivate {
|
||||
* when they're finished accepting the agreement
|
||||
*/
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
|
||||
if (!environment.info.enableEndUserAgreement) {
|
||||
return observableOf(true);
|
||||
}
|
||||
return this.hasAccepted().pipe(
|
||||
returnEndUserAgreementUrlTreeOnFalse(this.router, state.url)
|
||||
);
|
||||
|
@@ -2,6 +2,7 @@ import { EndUserAgreementCurrentUserGuard } from './end-user-agreement-current-u
|
||||
import { EndUserAgreementService } from './end-user-agreement.service';
|
||||
import { Router, UrlTree } from '@angular/router';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { environment } from '../../../environments/environment.test';
|
||||
|
||||
describe('EndUserAgreementGuard', () => {
|
||||
let guard: EndUserAgreementCurrentUserGuard;
|
||||
@@ -44,5 +45,24 @@ describe('EndUserAgreementGuard', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the end user agreement is disabled', () => {
|
||||
it('should return true', (done) => {
|
||||
environment.info.enableEndUserAgreement = false;
|
||||
guard.canActivate(undefined, Object.assign({ url: 'redirect' })).subscribe((result) => {
|
||||
console.log(result);
|
||||
expect(result).toEqual(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not resolve to the end user agreement page', (done) => {
|
||||
environment.info.enableEndUserAgreement = false;
|
||||
guard.canActivate(undefined, Object.assign({ url: 'redirect' })).subscribe((result) => {
|
||||
expect(router.navigateByUrl).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { AbstractEndUserAgreementGuard } from './abstract-end-user-agreement.guard';
|
||||
import { EndUserAgreementService } from './end-user-agreement.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { environment } from '../../../environments/environment';
|
||||
|
||||
/**
|
||||
* A guard redirecting logged in users to the end agreement page when they haven't accepted the latest user agreement
|
||||
@@ -19,6 +20,10 @@ export class EndUserAgreementCurrentUserGuard extends AbstractEndUserAgreementGu
|
||||
* True when the currently logged in user has accepted the agreements or when the user is not currently authenticated
|
||||
*/
|
||||
hasAccepted(): Observable<boolean> {
|
||||
if (!environment.info.enableEndUserAgreement) {
|
||||
return observableOf(true);
|
||||
}
|
||||
|
||||
return this.endUserAgreementService.hasCurrentUserAcceptedAgreement(true);
|
||||
}
|
||||
|
||||
|
@@ -55,7 +55,7 @@ export class EndUserAgreementService {
|
||||
|
||||
/**
|
||||
* Set the current user's accepted agreement status
|
||||
* When a user is authenticated, set his/her metadata to the provided value
|
||||
* When a user is authenticated, set their metadata to the provided value
|
||||
* When no user is authenticated, set the cookie to the provided value
|
||||
* @param accepted
|
||||
*/
|
||||
|
@@ -39,7 +39,7 @@ export class MetadataValue implements MetadataValueInterface {
|
||||
value: string;
|
||||
|
||||
/**
|
||||
* The place of this MetadataValue within his list of metadata
|
||||
* The place of this MetadataValue within its list of metadata
|
||||
* This is used to render metadata in a specific custom order
|
||||
*/
|
||||
@autoserialize
|
||||
@@ -105,7 +105,7 @@ export class MetadatumViewModel {
|
||||
value: string;
|
||||
|
||||
/**
|
||||
* The place of this MetadataValue within his list of metadata
|
||||
* The place of this MetadataValue within its list of metadata
|
||||
* This is used to render metadata in a specific custom order
|
||||
*/
|
||||
place: number;
|
||||
|
@@ -67,11 +67,11 @@
|
||||
<a class="text-white" href="javascript:void(0);"
|
||||
(click)="showCookieSettings()">{{ 'footer.link.cookies' | translate}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<li *ngIf="showPrivacyPolicy">
|
||||
<a class="text-white"
|
||||
routerLink="info/privacy">{{ 'footer.link.privacy-policy' | translate}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<li *ngIf="showEndUserAgreement">
|
||||
<a class="text-white"
|
||||
routerLink="info/end-user-agreement">{{ 'footer.link.end-user-agreement' | translate}}</a>
|
||||
</li>
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Component, Optional } from '@angular/core';
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
import { KlaroService } from '../shared/cookies/klaro.service';
|
||||
import { environment } from '../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-footer',
|
||||
@@ -14,6 +15,8 @@ export class FooterComponent {
|
||||
* A boolean representing if to show or not the top footer container
|
||||
*/
|
||||
showTopFooter = false;
|
||||
showPrivacyPolicy = environment.info.enablePrivacyStatement;
|
||||
showEndUserAgreement = environment.info.enableEndUserAgreement;
|
||||
|
||||
constructor(@Optional() private cookies: KlaroService) {
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@ export class HealthStatusComponent {
|
||||
@Input() status: HealthStatus;
|
||||
|
||||
/**
|
||||
* He
|
||||
* Health Status
|
||||
*/
|
||||
HealthStatus = HealthStatus;
|
||||
|
||||
|
@@ -76,7 +76,7 @@ export class EndUserAgreementComponent implements OnInit {
|
||||
|
||||
/**
|
||||
* Cancel the agreement
|
||||
* If the user is logged in, this will log him/her out
|
||||
* If the user is logged in, this will log them out
|
||||
* If the user is not logged in, they will be redirected to the homepage
|
||||
*/
|
||||
cancel() {
|
||||
|
@@ -6,35 +6,47 @@ import { ThemedEndUserAgreementComponent } from './end-user-agreement/themed-end
|
||||
import { ThemedPrivacyComponent } from './privacy/themed-privacy.component';
|
||||
import { ThemedFeedbackComponent } from './feedback/themed-feedback.component';
|
||||
import { FeedbackGuard } from '../core/feedback/feedback.guard';
|
||||
import { environment } from '../../environments/environment';
|
||||
|
||||
|
||||
const imports = [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: FEEDBACK_PATH,
|
||||
component: ThemedFeedbackComponent,
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
data: { title: 'info.feedback.title', breadcrumbKey: 'info.feedback' },
|
||||
canActivate: [FeedbackGuard]
|
||||
}
|
||||
])
|
||||
];
|
||||
|
||||
if (environment.info.enableEndUserAgreement) {
|
||||
imports.push(
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: END_USER_AGREEMENT_PATH,
|
||||
component: ThemedEndUserAgreementComponent,
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
data: { title: 'info.end-user-agreement.title', breadcrumbKey: 'info.end-user-agreement' }
|
||||
}
|
||||
]));
|
||||
}
|
||||
if (environment.info.enablePrivacyStatement) {
|
||||
imports.push(
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: PRIVACY_PATH,
|
||||
component: ThemedPrivacyComponent,
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
data: { title: 'info.privacy.title', breadcrumbKey: 'info.privacy' }
|
||||
}
|
||||
]));
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: END_USER_AGREEMENT_PATH,
|
||||
component: ThemedEndUserAgreementComponent,
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
data: { title: 'info.end-user-agreement.title', breadcrumbKey: 'info.end-user-agreement' }
|
||||
}
|
||||
]),
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: PRIVACY_PATH,
|
||||
component: ThemedPrivacyComponent,
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
data: { title: 'info.privacy.title', breadcrumbKey: 'info.privacy' }
|
||||
}
|
||||
]),
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: FEEDBACK_PATH,
|
||||
component: ThemedFeedbackComponent,
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
data: { title: 'info.feedback.title', breadcrumbKey: 'info.feedback' },
|
||||
canActivate: [FeedbackGuard]
|
||||
}
|
||||
])
|
||||
...imports
|
||||
]
|
||||
})
|
||||
/**
|
||||
|
@@ -24,7 +24,7 @@ import { ConfirmationModalComponent } from '../../shared/confirmation-modal/conf
|
||||
templateUrl: './profile-page-researcher-form.component.html',
|
||||
})
|
||||
/**
|
||||
* Component for a user to create/delete or change his researcher profile.
|
||||
* Component for a user to create/delete or change their researcher profile.
|
||||
*/
|
||||
export class ProfilePageResearcherFormComponent implements OnInit {
|
||||
|
||||
|
@@ -240,10 +240,12 @@ describe('BrowseByComponent', () => {
|
||||
});
|
||||
describe('back', () => {
|
||||
it('should navigate back to the main browse page', () => {
|
||||
const id = 'test-pagination';
|
||||
comp.back();
|
||||
expect(paginationService.updateRoute).toHaveBeenCalledWith('test-pagination', {page: 1}, {
|
||||
expect(paginationService.updateRoute).toHaveBeenCalledWith(id, {page: 1}, {
|
||||
value: null,
|
||||
startsWith: null
|
||||
startsWith: null,
|
||||
[id + '.return']: null
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import { Component, EventEmitter, Injector, Input, OnInit, Output } from '@angular/core';
|
||||
import { Component, EventEmitter, Injector, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||
import { PaginationComponentOptions } from '../pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { fadeIn, fadeInOut } from '../animations/fade';
|
||||
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs';
|
||||
import { ListableObject } from '../object-collection/shared/listable-object.model';
|
||||
import { getStartsWithComponent, StartsWithType } from '../starts-with/starts-with-decorator';
|
||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||
@@ -25,7 +25,7 @@ import { hasValue } from '../empty.util';
|
||||
/**
|
||||
* Component to display a browse-by page for any ListableObject
|
||||
*/
|
||||
export class BrowseByComponent implements OnInit {
|
||||
export class BrowseByComponent implements OnInit, OnDestroy {
|
||||
|
||||
/**
|
||||
* ViewMode that should be passed to {@link ListableObjectComponentLoaderComponent}.
|
||||
@@ -112,6 +112,16 @@ export class BrowseByComponent implements OnInit {
|
||||
*/
|
||||
shouldDisplayResetButton$: Observable<boolean>;
|
||||
|
||||
/**
|
||||
* Page number of the previous page
|
||||
*/
|
||||
previousPage$ = new BehaviorSubject<string>('1');
|
||||
|
||||
/**
|
||||
* Subscription that has to be unsubscribed from on destroy
|
||||
*/
|
||||
sub: Subscription;
|
||||
|
||||
public constructor(private injector: Injector,
|
||||
protected paginationService: PaginationService,
|
||||
private routeService: RouteService,
|
||||
@@ -171,9 +181,20 @@ export class BrowseByComponent implements OnInit {
|
||||
this.shouldDisplayResetButton$ = observableCombineLatest([startsWith$, value$]).pipe(
|
||||
map(([startsWith, value]) => hasValue(startsWith) || hasValue(value))
|
||||
);
|
||||
this.sub = this.routeService.getQueryParameterValue(this.paginationConfig.id + '.return').subscribe(this.previousPage$);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate back to the previous browse by page
|
||||
*/
|
||||
back() {
|
||||
this.paginationService.updateRoute(this.paginationConfig.id, {page: 1}, {value: null, startsWith: null});
|
||||
const page = +this.previousPage$.value > 1 ? +this.previousPage$.value : 1;
|
||||
this.paginationService.updateRoute(this.paginationConfig.id, {page: page}, {[this.paginationConfig.id + '.return']: null, value: null, startsWith: null});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.sub) {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -63,6 +63,11 @@ export class BrowserKlaroService extends KlaroService {
|
||||
* - Add and translate klaro configuration messages
|
||||
*/
|
||||
initialize() {
|
||||
if (!environment.info.enablePrivacyStatement) {
|
||||
delete this.klaroConfig.privacyPolicy;
|
||||
this.klaroConfig.translations.en.consentNotice.description = 'cookies.consent.content-notice.description.no-privacy';
|
||||
}
|
||||
|
||||
this.translateService.setDefaultLang(environment.defaultLanguage);
|
||||
|
||||
const user$: Observable<EPerson> = this.getUser$();
|
||||
@@ -90,7 +95,6 @@ export class BrowserKlaroService extends KlaroService {
|
||||
this.translateConfiguration();
|
||||
Klaro.setup(this.klaroConfig);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -24,7 +24,7 @@ export const klaroConfiguration: any = {
|
||||
/*
|
||||
Setting 'hideLearnMore' to 'true' will hide the "learn more / customize" link in
|
||||
the consent notice. We strongly advise against using this under most
|
||||
circumstances, as it keeps the user from customizing his/her consent choices.
|
||||
circumstances, as it keeps the user from customizing their consent choices.
|
||||
*/
|
||||
hideLearnMore: false,
|
||||
|
||||
|
@@ -20,6 +20,7 @@ import {
|
||||
createSuccessfulRemoteDataObject$
|
||||
} from '../../../remote-data.utils';
|
||||
import { ExportMetadataSelectorComponent } from './export-metadata-selector.component';
|
||||
import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
|
||||
|
||||
// No way to add entryComponents yet to testbed; alternative implemented; source: https://stackoverflow.com/questions/41689468/how-to-shallow-test-a-component-with-an-entrycomponents
|
||||
@NgModule({
|
||||
@@ -47,6 +48,7 @@ describe('ExportMetadataSelectorComponent', () => {
|
||||
let router;
|
||||
let notificationService: NotificationsServiceStub;
|
||||
let scriptService;
|
||||
let authorizationDataService;
|
||||
|
||||
const mockItem = Object.assign(new Item(), {
|
||||
id: 'fake-id',
|
||||
@@ -95,6 +97,9 @@ describe('ExportMetadataSelectorComponent', () => {
|
||||
invoke: createSuccessfulRemoteDataObject$({ processId: '45' })
|
||||
}
|
||||
);
|
||||
authorizationDataService = jasmine.createSpyObj('authorizationDataService', {
|
||||
isAuthorized: observableOf(true)
|
||||
});
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), ModelTestModule],
|
||||
declarations: [ExportMetadataSelectorComponent],
|
||||
@@ -102,6 +107,7 @@ describe('ExportMetadataSelectorComponent', () => {
|
||||
{ provide: NgbActiveModal, useValue: modalStub },
|
||||
{ provide: NotificationsService, useValue: notificationService },
|
||||
{ provide: ScriptDataService, useValue: scriptService },
|
||||
{ provide: AuthorizationDataService, useValue: authorizationDataService },
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
@@ -150,7 +156,7 @@ describe('ExportMetadataSelectorComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('if collection is selected', () => {
|
||||
describe('if collection is selected and is admin', () => {
|
||||
let scriptRequestSucceeded;
|
||||
beforeEach((done) => {
|
||||
spyOn((component as any).modalService, 'open').and.returnValue(modalRef);
|
||||
@@ -159,7 +165,32 @@ describe('ExportMetadataSelectorComponent', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should invoke the metadata-export script with option -i uuid', () => {
|
||||
it('should invoke the metadata-export script with option -i uuid and -a option', () => {
|
||||
const parameterValues: ProcessParameter[] = [
|
||||
Object.assign(new ProcessParameter(), { name: '-i', value: mockCollection.uuid }),
|
||||
Object.assign(new ProcessParameter(), { name: '-a' }),
|
||||
];
|
||||
expect(scriptService.invoke).toHaveBeenCalledWith(METADATA_EXPORT_SCRIPT_NAME, parameterValues, []);
|
||||
});
|
||||
it('success notification is shown', () => {
|
||||
expect(scriptRequestSucceeded).toBeTrue();
|
||||
expect(notificationService.success).toHaveBeenCalled();
|
||||
});
|
||||
it('redirected to process page', () => {
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith('/processes/45');
|
||||
});
|
||||
});
|
||||
describe('if collection is selected and is not admin', () => {
|
||||
let scriptRequestSucceeded;
|
||||
beforeEach((done) => {
|
||||
(authorizationDataService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));
|
||||
spyOn((component as any).modalService, 'open').and.returnValue(modalRef);
|
||||
component.navigate(mockCollection).subscribe((succeeded: boolean) => {
|
||||
scriptRequestSucceeded = succeeded;
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should invoke the metadata-export script with option -i uuid without the -a option', () => {
|
||||
const parameterValues: ProcessParameter[] = [
|
||||
Object.assign(new ProcessParameter(), { name: '-i', value: mockCollection.uuid }),
|
||||
];
|
||||
@@ -174,7 +205,7 @@ describe('ExportMetadataSelectorComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('if community is selected', () => {
|
||||
describe('if community is selected and is an admin', () => {
|
||||
let scriptRequestSucceeded;
|
||||
beforeEach((done) => {
|
||||
spyOn((component as any).modalService, 'open').and.returnValue(modalRef);
|
||||
@@ -183,7 +214,32 @@ describe('ExportMetadataSelectorComponent', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should invoke the metadata-export script with option -i uuid', () => {
|
||||
it('should invoke the metadata-export script with option -i uuid and -a option if the user is an admin', () => {
|
||||
const parameterValues: ProcessParameter[] = [
|
||||
Object.assign(new ProcessParameter(), { name: '-i', value: mockCommunity.uuid }),
|
||||
Object.assign(new ProcessParameter(), { name: '-a' }),
|
||||
];
|
||||
expect(scriptService.invoke).toHaveBeenCalledWith(METADATA_EXPORT_SCRIPT_NAME, parameterValues, []);
|
||||
});
|
||||
it('success notification is shown', () => {
|
||||
expect(scriptRequestSucceeded).toBeTrue();
|
||||
expect(notificationService.success).toHaveBeenCalled();
|
||||
});
|
||||
it('redirected to process page', () => {
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith('/processes/45');
|
||||
});
|
||||
});
|
||||
describe('if community is selected and is not an admin', () => {
|
||||
let scriptRequestSucceeded;
|
||||
beforeEach((done) => {
|
||||
(authorizationDataService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));
|
||||
spyOn((component as any).modalService, 'open').and.returnValue(modalRef);
|
||||
component.navigate(mockCommunity).subscribe((succeeded: boolean) => {
|
||||
scriptRequestSucceeded = succeeded;
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should invoke the metadata-export script with option -i uuid without the -a option', () => {
|
||||
const parameterValues: ProcessParameter[] = [
|
||||
Object.assign(new ProcessParameter(), { name: '-i', value: mockCommunity.uuid }),
|
||||
];
|
||||
|
@@ -19,6 +19,8 @@ import { getFirstCompletedRemoteData } from '../../../../core/shared/operators';
|
||||
import { Process } from '../../../../process-page/processes/process.model';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
import { getProcessDetailRoute } from '../../../../process-page/process-page-routing.paths';
|
||||
import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../../../core/data/feature-authorization/feature-id';
|
||||
|
||||
/**
|
||||
* Component to wrap a list of existing dso's inside a modal
|
||||
@@ -36,6 +38,7 @@ export class ExportMetadataSelectorComponent extends DSOSelectorModalWrapperComp
|
||||
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router,
|
||||
protected notificationsService: NotificationsService, protected translationService: TranslateService,
|
||||
protected scriptDataService: ScriptDataService,
|
||||
protected authorizationDataService: AuthorizationDataService,
|
||||
private modalService: NgbModal) {
|
||||
super(activeModal, route);
|
||||
}
|
||||
@@ -82,24 +85,29 @@ export class ExportMetadataSelectorComponent extends DSOSelectorModalWrapperComp
|
||||
const parameterValues: ProcessParameter[] = [
|
||||
Object.assign(new ProcessParameter(), { name: '-i', value: dso.uuid }),
|
||||
];
|
||||
return this.scriptDataService.invoke(METADATA_EXPORT_SCRIPT_NAME, parameterValues, [])
|
||||
.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((rd: RemoteData<Process>) => {
|
||||
if (rd.hasSucceeded) {
|
||||
const title = this.translationService.get('process.new.notification.success.title');
|
||||
const content = this.translationService.get('process.new.notification.success.content');
|
||||
this.notificationsService.success(title, content);
|
||||
if (isNotEmpty(rd.payload)) {
|
||||
this.router.navigateByUrl(getProcessDetailRoute(rd.payload.processId));
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
const title = this.translationService.get('process.new.notification.error.title');
|
||||
const content = this.translationService.get('process.new.notification.error.content');
|
||||
this.notificationsService.error(title, content);
|
||||
return false;
|
||||
return this.authorizationDataService.isAuthorized(FeatureID.AdministratorOf).pipe(
|
||||
switchMap((isAdmin) => {
|
||||
if (isAdmin) {
|
||||
parameterValues.push(Object.assign(new ProcessParameter(), {name: '-a'}));
|
||||
}
|
||||
return this.scriptDataService.invoke(METADATA_EXPORT_SCRIPT_NAME, parameterValues, []);
|
||||
}),
|
||||
getFirstCompletedRemoteData(),
|
||||
map((rd: RemoteData<Process>) => {
|
||||
if (rd.hasSucceeded) {
|
||||
const title = this.translationService.get('process.new.notification.success.title');
|
||||
const content = this.translationService.get('process.new.notification.success.content');
|
||||
this.notificationsService.success(title, content);
|
||||
if (isNotEmpty(rd.payload)) {
|
||||
this.router.navigateByUrl(getProcessDetailRoute(rd.payload.processId));
|
||||
}
|
||||
}));
|
||||
return true;
|
||||
} else {
|
||||
const title = this.translationService.get('process.new.notification.error.title');
|
||||
const content = this.translationService.get('process.new.notification.error.content');
|
||||
this.notificationsService.error(title, content);
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<div class="d-flex flex-row">
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="[]" [queryParams]="getQueryParams()" [queryParamsHandling]="'merge'" class="lead">
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="[]" [queryParams]="queryParams$ | async" [queryParamsHandling]="'merge'" class="lead">
|
||||
{{object.value}}
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="lead">
|
||||
|
@@ -4,7 +4,9 @@ import { By } from '@angular/platform-browser';
|
||||
import { TruncatePipe } from '../../utils/truncate.pipe';
|
||||
import { BrowseEntryListElementComponent } from './browse-entry-list-element.component';
|
||||
import { BrowseEntry } from '../../../core/shared/browse-entry.model';
|
||||
|
||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||
import { RouteService } from '../../../core/services/route.service';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
let browseEntryListElementComponent: BrowseEntryListElementComponent;
|
||||
let fixture: ComponentFixture<BrowseEntryListElementComponent>;
|
||||
|
||||
@@ -13,12 +15,28 @@ const mockValue: BrowseEntry = Object.assign(new BrowseEntry(), {
|
||||
value: 'De Langhe Kristof'
|
||||
});
|
||||
|
||||
describe('MetadataListElementComponent', () => {
|
||||
let paginationService;
|
||||
let routeService;
|
||||
const pageParam = 'bbm.page';
|
||||
|
||||
function init() {
|
||||
paginationService = jasmine.createSpyObj('paginationService', {
|
||||
getPageParam: pageParam
|
||||
});
|
||||
|
||||
routeService = jasmine.createSpyObj('routeService', {
|
||||
getQueryParameterValue: observableOf('1')
|
||||
});
|
||||
}
|
||||
describe('BrowseEntryListElementComponent', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
init();
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [BrowseEntryListElementComponent, TruncatePipe],
|
||||
providers: [
|
||||
{ provide: 'objectElementProvider', useValue: { mockValue } }
|
||||
{ provide: 'objectElementProvider', useValue: { mockValue } },
|
||||
{provide: PaginationService, useValue: paginationService},
|
||||
{provide: RouteService, useValue: routeService},
|
||||
],
|
||||
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
|
@@ -1,9 +1,15 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
||||
import { BrowseEntry } from '../../../core/shared/browse-entry.model';
|
||||
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||
import { listableObjectComponent } from '../../object-collection/shared/listable-object/listable-object.decorator';
|
||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||
import { Params } from '@angular/router';
|
||||
import { BBM_PAGINATION_ID } from '../../../browse-by/browse-by-metadata-page/browse-by-metadata-page.component';
|
||||
import { RouteService } from 'src/app/core/services/route.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-browse-entry-list-element',
|
||||
@@ -15,16 +21,35 @@ import { listableObjectComponent } from '../../object-collection/shared/listable
|
||||
* This component is automatically used to create a list view for BrowseEntry objects when used in ObjectCollectionComponent
|
||||
*/
|
||||
@listableObjectComponent(BrowseEntry, ViewMode.ListElement)
|
||||
export class BrowseEntryListElementComponent extends AbstractListableElementComponent<BrowseEntry> {
|
||||
export class BrowseEntryListElementComponent extends AbstractListableElementComponent<BrowseEntry> implements OnInit {
|
||||
/**
|
||||
* Emits the query parameters for the link of this browse entry list element
|
||||
*/
|
||||
queryParams$: Observable<Params>;
|
||||
|
||||
constructor(private paginationService: PaginationService, private routeService: RouteService) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.queryParams$ = this.getQueryParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query params to access the item page of this browse entry.
|
||||
*/
|
||||
public getQueryParams(): {[param: string]: any} {
|
||||
return {
|
||||
value: this.object.value,
|
||||
authority: !!this.object.authority ? this.object.authority : undefined,
|
||||
startsWith: undefined,
|
||||
};
|
||||
private getQueryParams(): Observable<Params> {
|
||||
const pageParamName = this.paginationService.getPageParam(BBM_PAGINATION_ID);
|
||||
return this.routeService.getQueryParameterValue(pageParamName).pipe(
|
||||
map((currentPage) => {
|
||||
return {
|
||||
value: this.object.value,
|
||||
authority: !!this.object.authority ? this.object.authority : undefined,
|
||||
startsWith: undefined,
|
||||
[pageParamName]: null,
|
||||
[BBM_PAGINATION_ID + '.return']: currentPage
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ import { metadataRepresentationComponent } from '../../../metadata-representatio
|
||||
/**
|
||||
* A component for displaying MetadataRepresentation objects in the form of items
|
||||
* It will send the MetadataRepresentation object along with ElementViewMode.SetElement to the ItemTypeSwitcherComponent,
|
||||
* which will in his turn decide how to render the item as metadata.
|
||||
* which will in its turn decide how to render the item as metadata.
|
||||
*/
|
||||
export class ItemMetadataListElementComponent extends MetadataRepresentationListElementComponent {
|
||||
/**
|
||||
|
@@ -19,6 +19,7 @@
|
||||
<span *ngIf="item.allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length === 0">{{'mydspace.results.no-authors' | translate}}</span>
|
||||
<span *ngFor="let author of item.allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">
|
||||
<span [innerHTML]="author"><span [innerHTML]="author"></span></span>
|
||||
<span *ngIf="!last">; </span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
|
@@ -1225,6 +1225,8 @@
|
||||
|
||||
"cookies.consent.content-notice.description": "We collect and process your personal information for the following purposes: <strong>Authentication, Preferences, Acknowledgement and Statistics</strong>. <br/> To learn more, please read our {privacyPolicy}.",
|
||||
|
||||
"cookies.consent.content-notice.description.no-privacy": "We collect and process your personal information for the following purposes: <strong>Authentication, Preferences, Acknowledgement and Statistics</strong>.",
|
||||
|
||||
"cookies.consent.content-notice.learnMore": "Customize",
|
||||
|
||||
"cookies.consent.content-modal.description": "Here you can see and customize the information that we collect about you.",
|
||||
|
8078
src/assets/i18n/sv.json5
Normal file
8078
src/assets/i18n/sv.json5
Normal file
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@ import { MediaViewerConfig } from './media-viewer-config.interface';
|
||||
import { BrowseByConfig } from './browse-by-config.interface';
|
||||
import { BundleConfig } from './bundle-config.interface';
|
||||
import { ActuatorsConfig } from './actuators.config';
|
||||
import { InfoConfig } from './info-config.interface';
|
||||
|
||||
interface AppConfig extends Config {
|
||||
ui: UIServerConfig;
|
||||
@@ -36,6 +37,7 @@ interface AppConfig extends Config {
|
||||
mediaViewer: MediaViewerConfig;
|
||||
bundle: BundleConfig;
|
||||
actuators: ActuatorsConfig
|
||||
info: InfoConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -16,6 +16,7 @@ import { ThemeConfig } from './theme.model';
|
||||
import { UIServerConfig } from './ui-server-config.interface';
|
||||
import { BundleConfig } from './bundle-config.interface';
|
||||
import { ActuatorsConfig } from './actuators.config';
|
||||
import { InfoConfig } from './info-config.interface';
|
||||
|
||||
export class DefaultAppConfig implements AppConfig {
|
||||
production = false;
|
||||
@@ -192,6 +193,7 @@ export class DefaultAppConfig implements AppConfig {
|
||||
{ code: 'pt-PT', label: 'Português', active: true },
|
||||
{ code: 'pt-BR', label: 'Português do Brasil', active: true },
|
||||
{ code: 'fi', label: 'Suomi', active: true },
|
||||
{ code: 'sv', label: 'Svenska', active: true },
|
||||
{ code: 'tr', label: 'Türkçe', active: true },
|
||||
{ code: 'bn', label: 'বাংলা', active: true }
|
||||
];
|
||||
@@ -324,4 +326,16 @@ export class DefaultAppConfig implements AppConfig {
|
||||
image: false,
|
||||
video: false
|
||||
};
|
||||
// Whether the end-user-agreement and privacy policy feature should be enabled or not.
|
||||
// Disabling the end user agreement feature will result in:
|
||||
// - Users no longer being forced to accept the end-user-agreement before they can access the repository
|
||||
// - A 404 page if you manually try to navigate to the end-user-agreement page at info/end-user-agreement
|
||||
// - All end-user-agreement related links and pages will be removed from the UI (e.g. in the footer)
|
||||
// Disabling the privacy policy feature will result in:
|
||||
// - A 404 page if you manually try to navigate to the privacy policy page at info/privacy
|
||||
// - All mentions of the privacy policy being removed from the UI (e.g. in the footer)
|
||||
info: InfoConfig = {
|
||||
enableEndUserAgreement: true,
|
||||
enablePrivacyStatement: true
|
||||
};
|
||||
}
|
||||
|
6
src/config/info-config.interface.ts
Normal file
6
src/config/info-config.interface.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Config } from './config.interface';
|
||||
|
||||
export interface InfoConfig extends Config {
|
||||
enableEndUserAgreement: boolean;
|
||||
enablePrivacyStatement: boolean;
|
||||
}
|
@@ -243,5 +243,9 @@ export const environment: BuildConfig = {
|
||||
mediaViewer: {
|
||||
image: true,
|
||||
video: true
|
||||
},
|
||||
info: {
|
||||
enableEndUserAgreement: true,
|
||||
enablePrivacyStatement: true,
|
||||
}
|
||||
};
|
||||
|
5
src/styles/_vendor.scss
Normal file
5
src/styles/_vendor.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
// node_modules imports meant for all the themes
|
||||
|
||||
@import '~node_modules/bootstrap/scss/bootstrap.scss';
|
||||
@import '~node_modules/nouislider/distribute/nouislider.min';
|
||||
@import '~node_modules/ngx-ui-switch/ui-switch.component.scss';
|
@@ -1,8 +1,6 @@
|
||||
@import './helpers/font_awesome_imports.scss';
|
||||
@import '../../node_modules/bootstrap/scss/bootstrap.scss';
|
||||
@import '../../node_modules/nouislider/distribute/nouislider.min';
|
||||
@import './_vendor.scss';
|
||||
@import './_custom_variables.scss';
|
||||
@import './bootstrap_variables_mapping.scss';
|
||||
@import './_truncatable-part.component.scss';
|
||||
@import './_global-styles.scss';
|
||||
@import '../../node_modules/ngx-ui-switch/ui-switch.component.scss';
|
||||
|
@@ -4,11 +4,9 @@
|
||||
@import '../../../styles/_variables.scss';
|
||||
@import '../../../styles/_mixins.scss';
|
||||
@import '../../../styles/helpers/font_awesome_imports.scss';
|
||||
@import '../../../../node_modules/bootstrap/scss/bootstrap.scss';
|
||||
@import '../../../../node_modules/nouislider/distribute/nouislider.min';
|
||||
@import '../../../styles/_vendor.scss';
|
||||
@import '../../../styles/_custom_variables.scss';
|
||||
@import './_theme_css_variable_overrides.scss';
|
||||
@import '../../../styles/bootstrap_variables_mapping.scss';
|
||||
@import '../../../styles/_truncatable-part.component.scss';
|
||||
@import './_global-styles.scss';
|
||||
@import '../../../../node_modules/ngx-ui-switch/ui-switch.component.scss';
|
||||
|
@@ -4,11 +4,9 @@
|
||||
@import '../../../styles/_variables.scss';
|
||||
@import '../../../styles/_mixins.scss';
|
||||
@import '../../../styles/helpers/font_awesome_imports.scss';
|
||||
@import '../../../../node_modules/bootstrap/scss/bootstrap.scss';
|
||||
@import '../../../../node_modules/nouislider/distribute/nouislider.min';
|
||||
@import '../../../styles/_vendor.scss';
|
||||
@import '../../../styles/_custom_variables.scss';
|
||||
@import './_theme_css_variable_overrides.scss';
|
||||
@import '../../../styles/bootstrap_variables_mapping.scss';
|
||||
@import '../../../styles/_truncatable-part.component.scss';
|
||||
@import './_global-styles.scss';
|
||||
@import '../../../../node_modules/ngx-ui-switch/ui-switch.component.scss';
|
||||
|
14
yarn.lock
14
yarn.lock
@@ -8741,10 +8741,10 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
moment@^2.29.2:
|
||||
version "2.29.2"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4"
|
||||
integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==
|
||||
moment@^2.29.4:
|
||||
version "2.29.4"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
|
||||
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
|
||||
|
||||
morgan@^1.10.0:
|
||||
version "1.10.0"
|
||||
@@ -12239,9 +12239,9 @@ terser@5.11.0:
|
||||
source-map-support "~0.5.20"
|
||||
|
||||
terser@^4.6.12, terser@^4.6.3:
|
||||
version "4.8.0"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17"
|
||||
integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==
|
||||
version "4.8.1"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.1.tgz#a00e5634562de2239fd404c649051bf6fc21144f"
|
||||
integrity sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==
|
||||
dependencies:
|
||||
commander "^2.20.0"
|
||||
source-map "~0.6.1"
|
||||
|
Reference in New Issue
Block a user