diff --git a/src/app/admin/admin-edit-cms-metadata/admin-edit-cms-metadata.component.html b/src/app/admin/admin-edit-cms-metadata/admin-edit-cms-metadata.component.html new file mode 100644 index 00000000000..ff32c6f0b2f --- /dev/null +++ b/src/app/admin/admin-edit-cms-metadata/admin-edit-cms-metadata.component.html @@ -0,0 +1,64 @@ +@if (editMode | async) { + +} +@else { + +} + + + + {{'menu.section.cms.edit.metadata.head' | translate}} + + + {{'admin.edit-cms-metadata.select-metadata' | translate}} + @for (md of metadataList; track $index) { + {{md}} + } + + + + + {{'admin.edit-cms-metadata.edit-button' | translate}} + + + + + + + + + + {{'admin.edit-cms-metadata.title' | translate}} '{{selectedMetadata}}' + + + @for (lang of languageList; track $index) { + + {{languageLabel(lang)}} + + + } + + + + + + + + + + + + + + {{ 'admin.edit-cms-metadata.save-button' | translate }} + + + + + + {{ 'admin.edit-cms-metadata.back-button' | translate }} + + + + + diff --git a/src/app/admin/admin-edit-cms-metadata/admin-edit-cms-metadata.component.scss b/src/app/admin/admin-edit-cms-metadata/admin-edit-cms-metadata.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/admin/admin-edit-cms-metadata/admin-edit-cms-metadata.component.spec.ts b/src/app/admin/admin-edit-cms-metadata/admin-edit-cms-metadata.component.spec.ts new file mode 100644 index 00000000000..8eb5a51083b --- /dev/null +++ b/src/app/admin/admin-edit-cms-metadata/admin-edit-cms-metadata.component.spec.ts @@ -0,0 +1,160 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { + ComponentFixture, + TestBed, + waitForAsync, +} from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; +import { By } from '@angular/platform-browser'; +import { NotificationsService } from '@dspace/core/notification-system/notifications.service'; +import { NotificationsServiceStub } from '@dspace/core/testing/notifications-service.stub'; +import { TranslateLoaderMock } from '@dspace/core/testing/translate-loader.mock'; +import { + TranslateLoader, + TranslateModule, +} from '@ngx-translate/core'; +import { of } from 'rxjs'; + +import { environment } from '../../../environments/environment.test'; +import { SiteDataService } from '../../core/data/site-data.service'; +import { Site } from '../../core/shared/site.model'; +import { AdminEditCmsMetadataComponent } from './admin-edit-cms-metadata.component'; + +describe('AdminEditCmsMetadataComponent', () => { + + let component: AdminEditCmsMetadataComponent; + let fixture: ComponentFixture; + const site = Object.assign(new Site(), { + metadata: { }, + }); + + const siteServiceStub = jasmine.createSpyObj('SiteDataService', { + find: jasmine.createSpy('find'), + patch: jasmine.createSpy('patch'), + }); + + const metadataValueMap = new Map([ + ['en', ''], + ['de', ''], + ['cs', ''], + ['nl', ''], + ['pt', ''], + ['fr', ''], + ['lv', ''], + ['bn', ''], + ['el', ''], + ]); + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + FormsModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock, + }, + }), + AdminEditCmsMetadataComponent, + ], + providers: [ + { provide: NotificationsService, useValue: NotificationsServiceStub }, + { provide: SiteDataService, useValue: siteServiceStub }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminEditCmsMetadataComponent); + component = fixture.componentInstance; + siteServiceStub.find.and.returnValue(of(site)); + siteServiceStub.patch.and.returnValue(of(site)); + }); + + describe('', () => { + + beforeEach(() => { + // component.editMode = false; + fixture.detectChanges(); + }); + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should show metadata cms list correctly', () => { + const metadataListLength = environment.cms.metadataList.length; + const selectMetadata = fixture.debugElement.query(By.css('select')); + expect(selectMetadata.children).toHaveSize(metadataListLength + 1); + }); + + }); + + describe('when the edit button is clicked', () => { + beforeEach(() => { + spyOn(component, 'editSelectedMetadata'); + component.selectedMetadata = 'metadata'; + fixture.detectChanges(); + }); + it('should call selectMetadataToEdit', () => { + const editButton = fixture.debugElement.query(By.css('#edit-metadata-btn')); + editButton.nativeElement.click(); + expect(component.editSelectedMetadata).toHaveBeenCalled(); + }); + }); + + describe('after the button edit is clicked', () => { + + beforeEach(() => { + component.selectedMetadata = environment.cms.metadataList[0]; + component.selectedMetadataValues = metadataValueMap; + component.editMode.next(true); + fixture.detectChanges(); + }); + + it('should render textareas of the languages', () => { + const languagesLength = environment.languages.filter((l) => l.active).length; + const textareas = fixture.debugElement.queryAll(By.css('textarea')); + console.log(textareas.length, languagesLength); + expect(textareas).toHaveSize(languagesLength); + }); + + describe('after the button save is clicked', () => { + + it('should call method edit', () => { + spyOn(component, 'saveMetadata'); + const saveButton = fixture.debugElement.query(By.css('#save-metadata-btn')); + saveButton.nativeElement.click(); + expect(component.saveMetadata).toHaveBeenCalled(); + }); + + it('should call method patch of service', () => { + component.selectedMetadata = environment.cms.metadataList[0]; + const saveButton = fixture.debugElement.query(By.css('#save-metadata-btn')); + saveButton.nativeElement.click(); + const operations = []; + operations.push({ + op: 'replace', + path: '/metadata/' + component.selectedMetadata, + value: { + value: component.selectedMetadataValues.get(environment.languages[0].code), + language: environment.languages[0].code, + }, + }); + component.selectedMetadataValues.forEach((value, key) => { + if (key !== environment.languages[0].code) { + operations.push({ + op: 'add', + path: '/metadata/' + component.selectedMetadata, + value: { + value: value, + language: key, + }, + }); + } + }); + expect(siteServiceStub.patch).toHaveBeenCalledWith(site, operations); + }); + + }); + }); +}); diff --git a/src/app/admin/admin-edit-cms-metadata/admin-edit-cms-metadata.component.ts b/src/app/admin/admin-edit-cms-metadata/admin-edit-cms-metadata.component.ts new file mode 100644 index 00000000000..c6e0e20cea1 --- /dev/null +++ b/src/app/admin/admin-edit-cms-metadata/admin-edit-cms-metadata.component.ts @@ -0,0 +1,159 @@ +import { + AsyncPipe, + NgTemplateOutlet, +} from '@angular/common'; +import { + Component, + OnInit, +} from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { NotificationsService } from '@dspace/core/notification-system/notifications.service'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; +import { Operation } from 'fast-json-patch'; +import { BehaviorSubject } from 'rxjs'; +import { BtnDisabledDirective } from 'src/app/shared/btn-disabled.directive'; + +import { environment } from '../../../environments/environment'; +import { SiteDataService } from '../../core/data/site-data.service'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { Site } from '../../core/shared/site.model'; + +/** + * Component representing the page to edit cms metadata for site. + */ +@Component({ + selector: 'ds-edit-homepage-metadata', + templateUrl: './admin-edit-cms-metadata.component.html', + styleUrls: ['./admin-edit-cms-metadata.component.scss'], + imports: [ + AsyncPipe, + BtnDisabledDirective, + FormsModule, + NgTemplateOutlet, + TranslateModule, + ], +}) +export class AdminEditCmsMetadataComponent implements OnInit { + /** + * default value of the select options + */ + selectedMetadata: string; + /** + * default true to show the select options + */ + editMode: BehaviorSubject = new BehaviorSubject(false); + /** + * The map between language codes available and their label + */ + languageMap: Map = new Map(); + /** + * The list of languages available + */ + languageList: string[] = []; + /** + * key value pair map with language and value of metadata + */ + selectedMetadataValues: Map = new Map(); + /** + * the owner object of the metadataList + */ + site: Site; + /** + * list of the metadata to be edited by the user + */ + metadataList: string[] = []; + + constructor( + private siteService: SiteDataService, + private notificationsService: NotificationsService, + private translateService: TranslateService, + ) { + } + + ngOnInit(): void { + environment.languages.filter((language) => language.active).forEach((language) => { + this.languageMap.set(language.code, language.label); + this.languageList.push(language.code); + }); + environment.cms.metadataList.forEach((md) => { + this.metadataList.push(md); + }); + this.siteService.find().subscribe((site) => { + this.site = site; + }); + } + + /** + * Save metadata values + */ + saveMetadata() { + const operations = this.getOperationsToEditText(); + + this.siteService.patch(this.site, operations).pipe(getFirstCompletedRemoteData()) + .subscribe((restResponse) => { + if (restResponse.hasSucceeded) { + this.site = restResponse.payload; + this.notificationsService.success(this.translateService.get('admin.edit-cms-metadata.success')); + this.selectedMetadata = undefined; + this.editMode.next(false); + } else { + this.notificationsService.error(this.translateService.get('admin.edit-cms-metadata.error')); + } + this.siteService.setStale(); + this.siteService.find().subscribe((site) => { + this.site = site; + }); + }); + } + + /** + * Reset metadata selection and go back to the select options + */ + back() { + this.selectedMetadata = undefined; + this.editMode.next(false); + } + + /** + * Get the label for a language key using language map + * @param key Key of the language to get the label for + * @returns Label of the language if found, otherwise the key itself + */ + languageLabel(key: string) { + return this.languageMap.get(key) ?? key; + } + + /** + * Start editing selected metadata + */ + editSelectedMetadata() { + if (this.selectedMetadata) { + this.languageList.forEach((languageCode: string) => { + const text = this.site.firstMetadataValue(this.selectedMetadata, { language: languageCode }); + this.selectedMetadataValues.set(languageCode, text); + }); + } + this.editMode.next(true); + } + + /** + * Create a list of operations to send to backend to edit selected metadata + * @returns List of operations to send to backend to edit selected metadata + */ + private getOperationsToEditText(): Operation[] { + const entries = Array.from(this.selectedMetadataValues.entries()); + + // First entry should form a 'replace' operation, then the rest should be an 'add' operation + return entries.map(([language, text], index) => ({ + op: index === 0 ? 'replace' : 'add', + path: `/metadata/${this.selectedMetadata}`, + value: { + value: text ?? '', + language: language, + }, + })); + } +} diff --git a/src/app/admin/admin-edit-user-agreement/admin-edit-user-agreement.component.html b/src/app/admin/admin-edit-user-agreement/admin-edit-user-agreement.component.html new file mode 100644 index 00000000000..9f5947dc18c --- /dev/null +++ b/src/app/admin/admin-edit-user-agreement/admin-edit-user-agreement.component.html @@ -0,0 +1,32 @@ + + {{'admin.edit-user-agreement.header' | translate}} + + @for (userAgreementText of (userAgreementTexts | keyvalue); track userAgreementText.key) { + + {{ userAgreementText.value.languageLabel }} + + + } + + + + + {{ 'admin.edit-user-agreement.save-button' | translate }} + + + + + + + + {{'admin.edit-user-agreement.confirm.title' | translate}} + + + {{'admin.edit-user-agreement.confirm.info' | translate}} + + + diff --git a/src/app/admin/admin-edit-user-agreement/admin-edit-user-agreement.component.spec.ts b/src/app/admin/admin-edit-user-agreement/admin-edit-user-agreement.component.spec.ts new file mode 100644 index 00000000000..8ef746c5cac --- /dev/null +++ b/src/app/admin/admin-edit-user-agreement/admin-edit-user-agreement.component.spec.ts @@ -0,0 +1,113 @@ +import { CommonModule } from '@angular/common'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { + ComponentFixture, + inject, + TestBed, + waitForAsync, +} from '@angular/core/testing'; +import { + FormsModule, + ReactiveFormsModule, +} from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterTestingModule } from '@angular/router/testing'; +import { APP_DATA_SERVICES_MAP } from '@dspace/core/data-services-map-type'; +import { NotificationsService } from '@dspace/core/notification-system/notifications.service'; +import { ResourceType } from '@dspace/core/shared/resource-type'; +import { NotificationsServiceStub } from '@dspace/core/testing/notifications-service.stub'; +import { TranslateLoaderMock } from '@dspace/core/testing/translate-loader.mock'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { provideMockStore } from '@ngrx/store/testing'; +import { + TranslateLoader, + TranslateModule, +} from '@ngx-translate/core'; +import { + Observable, + of, +} from 'rxjs'; + +import { ScriptDataService } from '../../core/data/processes/script-data.service'; +import { SiteDataService } from '../../core/data/site-data.service'; +import { Site } from '../../core/shared/site.model'; +import { AlertComponent } from '../../shared/alert/alert.component'; +import { AdminEditUserAgreementComponent } from './admin-edit-user-agreement.component'; + +const TEST_MODEL = new ResourceType('testmodel'); + +const mockDataServiceMap: any = new Map([ + [TEST_MODEL.value, () => import('../../core/testing/test-data-service.mock').then(m => m.TestDataService)], +]); + +describe('AdminEditUserAgreementComponent', () => { + + let component: AdminEditUserAgreementComponent; + let fixture: ComponentFixture; + + let notificationService: NotificationsServiceStub; + let siteService: any; + let scriptDataService: any; + + const site: Site = Object.assign(new Site(), { + metadata: { + 'dc.rights' : [{ + value: 'This is the End User Agreement text for this test', + language: 'en', + }, + { + value: 'Dies ist der Text der Endbenutzervereinbarung für diesen Test', + language: 'de', + }], + }, + }); + + beforeEach(waitForAsync(() => { + + scriptDataService = {}; + notificationService = new NotificationsServiceStub(); + siteService = { + find(): Observable { + return of(site); + }, + }; + + TestBed.configureTestingModule({ + imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, RouterTestingModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock, + }, + }), AdminEditUserAgreementComponent], + providers: [AdminEditUserAgreementComponent, + provideMockStore({ + initialState: { + index: { + }, + }, + }), + { provide: APP_DATA_SERVICES_MAP, useValue: mockDataServiceMap }, + { provide: NotificationsService, useValue: notificationService }, + { provide: SiteDataService, useValue: siteService }, + { provide: ScriptDataService, useValue: scriptDataService }], + schemas: [NO_ERRORS_SCHEMA], + }).overrideComponent(AdminEditUserAgreementComponent, { remove: { imports: [AlertComponent] } }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminEditUserAgreementComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create AdminEditUserAgreementComponent', inject([AdminEditUserAgreementComponent], (comp: AdminEditUserAgreementComponent) => { + expect(comp).toBeDefined(); + })); + + it('should fill the text areas with the dc.rights values', waitForAsync(() => { + expect(component.userAgreementTexts.get('en').text).toEqual('This is the End User Agreement text for this test'); + expect(component.userAgreementTexts.get('de').text).toEqual('Dies ist der Text der Endbenutzervereinbarung für diesen Test'); + })); + +}); diff --git a/src/app/admin/admin-edit-user-agreement/admin-edit-user-agreement.component.ts b/src/app/admin/admin-edit-user-agreement/admin-edit-user-agreement.component.ts new file mode 100644 index 00000000000..2bc6f7ba944 --- /dev/null +++ b/src/app/admin/admin-edit-user-agreement/admin-edit-user-agreement.component.ts @@ -0,0 +1,149 @@ +import { KeyValuePipe } from '@angular/common'; +import { + Component, + OnDestroy, + OnInit, +} from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { NotificationsService } from '@dspace/core/notification-system/notifications.service'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; +import { Operation } from 'fast-json-patch'; +import { Subscription } from 'rxjs'; + +import { environment } from '../../../environments/environment'; +import { ScriptDataService } from '../../core/data/processes/script-data.service'; +import { SiteDataService } from '../../core/data/site-data.service'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { Site } from '../../core/shared/site.model'; +import { AlertComponent } from '../../shared/alert/alert.component'; + +/** + * Component that represents the user agreement edit page for administrators. + */ +@Component({ + selector: 'ds-admin-edit-user-agreement', + templateUrl: './admin-edit-user-agreement.component.html', + imports: [ + AlertComponent, + FormsModule, + KeyValuePipe, + TranslateModule, + ], +}) +export class AdminEditUserAgreementComponent implements OnInit, OnDestroy { + + userAgreementTexts: Map = new Map(); + site: Site; + + subs: Subscription[] = []; + + USER_AGREEMENT_TEXT_METADATA = 'dc.rights'; + + USER_AGREEMENT_METADATA = 'dspace.agreements.end-user'; + + constructor(private siteService: SiteDataService, + private modalService: NgbModal, + private translateService: TranslateService, + private notificationsService: NotificationsService, + private scriptDataService: ScriptDataService ) { + + } + + ngOnInit(): void { + + environment.languages.filter((language) => language.active) + .forEach((language) => { + this.userAgreementTexts.set( language.code, { + languageLabel: language.label, + text: '', + }); + }); + + this.subs.push(this.siteService.find().subscribe((site) => { + this.site = site; + for (const metadata of site.metadataAsList) { + if (metadata.key === this.USER_AGREEMENT_TEXT_METADATA) { + const userAgreementText = this.userAgreementTexts.get(metadata.language); + if (userAgreementText != null) { + userAgreementText.text = metadata.value; + } + } + } + })); + } + + /** + * Show the confirm modal to choose if all users must be forced to accept the new user agreement or not. + * @param content the modal content + */ + confirmEdit(content: any) { + this.modalService.open(content).result.then( (result) => { + if (result === 'cancel') { + return; + } + const operations = this.getOperationsToEditText(); + this.subs.push(this.siteService.patch(this.site, operations).pipe( + getFirstCompletedRemoteData(), + ).subscribe((restResponse) => { + if (restResponse.hasSucceeded) { + this.notificationsService.success(this.translateService.get('admin.edit-user-agreement.success')); + if ( result === 'edit-with-reset' ) { + this.deleteAllUserAgreementMetadataValues(); + } + } else { + this.notificationsService.error(this.translateService.get('admin.edit-user-agreement.error')); + } + })); + }); + } + + /** + * Returns the operations to update the user agreement text metadata. + */ + private getOperationsToEditText(): Operation[] { + const firstLanguage = this.userAgreementTexts.keys().next().value; + const operations = []; + operations.push({ + op: 'replace', + path: '/metadata/' + this.USER_AGREEMENT_TEXT_METADATA, + value: { + value: this.userAgreementTexts.get(firstLanguage).text, + language: firstLanguage, + }, + }); + this.userAgreementTexts.forEach((value, key) => { + if (key !== firstLanguage) { + operations.push({ + op: 'add', + path: '/metadata/' + this.USER_AGREEMENT_TEXT_METADATA, + value: { + value: value.text, + language: key, + }, + }); + } + }); + return operations; + } + + /** + * Invoke the script to delete all the the user agreement text metadata values. + */ + private deleteAllUserAgreementMetadataValues() { + this.subs.push(this.scriptDataService.invoke('metadata-deletion', [{ name: '-metadata', value: this.USER_AGREEMENT_METADATA }], []).subscribe()); + } + + ngOnDestroy(): void { + this.subs.forEach((sub) => sub.unsubscribe()); + } + +} + +interface UserAgreementText { + languageLabel: string; + text: string; +} diff --git a/src/app/admin/admin-routes.ts b/src/app/admin/admin-routes.ts index b2a625951b3..2caca8b29fb 100644 --- a/src/app/admin/admin-routes.ts +++ b/src/app/admin/admin-routes.ts @@ -2,6 +2,8 @@ import { Route } from '@angular/router'; import { i18nBreadcrumbResolver } from '@dspace/core/breadcrumbs/i18n-breadcrumb.resolver'; import { AdminCurationTasksComponent } from './admin-curation-tasks/admin-curation-tasks.component'; +import { AdminEditCmsMetadataComponent } from './admin-edit-cms-metadata/admin-edit-cms-metadata.component'; +import { AdminEditUserAgreementComponent } from './admin-edit-user-agreement/admin-edit-user-agreement.component'; import { BatchImportPageComponent } from './admin-import-batch-page/batch-import-page.component'; import { ThemedMetadataImportPageComponent } from './admin-import-metadata-page/themed-metadata-import-page.component'; import { @@ -55,6 +57,18 @@ export const ROUTES: Route[] = [ component: BatchImportPageComponent, data: { title: 'admin.batch-import.title', breadcrumbKey: 'admin.batch-import' }, }, + { + path: 'edit-cms-metadata', + resolve: { breadcrumb: i18nBreadcrumbResolver }, + component: AdminEditCmsMetadataComponent, + data: { title: 'admin.edit-cms-metadata.title', breadcrumbKey: 'admin.edit-cms-metadata' }, + }, + { + path: 'edit-user-agreement', + resolve: { breadcrumb: i18nBreadcrumbResolver }, + component: AdminEditUserAgreementComponent, + data: { title: 'admin.edit-user-agreement.title', breadcrumbKey: 'admin.edit-user-agreement' }, + }, { path: 'system-wide-alert', resolve: { breadcrumb: i18nBreadcrumbResolver }, diff --git a/src/app/app.menus.ts b/src/app/app.menus.ts index e230b039719..34f21f81b65 100644 --- a/src/app/app.menus.ts +++ b/src/app/app.menus.ts @@ -21,6 +21,8 @@ import { CurationMenuProvider } from './shared/menu/providers/curation.menu'; import { DSpaceObjectEditMenuProvider } from './shared/menu/providers/dso-edit.menu'; import { DsoOptionMenuProvider } from './shared/menu/providers/dso-option.menu'; import { EditMenuProvider } from './shared/menu/providers/edit.menu'; +import { EditCMSMetadataMenuProvider } from './shared/menu/providers/edit-cms-metadata.menu'; +import { EditUserAgreementMenuProvider } from './shared/menu/providers/edit-user-agreement.menu'; import { ExportMenuProvider } from './shared/menu/providers/export.menu'; import { HealthMenuProvider } from './shared/menu/providers/health.menu'; import { ImportMenuProvider } from './shared/menu/providers/import.menu'; @@ -75,6 +77,8 @@ export const MENUS = buildMenuStructure({ SystemWideAlertMenuProvider, CoarNotifyMenuProvider, AuditOverviewMenuProvider, + EditCMSMetadataMenuProvider, + EditUserAgreementMenuProvider, ], [MenuID.DSO_EDIT]: [ DsoOptionMenuProvider.withSubs([ diff --git a/src/app/core/data/base/base-data.service.ts b/src/app/core/data/base/base-data.service.ts index 93f85f080da..309ab33898e 100644 --- a/src/app/core/data/base/base-data.service.ts +++ b/src/app/core/data/base/base-data.service.ts @@ -51,6 +51,12 @@ import { RequestService } from '../request.service'; import { HALDataService } from './hal-data-service.interface'; export const EMBED_SEPARATOR = '%2F'; + +/** + * The default method to construct an ID endpoint +*/ +export const constructIdEndpointDefault = (endpoint, resourceID) => `${endpoint}/${resourceID}`; + /** * Common functionality for data services. * Specific functionality that not all services would need diff --git a/src/app/core/data/site-data.service.spec.ts b/src/app/core/data/site-data.service.spec.ts index 596cbc8b02c..56ac1394366 100644 --- a/src/app/core/data/site-data.service.spec.ts +++ b/src/app/core/data/site-data.service.spec.ts @@ -11,6 +11,7 @@ import { Site } from '../shared/site.model'; import { createPaginatedList } from '../testing/utils.test'; import { createSuccessfulRemoteDataObject } from '../utilities/remote-data.utils'; import { testFindAllDataImplementation } from './base/find-all-data.spec'; +import { DefaultChangeAnalyzer } from './default-change-analyzer.service'; import { FindListOptions } from './find-list-options.model'; import { RequestService } from './request.service'; import { SiteDataService } from './site-data.service'; @@ -22,6 +23,7 @@ describe('SiteDataService', () => { let requestService: RequestService; let rdbService: RemoteDataBuildService; let objectCache: ObjectCacheService; + let comparator: DefaultChangeAnalyzer; const testObject = Object.assign(new Site(), { uuid: '9b4f22f4-164a-49db-8817-3316b6ee5746', @@ -55,11 +57,12 @@ describe('SiteDataService', () => { rdbService, objectCache, halService, + comparator, ); }); describe('composition', () => { - const initService = () => new SiteDataService(null, null, null, null); + const initService = () => new SiteDataService(null, null, null, null, null); testFindAllDataImplementation(initService); }); diff --git a/src/app/core/data/site-data.service.ts b/src/app/core/data/site-data.service.ts index b1f9581804c..dd22ce5bf7b 100644 --- a/src/app/core/data/site-data.service.ts +++ b/src/app/core/data/site-data.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { FollowLinkConfig } from '@dspace/core/shared/follow-link-config.model'; +import { Operation } from 'fast-json-patch/module/core'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -13,6 +14,12 @@ import { FindAllData, FindAllDataImpl, } from './base/find-all-data'; +import { constructIdEndpointDefault } from './base/identifiable-data.service'; +import { + PatchData, + PatchDataImpl, +} from './base/patch-data'; +import { DefaultChangeAnalyzer } from './default-change-analyzer.service'; import { FindListOptions } from './find-list-options.model'; import { PaginatedList } from './paginated-list.model'; import { RemoteData } from './remote-data'; @@ -24,16 +31,19 @@ import { RequestService } from './request.service'; @Injectable({ providedIn: 'root' }) export class SiteDataService extends BaseDataService implements FindAllData { private findAllData: FindAllData; + private patchData: PatchData; constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, + protected comparator: DefaultChangeAnalyzer, ) { super('sites', requestService, rdbService, objectCache, halService); this.findAllData = new FindAllDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive); + this.patchData = new PatchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, comparator, this.responseMsToLive, constructIdEndpointDefault); } /** @@ -64,4 +74,20 @@ export class SiteDataService extends BaseDataService implements FindAllDat public findAll(options?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig[]): Observable>> { return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); } + + /** + * Send a patch request for a specified object + * @param {Site} object The object to send a patch request for + * @param {Operation[]} operations The patch operations to be performed + */ + patch(object: Site, operations: Operation[]): Observable> { + return this.patchData.patch(object, operations); + } + + /** + * Set the processes stale + */ + setStale(): Observable { + return this.requestService.setStaleByHrefSubstring(this.linkPath); + } } diff --git a/src/app/footer/footer.component.html b/src/app/footer/footer.component.html index b4461df18a3..f407501d3f8 100644 --- a/src/app/footer/footer.component.html +++ b/src/app/footer/footer.component.html @@ -1,49 +1,54 @@
{{'admin.edit-user-agreement.confirm.info' | translate}}