Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
@if (editMode | async) {
<ng-container *ngTemplateOutlet="editMetadataTemplate"></ng-container>
}
@else {
<ng-container *ngTemplateOutlet="selectMetadataTemplate"></ng-container>
}

<ng-template #selectMetadataTemplate>
<div class="container">
<h3 class="mb-4">{{'menu.section.cms.edit.metadata.head' | translate}}</h3>
<div class="input-group">
<select class="form-control col-md-4 mr-3 mb-2" aria-label="Select metadata" [(ngModel)]="selectedMetadata">
<option [ngValue]="undefined" disabled selected>{{'admin.edit-cms-metadata.select-metadata' | translate}}</option>
@for (md of metadataList; track $index) {
<option [value]="md">{{md}}</option>
}
</select>
<span class="input-group-btn">
<button id="edit-metadata-btn" class="btn btn-primary" (click)="editSelectedMetadata()" [dsBtnDisabled]="!selectedMetadata">
<i class="fas fa-edit mr-1"></i>
{{'admin.edit-cms-metadata.edit-button' | translate}}
</button>
</span>
</div>
</div>
</ng-template>


<ng-template #editMetadataTemplate>
<div class="container">
<h2 class=" mb-5">{{'admin.edit-cms-metadata.title' | translate}} '{{selectedMetadata}}'</h2>
<div class="row">
<div class="col-md">
@for (lang of languageList; track $index) {
<div class="form-group">
<label>{{languageLabel(lang)}}</label>
<textarea class="col-md-12 m-2" [ngModel]="selectedMetadataValues.get(lang)" (ngModelChange)="selectedMetadataValues.set(lang, $event)" rows="10"></textarea>
</div>
}
</div>
</div>
<ng-container *ngTemplateOutlet="editMetadataButtonsTemplate"></ng-container>
</div>
</ng-template>


<ng-template #editMetadataButtonsTemplate>
<div class="row">
<div class="col-md">
<button id="save-metadata-btn" class="btn btn-primary float-right m-2" (click)="saveMetadata()">
<span>
<i class="fas fa-save mr-1"></i>
{{ 'admin.edit-cms-metadata.save-button' | translate }}
</span>
</button>
<button id="back-metadata-btn" class="btn btn-outline-secondary float-right m-2" (click)="back()">
<span>
<i class="fas fa-arrow-left mr-1"></i>
{{ 'admin.edit-cms-metadata.back-button' | translate }}
</span>
</button>
</div>
</div>
</ng-template>
Original file line number Diff line number Diff line change
@@ -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<AdminEditCmsMetadataComponent>;
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);
});

});
});
});
Original file line number Diff line number Diff line change
@@ -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<boolean> = new BehaviorSubject(false);
/**
* The map between language codes available and their label
*/
languageMap: Map<string, string> = new Map();
/**
* The list of languages available
*/
languageList: string[] = [];
/**
* key value pair map with language and value of metadata
*/
selectedMetadataValues: Map<string, string> = 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,
},
}));
}
}
Loading
Loading