Skip to content

Commit 32bd535

Browse files
DawnkaiMaciej Kleban
authored andcommitted
[CRIS-Merger] Port User Agreement edit feature from CRIS
1 parent ea85961 commit 32bd535

46 files changed

Lines changed: 2371 additions & 104 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<div class="container">
2+
<h2>{{'admin.edit-user-agreement.header' | translate}}</h2>
3+
<ds-alert [type]="'alert-info'" [dismissible]="true" [content]="'admin.edit-user-agreement.markdown' | translate"></ds-alert>
4+
@for (userAgreementText of (userAgreementTexts | keyvalue); track userAgreementText.key) {
5+
<div class="form-group">
6+
<label>{{ userAgreementText.value.languageLabel }}</label>
7+
<textarea class="col-md-12 m-2" [(ngModel)]="userAgreementText.value.text" rows="10"></textarea>
8+
</div>
9+
}
10+
<div class="col-md">
11+
<button class="btn btn-primary float-right m-2" (click)="$event.preventDefault();confirmEdit(content)" >
12+
<span>
13+
<i class="fas fa-save mr-1"></i>
14+
{{ 'admin.edit-user-agreement.save-button' | translate }}
15+
</span>
16+
</button>
17+
</div>
18+
</div>
19+
20+
<ng-template #content let-c="close" let-d="dismiss">
21+
<div class="modal-header">
22+
<h4 class="modal-title text-info">{{'admin.edit-user-agreement.confirm.title' | translate}}</h4>
23+
</div>
24+
<div class="modal-body">
25+
<p>{{'admin.edit-user-agreement.confirm.info' | translate}}</p>
26+
</div>
27+
<div class="modal-footer">
28+
<button type="button" class="btn btn-secondary" (click)="c('cancel')">{{'admin.edit-user-agreement.confirm.cancel' | translate}}</button>
29+
<button type="button" class="btn btn-primary" (click)="c('edit-without-reset')">{{'admin.edit-user-agreement.confirm.no' | translate}}</button>
30+
<button type="button" class="btn btn-danger" (click)="c('edit-with-reset')">{{'admin.edit-user-agreement.confirm.yes' | translate}}</button>
31+
</div>
32+
</ng-template>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { CommonModule } from '@angular/common';
2+
import { NO_ERRORS_SCHEMA } from '@angular/core';
3+
import {
4+
waitForAsync,
5+
ComponentFixture,
6+
inject,
7+
TestBed,
8+
} from '@angular/core/testing';
9+
import {
10+
FormsModule,
11+
ReactiveFormsModule,
12+
} from '@angular/forms';
13+
import { BrowserModule } from '@angular/platform-browser';
14+
import { RouterTestingModule } from '@angular/router/testing';
15+
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
16+
import {
17+
TranslateLoader,
18+
TranslateModule,
19+
} from '@ngx-translate/core';
20+
import {
21+
Observable,
22+
of,
23+
} from 'rxjs';
24+
25+
import { ScriptDataService } from '../../core/data/processes/script-data.service';
26+
import { SiteDataService } from '../../core/data/site-data.service';
27+
import { Site } from '../../core/shared/site.model';
28+
import { AlertComponent } from '../../shared/alert/alert.component';
29+
import { AdminEditUserAgreementComponent } from './admin-edit-user-agreement.component';
30+
import { NotificationsServiceStub } from '@dspace/core/testing/notifications-service.stub';
31+
import { TranslateLoaderMock } from '@dspace/core/testing/translate-loader.mock';
32+
import { NotificationsService } from '@dspace/core/notification-system/notifications.service';
33+
import { provideMockStore } from '@ngrx/store/testing';
34+
import { APP_DATA_SERVICES_MAP } from '@dspace/core/data-services-map-type';
35+
import { ResourceType } from '@dspace/core/shared/resource-type';
36+
37+
const TEST_MODEL = new ResourceType('testmodel');
38+
39+
const mockDataServiceMap: any = new Map([
40+
[TEST_MODEL.value, () => import('../../core/testing/test-data-service.mock').then(m => m.TestDataService)],
41+
]);
42+
43+
describe('AdminEditUserAgreementComponent', () => {
44+
45+
let component: AdminEditUserAgreementComponent;
46+
let fixture: ComponentFixture<AdminEditUserAgreementComponent>;
47+
48+
let notificationService: NotificationsServiceStub;
49+
let siteService: any;
50+
let scriptDataService: any;
51+
52+
const site: Site = Object.assign(new Site(), {
53+
metadata: {
54+
'dc.rights' : [{
55+
value: 'This is the End User Agreement text for this test',
56+
language: 'en',
57+
},
58+
{
59+
value: 'Dies ist der Text der Endbenutzervereinbarung für diesen Test',
60+
language: 'de',
61+
}],
62+
},
63+
});
64+
65+
beforeEach(waitForAsync(() => {
66+
67+
scriptDataService = {};
68+
notificationService = new NotificationsServiceStub();
69+
siteService = {
70+
find(): Observable<Site> {
71+
return of(site);
72+
},
73+
};
74+
75+
TestBed.configureTestingModule({
76+
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, RouterTestingModule,
77+
TranslateModule.forRoot({
78+
loader: {
79+
provide: TranslateLoader,
80+
useClass: TranslateLoaderMock,
81+
},
82+
}), AdminEditUserAgreementComponent],
83+
providers: [AdminEditUserAgreementComponent,
84+
provideMockStore({
85+
initialState: {
86+
index: {
87+
}
88+
}
89+
}),
90+
{ provide: APP_DATA_SERVICES_MAP, useValue: mockDataServiceMap },
91+
{ provide: NotificationsService, useValue: notificationService },
92+
{ provide: SiteDataService, useValue: siteService },
93+
{ provide: ScriptDataService, useValue: scriptDataService }],
94+
schemas: [NO_ERRORS_SCHEMA],
95+
}).overrideComponent(AdminEditUserAgreementComponent, { remove: { imports: [AlertComponent] } }).compileComponents();
96+
}));
97+
98+
beforeEach(() => {
99+
fixture = TestBed.createComponent(AdminEditUserAgreementComponent);
100+
component = fixture.componentInstance;
101+
fixture.detectChanges();
102+
});
103+
104+
it('should create AdminEditUserAgreementComponent', inject([AdminEditUserAgreementComponent], (comp: AdminEditUserAgreementComponent) => {
105+
expect(comp).toBeDefined();
106+
}));
107+
108+
it('should fill the text areas with the dc.rights values', waitForAsync(() => {
109+
expect(component.userAgreementTexts.get('en').text).toEqual('This is the End User Agreement text for this test');
110+
expect(component.userAgreementTexts.get('de').text).toEqual('Dies ist der Text der Endbenutzervereinbarung für diesen Test');
111+
}));
112+
113+
});
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import {
2+
KeyValuePipe,
3+
NgForOf,
4+
} from '@angular/common';
5+
import {
6+
Component,
7+
OnDestroy,
8+
OnInit,
9+
} from '@angular/core';
10+
import { FormsModule } from '@angular/forms';
11+
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
12+
import {
13+
TranslateModule,
14+
TranslateService,
15+
} from '@ngx-translate/core';
16+
import { Operation } from 'fast-json-patch';
17+
import { Subscription } from 'rxjs';
18+
19+
import { environment } from '../../../environments/environment';
20+
import { ScriptDataService } from '../../core/data/processes/script-data.service';
21+
import { SiteDataService } from '../../core/data/site-data.service';
22+
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
23+
import { Site } from '../../core/shared/site.model';
24+
import { AlertComponent } from '../../shared/alert/alert.component';
25+
import { NotificationsService } from '@dspace/core/notification-system/notifications.service';
26+
27+
/**
28+
* Component that represents the user agreement edit page for administrators.
29+
*/
30+
@Component({
31+
selector: 'ds-admin-edit-user-agreement',
32+
templateUrl: './admin-edit-user-agreement.component.html',
33+
imports: [
34+
AlertComponent,
35+
TranslateModule,
36+
KeyValuePipe,
37+
FormsModule,
38+
],
39+
standalone: true,
40+
})
41+
export class AdminEditUserAgreementComponent implements OnInit, OnDestroy {
42+
43+
userAgreementTexts: Map<string,UserAgreementText> = new Map();
44+
site: Site;
45+
46+
subs: Subscription[] = [];
47+
48+
USER_AGREEMENT_TEXT_METADATA = 'dc.rights';
49+
50+
USER_AGREEMENT_METADATA = 'dspace.agreements.end-user';
51+
52+
constructor(private siteService: SiteDataService,
53+
private modalService: NgbModal,
54+
private translateService: TranslateService,
55+
private notificationsService: NotificationsService,
56+
private scriptDataService: ScriptDataService ) {
57+
58+
}
59+
60+
ngOnInit(): void {
61+
62+
environment.languages.filter((language) => language.active)
63+
.forEach((language) => {
64+
this.userAgreementTexts.set( language.code, {
65+
languageLabel: language.label,
66+
text: '',
67+
});
68+
});
69+
70+
this.subs.push(this.siteService.find().subscribe((site) => {
71+
this.site = site;
72+
for (const metadata of site.metadataAsList) {
73+
if (metadata.key === this.USER_AGREEMENT_TEXT_METADATA) {
74+
const userAgreementText = this.userAgreementTexts.get(metadata.language);
75+
if (userAgreementText != null) {
76+
userAgreementText.text = metadata.value;
77+
}
78+
}
79+
}
80+
}));
81+
}
82+
83+
/**
84+
* Show the confirm modal to choose if all users must be forced to accept the new user agreement or not.
85+
* @param content the modal content
86+
*/
87+
confirmEdit(content: any) {
88+
this.modalService.open(content).result.then( (result) => {
89+
if (result === 'cancel') {
90+
return;
91+
}
92+
const operations = this.getOperationsToEditText();
93+
this.subs.push(this.siteService.patch(this.site, operations).pipe(
94+
getFirstCompletedRemoteData(),
95+
).subscribe((restResponse) => {
96+
if (restResponse.hasSucceeded) {
97+
this.notificationsService.success(this.translateService.get('admin.edit-user-agreement.success'));
98+
if ( result === 'edit-with-reset' ) {
99+
this.deleteAllUserAgreementMetadataValues();
100+
}
101+
} else {
102+
this.notificationsService.error(this.translateService.get('admin.edit-user-agreement.error'));
103+
}
104+
}));
105+
});
106+
}
107+
108+
/**
109+
* Returns the operations to update the user agreement text metadata.
110+
*/
111+
private getOperationsToEditText(): Operation[] {
112+
const firstLanguage = this.userAgreementTexts.keys().next().value;
113+
const operations = [];
114+
operations.push({
115+
op: 'replace',
116+
path: '/metadata/' + this.USER_AGREEMENT_TEXT_METADATA,
117+
value: {
118+
value: this.userAgreementTexts.get(firstLanguage).text,
119+
language: firstLanguage,
120+
},
121+
});
122+
this.userAgreementTexts.forEach((value, key) => {
123+
if (key !== firstLanguage) {
124+
operations.push({
125+
op: 'add',
126+
path: '/metadata/' + this.USER_AGREEMENT_TEXT_METADATA,
127+
value: {
128+
value: value.text,
129+
language: key,
130+
},
131+
});
132+
}
133+
});
134+
return operations;
135+
}
136+
137+
/**
138+
* Invoke the script to delete all the the user agreement text metadata values.
139+
*/
140+
private deleteAllUserAgreementMetadataValues() {
141+
this.subs.push(this.scriptDataService.invoke('metadata-deletion', [{ name: '-metadata', value: this.USER_AGREEMENT_METADATA }], []).subscribe());
142+
}
143+
144+
ngOnDestroy(): void {
145+
this.subs.forEach((sub) => sub.unsubscribe());
146+
}
147+
148+
}
149+
150+
interface UserAgreementText {
151+
languageLabel: string;
152+
text: string;
153+
}

src/app/admin/admin-routes.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import { ThemedAdminSearchPageComponent } from './admin-search-page/themed-admin-search-page.component';
1515
import { ThemedAdminWorkflowPageComponent } from './admin-workflow-page/themed-admin-workflow-page.component';
1616
import { AdminEditCmsMetadataComponent } from './admin-edit-cms-metadata/admin-edit-cms-metadata.component';
17+
import { AdminEditUserAgreementComponent } from './admin-edit-user-agreement/admin-edit-user-agreement.component';
1718

1819
export const ROUTES: Route[] = [
1920
{
@@ -62,6 +63,12 @@ export const ROUTES: Route[] = [
6263
component: AdminEditCmsMetadataComponent,
6364
data: { title: 'admin.edit-cms-metadata.title', breadcrumbKey: 'admin.edit-cms-metadata' },
6465
},
66+
{
67+
path: 'edit-user-agreement',
68+
resolve: { breadcrumb: i18nBreadcrumbResolver },
69+
component: AdminEditUserAgreementComponent,
70+
data: { title: 'admin.edit-user-agreement.title', breadcrumbKey: 'admin.edit-user-agreement' },
71+
},
6572
{
6673
path: 'system-wide-alert',
6774
resolve: { breadcrumb: i18nBreadcrumbResolver },

src/app/app.menus.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { SystemWideAlertMenuProvider } from './shared/menu/providers/system-wide
3636
import { WithdrawnReinstateItemMenuProvider } from './shared/menu/providers/withdrawn-reinstate-item.menu';
3737
import { WorkflowMenuProvider } from './shared/menu/providers/workflow.menu';
3838
import { EditCMSMetadataMenuProvider } from './shared/menu/providers/edit-cms-metadata.menu';
39+
import { EditUserAgreementMenuProvider } from './shared/menu/providers/edit-user-agreement.menu';
3940

4041
/**
4142
* Represents and builds the menu structure for the three available menus (public navbar, admin sidebar and the dso edit
@@ -77,6 +78,7 @@ export const MENUS = buildMenuStructure({
7778
CoarNotifyMenuProvider,
7879
AuditOverviewMenuProvider,
7980
EditCMSMetadataMenuProvider,
81+
EditUserAgreementMenuProvider,
8082
],
8183
[MenuID.DSO_EDIT]: [
8284
DsoOptionMenuProvider.withSubs([

src/app/home-page/home-page.component.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
<ds-home-news></ds-home-news>
21
@if (homeHeaderMetadataValue$ | async; as homeHeaderMetadataValue) {
32
<ds-markdown-viewer [value]="homeHeaderMetadataValue"></ds-markdown-viewer>
43
}

0 commit comments

Comments
 (0)