Merge pull request #1417 from 4Science/CST-4878

Upload files - Edit bitstream (improvements and bug fixes)
This commit is contained in:
Tim Donohue
2021-12-21 14:33:22 -06:00
committed by GitHub
7 changed files with 559 additions and 433 deletions

View File

@@ -1,9 +1,21 @@
<div> <div>
<div class="modal-header">
<h4 class="modal-title">{{'submission.sections.upload.edit.title' | translate}}</h4>
<button type="button" class="close" (click)="onModalClose()" aria-label="Close" [disabled]="isSaving">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<ds-form *ngIf="formModel" <ds-form *ngIf="formModel"
#formRef="formComponent" #formRef="formComponent"
[formId]="formId" [formId]="formId"
[formModel]="formModel" [formModel]="formModel"
[displaySubmit]="false" [displaySubmit]="!isSaving"
[displayCancel]="false" [displayCancel]="!isSaving"
(submitForm)="onSubmit()"
(cancel)="onModalClose()"
(dfChange)="onChange($event)"></ds-form> (dfChange)="onChange($event)"></ds-form>
</div>
</div> </div>

View File

@@ -1,5 +1,5 @@
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core'; import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
import { waitForAsync, ComponentFixture, inject, TestBed } from '@angular/core/testing'; import { waitForAsync, ComponentFixture, inject, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
@@ -17,19 +17,37 @@ import { SubmissionService } from '../../../../submission.service';
import { SubmissionSectionUploadFileEditComponent } from './section-upload-file-edit.component'; import { SubmissionSectionUploadFileEditComponent } from './section-upload-file-edit.component';
import { POLICY_DEFAULT_WITH_LIST } from '../../section-upload.component'; import { POLICY_DEFAULT_WITH_LIST } from '../../section-upload.component';
import { import {
mockGroup,
mockSubmissionCollectionId, mockSubmissionCollectionId,
mockSubmissionId, mockSubmissionId,
mockUploadConfigResponse, mockUploadConfigResponse,
mockUploadConfigResponseMetadata, mockUploadConfigResponseMetadata,
mockUploadFiles mockUploadFiles,
mockFileFormData,
mockSubmissionObject,
} from '../../../../../shared/mocks/submission.mock'; } from '../../../../../shared/mocks/submission.mock';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FormComponent } from '../../../../../shared/form/form.component'; import { FormComponent } from '../../../../../shared/form/form.component';
import { FormService } from '../../../../../shared/form/form.service'; import { FormService } from '../../../../../shared/form/form.service';
import { getMockFormService } from '../../../../../shared/mocks/form-service.mock'; import { getMockFormService } from '../../../../../shared/mocks/form-service.mock';
import { Group } from '../../../../../core/eperson/models/group.model';
import { createTestComponent } from '../../../../../shared/testing/utils.test'; import { createTestComponent } from '../../../../../shared/testing/utils.test';
import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { JsonPatchOperationsBuilder } from '../../../../../core/json-patch/builder/json-patch-operations-builder';
import { SubmissionJsonPatchOperationsServiceStub } from '../../../../../shared/testing/submission-json-patch-operations-service.stub';
import { SubmissionJsonPatchOperationsService } from '../../../../../core/submission/submission-json-patch-operations.service';
import { SectionUploadService } from '../../section-upload.service';
import { getMockSectionUploadService } from '../../../../../shared/mocks/section-upload.service.mock';
import { FormFieldMetadataValueObject } from '../../../../../shared/form/builder/models/form-field-metadata-value.model';
import { JsonPatchOperationPathCombiner } from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner';
import { dateToISOFormat } from '../../../../../shared/date.util';
import { of } from 'rxjs';
const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', {
add: jasmine.createSpy('add'),
replace: jasmine.createSpy('replace'),
remove: jasmine.createSpy('remove'),
});
const formMetadataMock = ['dc.title', 'dc.description'];
describe('SubmissionSectionUploadFileEditComponent test suite', () => { describe('SubmissionSectionUploadFileEditComponent test suite', () => {
@@ -38,7 +56,12 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
let fixture: ComponentFixture<SubmissionSectionUploadFileEditComponent>; let fixture: ComponentFixture<SubmissionSectionUploadFileEditComponent>;
let submissionServiceStub: SubmissionServiceStub; let submissionServiceStub: SubmissionServiceStub;
let formbuilderService: any; let formbuilderService: any;
let operationsBuilder: any;
let operationsService: any;
let formService: any;
let uploadService: any;
const submissionJsonPatchOperationsServiceStub = new SubmissionJsonPatchOperationsServiceStub();
const submissionId = mockSubmissionId; const submissionId = mockSubmissionId;
const sectionId = 'upload'; const sectionId = 'upload';
const collectionId = mockSubmissionCollectionId; const collectionId = mockSubmissionCollectionId;
@@ -48,6 +71,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
const fileIndex = '0'; const fileIndex = '0';
const fileId = '123456-test-upload'; const fileId = '123456-test-upload';
const fileData: any = mockUploadFiles[0]; const fileData: any = mockUploadFiles[0];
const pathCombiner = new JsonPatchOperationPathCombiner('sections', sectionId, 'files', fileIndex);
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -66,9 +90,15 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
providers: [ providers: [
{ provide: FormService, useValue: getMockFormService() }, { provide: FormService, useValue: getMockFormService() },
{ provide: SubmissionService, useClass: SubmissionServiceStub }, { provide: SubmissionService, useClass: SubmissionServiceStub },
{ provide: SubmissionJsonPatchOperationsService, useValue: submissionJsonPatchOperationsServiceStub },
{ provide: JsonPatchOperationsBuilder, useValue: jsonPatchOpBuilder },
{ provide: SectionUploadService, useValue: getMockSectionUploadService() },
FormBuilderService, FormBuilderService,
ChangeDetectorRef, ChangeDetectorRef,
SubmissionSectionUploadFileEditComponent SubmissionSectionUploadFileEditComponent,
NgbModal,
NgbActiveModal,
FormComponent,
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).compileComponents().then(); }).compileComponents().then();
@@ -114,6 +144,10 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
compAsAny = comp; compAsAny = comp;
submissionServiceStub = TestBed.inject(SubmissionService as any); submissionServiceStub = TestBed.inject(SubmissionService as any);
formbuilderService = TestBed.inject(FormBuilderService); formbuilderService = TestBed.inject(FormBuilderService);
operationsBuilder = TestBed.inject(JsonPatchOperationsBuilder);
operationsService = TestBed.inject(SubmissionJsonPatchOperationsService);
formService = TestBed.inject(FormService);
uploadService = TestBed.inject(SectionUploadService);
comp.submissionId = submissionId; comp.submissionId = submissionId;
comp.collectionId = collectionId; comp.collectionId = collectionId;
@@ -123,6 +157,9 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
comp.fileIndex = fileIndex; comp.fileIndex = fileIndex;
comp.fileId = fileId; comp.fileId = fileId;
comp.configMetadataForm = configMetadataForm; comp.configMetadataForm = configMetadataForm;
comp.formMetadata = formMetadataMock;
formService.isValid.and.returnValue(of(true));
}); });
afterEach(() => { afterEach(() => {
@@ -135,7 +172,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
comp.fileData = fileData; comp.fileData = fileData;
comp.formId = 'testFileForm'; comp.formId = 'testFileForm';
comp.ngOnChanges(); comp.ngOnInit();
expect(comp.formModel).toBeDefined(); expect(comp.formModel).toBeDefined();
expect(comp.formModel.length).toBe(2); expect(comp.formModel.length).toBe(2);
@@ -165,7 +202,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
comp.fileData = fileData; comp.fileData = fileData;
comp.formId = 'testFileForm'; comp.formId = 'testFileForm';
comp.ngOnChanges(); comp.ngOnInit();
const model: DynamicSelectModel<string> = formbuilderService.findById('name', comp.formModel, 0); const model: DynamicSelectModel<string> = formbuilderService.findById('name', comp.formModel, 0);
const formGroup = formbuilderService.createFormGroup(comp.formModel); const formGroup = formbuilderService.createFormGroup(comp.formModel);
@@ -186,6 +223,82 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
comp.setOptions(model, control); comp.setOptions(model, control);
expect(formbuilderService.findById).toHaveBeenCalledWith('startDate', (model.parent as DynamicFormArrayGroupModel).group); expect(formbuilderService.findById).toHaveBeenCalledWith('startDate', (model.parent as DynamicFormArrayGroupModel).group);
}); });
it('should retrieve Value From Field properly', () => {
let field;
expect(compAsAny.retrieveValueFromField(field)).toBeUndefined();
field = new FormFieldMetadataValueObject('test');
expect(compAsAny.retrieveValueFromField(field)).toBe('test');
field = [new FormFieldMetadataValueObject('test')];
expect(compAsAny.retrieveValueFromField(field)).toBe('test');
});
it('should save Bitstream File data properly when form is valid', fakeAsync(() => {
compAsAny.formRef = {formGroup: null};
compAsAny.fileData = fileData;
compAsAny.pathCombiner = pathCombiner;
formService.validateAllFormFields.and.callFake(() => null);
formService.isValid.and.returnValue(of(true));
formService.getFormData.and.returnValue(of(mockFileFormData));
const response = [
Object.assign(mockSubmissionObject, {
sections: {
upload: {
files: mockUploadFiles
}
}
})
];
operationsService.jsonPatchByResourceID.and.returnValue(of(response));
const accessConditionsToSave = [
{ name: 'openaccess' },
{ name: 'lease', endDate: dateToISOFormat('2019-01-16T00:00:00Z') },
{ name: 'embargo', startDate: dateToISOFormat('2019-01-16T00:00:00Z') },
];
comp.saveBitstreamData();
tick();
let path = 'metadata/dc.title';
expect(operationsBuilder.add).toHaveBeenCalledWith(
pathCombiner.getPath(path),
mockFileFormData.metadata['dc.title'],
true
);
path = 'metadata/dc.description';
expect(operationsBuilder.add).toHaveBeenCalledWith(
pathCombiner.getPath(path),
mockFileFormData.metadata['dc.description'],
true
);
path = 'accessConditions';
expect(operationsBuilder.add).toHaveBeenCalledWith(
pathCombiner.getPath(path),
accessConditionsToSave,
true
);
expect(uploadService.updateFileData).toHaveBeenCalledWith(submissionId, sectionId, mockUploadFiles[0].uuid, mockUploadFiles[0]);
}));
it('should not save Bitstream File data properly when form is not valid', fakeAsync(() => {
compAsAny.formRef = {formGroup: null};
compAsAny.pathCombiner = pathCombiner;
formService.validateAllFormFields.and.callFake(() => null);
formService.isValid.and.returnValue(of(false));
comp.saveBitstreamData();
tick();
expect(uploadService.updateFileData).not.toHaveBeenCalled();
}));
}); });
}); });

View File

@@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, Input, OnChanges, ViewChild } from '@angular/core'; import { ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
import { import {
@@ -32,13 +32,23 @@ import {
BITSTREAM_METADATA_FORM_GROUP_LAYOUT BITSTREAM_METADATA_FORM_GROUP_LAYOUT
} from './section-upload-file-edit.model'; } from './section-upload-file-edit.model';
import { POLICY_DEFAULT_WITH_LIST } from '../../section-upload.component'; import { POLICY_DEFAULT_WITH_LIST } from '../../section-upload.component';
import { isNotEmpty } from '../../../../../shared/empty.util'; import { hasNoValue, hasValue, isNotEmpty, isNotNull } from '../../../../../shared/empty.util';
import { SubmissionFormsModel } from '../../../../../core/config/models/config-submission-forms.model'; import { SubmissionFormsModel } from '../../../../../core/config/models/config-submission-forms.model';
import { FormFieldModel } from '../../../../../shared/form/builder/models/form-field.model'; import { FormFieldModel } from '../../../../../shared/form/builder/models/form-field.model';
import { AccessConditionOption } from '../../../../../core/config/models/config-access-condition-option.model'; import { AccessConditionOption } from '../../../../../core/config/models/config-access-condition-option.model';
import { SubmissionService } from '../../../../submission.service'; import { SubmissionService } from '../../../../submission.service';
import { FormService } from '../../../../../shared/form/form.service'; import { FormService } from '../../../../../shared/form/form.service';
import { FormComponent } from '../../../../../shared/form/form.component'; import { FormComponent } from '../../../../../shared/form/form.component';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { filter, mergeMap, take } from 'rxjs/operators';
import { dateToISOFormat } from '../../../../../shared/date.util';
import { SubmissionObject } from '../../../../../core/submission/models/submission-object.model';
import { WorkspaceitemSectionUploadObject } from '../../../../../core/submission/models/workspaceitem-section-upload.model';
import { JsonPatchOperationsBuilder } from '../../../../../core/json-patch/builder/json-patch-operations-builder';
import { SubmissionJsonPatchOperationsService } from '../../../../../core/submission/submission-json-patch-operations.service';
import { JsonPatchOperationPathCombiner } from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner';
import { SectionUploadService } from '../../section-upload.service';
import { Subscription } from 'rxjs';
/** /**
* This component represents the edit form for bitstream * This component represents the edit form for bitstream
@@ -48,105 +58,246 @@ import { FormComponent } from '../../../../../shared/form/form.component';
styleUrls: ['./section-upload-file-edit.component.scss'], styleUrls: ['./section-upload-file-edit.component.scss'],
templateUrl: './section-upload-file-edit.component.html', templateUrl: './section-upload-file-edit.component.html',
}) })
export class SubmissionSectionUploadFileEditComponent implements OnChanges { export class SubmissionSectionUploadFileEditComponent implements OnInit {
/**
* The list of available access condition
* @type {Array}
*/
@Input() availableAccessConditionOptions: any[];
/**
* The submission id
* @type {string}
*/
@Input() collectionId: string;
/**
* Define if collection access conditions policy type :
* POLICY_DEFAULT_NO_LIST : is not possible to define additional access group/s for the single file
* POLICY_DEFAULT_WITH_LIST : is possible to define additional access group/s for the single file
* @type {number}
*/
@Input() collectionPolicyType: number;
/**
* The configuration for the bitstream's metadata form
* @type {SubmissionFormsModel}
*/
@Input() configMetadataForm: SubmissionFormsModel;
/**
* The bitstream's metadata data
* @type {WorkspaceitemSectionUploadFileObject}
*/
@Input() fileData: WorkspaceitemSectionUploadFileObject;
/**
* The bitstream id
* @type {string}
*/
@Input() fileId: string;
/**
* The bitstream array key
* @type {string}
*/
@Input() fileIndex: string;
/**
* The form id
* @type {string}
*/
@Input() formId: string;
/**
* The section id
* @type {string}
*/
@Input() sectionId: string;
/**
* The submission id
* @type {string}
*/
@Input() submissionId: string;
/**
* The form model
* @type {DynamicFormControlModel[]}
*/
public formModel: DynamicFormControlModel[];
/** /**
* The FormComponent reference * The FormComponent reference
*/ */
@ViewChild('formRef') public formRef: FormComponent; @ViewChild('formRef') public formRef: FormComponent;
/**
* The list of available access condition
* @type {Array}
*/
public availableAccessConditionOptions: any[];
/**
* The submission id
* @type {string}
*/
public collectionId: string;
/**
* Define if collection access conditions policy type :
* POLICY_DEFAULT_NO_LIST : is not possible to define additional access group/s for the single file
* POLICY_DEFAULT_WITH_LIST : is possible to define additional access group/s for the single file
* @type {number}
*/
public collectionPolicyType: number;
/**
* The configuration for the bitstream's metadata form
* @type {SubmissionFormsModel}
*/
public configMetadataForm: SubmissionFormsModel;
/**
* The bitstream's metadata data
* @type {WorkspaceitemSectionUploadFileObject}
*/
public fileData: WorkspaceitemSectionUploadFileObject;
/**
* The bitstream id
* @type {string}
*/
public fileId: string;
/**
* The bitstream array key
* @type {string}
*/
public fileIndex: string;
/**
* The form id
* @type {string}
*/
public formId: string;
/**
* The section id
* @type {string}
*/
public sectionId: string;
/**
* The submission id
* @type {string}
*/
public submissionId: string;
/**
* The list of all available metadata
*/
formMetadata: string[] = [];
/**
* The form model
* @type {DynamicFormControlModel[]}
*/
formModel: DynamicFormControlModel[];
/**
* When `true` form controls are deactivated
*/
isSaving = false;
/**
* The [JsonPatchOperationPathCombiner] object
* @type {JsonPatchOperationPathCombiner}
*/
protected pathCombiner: JsonPatchOperationPathCombiner;
protected subscriptions: Subscription[] = [];
/** /**
* Initialize instance variables * Initialize instance variables
* *
* @param activeModal
* @param {ChangeDetectorRef} cdr * @param {ChangeDetectorRef} cdr
* @param {FormBuilderService} formBuilderService * @param {FormBuilderService} formBuilderService
* @param {FormService} formService * @param {FormService} formService
* @param {SubmissionService} submissionService * @param {SubmissionService} submissionService
* @param {JsonPatchOperationsBuilder} operationsBuilder
* @param {SubmissionJsonPatchOperationsService} operationsService
* @param {SectionUploadService} uploadService
*/ */
constructor(private cdr: ChangeDetectorRef, constructor(
protected activeModal: NgbActiveModal,
private cdr: ChangeDetectorRef,
private formBuilderService: FormBuilderService, private formBuilderService: FormBuilderService,
private formService: FormService, private formService: FormService,
private submissionService: SubmissionService) { private submissionService: SubmissionService,
private operationsBuilder: JsonPatchOperationsBuilder,
private operationsService: SubmissionJsonPatchOperationsService,
private uploadService: SectionUploadService,
) {
}
/**
* Initialize form model values
*
* @param formModel
* The form model
*/
public initModelData(formModel: DynamicFormControlModel[]) {
this.fileData.accessConditions.forEach((accessCondition, index) => {
Array.of('name', 'startDate', 'endDate')
.filter((key) => accessCondition.hasOwnProperty(key) && isNotEmpty(accessCondition[key]))
.forEach((key) => {
const metadataModel: any = this.formBuilderService.findById(key, formModel, index);
if (metadataModel) {
if (metadataModel.type === DYNAMIC_FORM_CONTROL_TYPE_DATEPICKER) {
const date = new Date(accessCondition[key]);
metadataModel.value = {
year: date.getUTCFullYear(),
month: date.getUTCMonth() + 1,
day: date.getUTCDate()
};
} else {
metadataModel.value = accessCondition[key];
}
}
});
});
}
/**
* Dispatch form model update when changing an access condition
*
* @param event
* The event emitted
*/
onChange(event: DynamicFormControlEvent) {
if (event.model.id === 'name') {
this.setOptions(event.model, event.control);
}
}
onModalClose() {
this.activeModal.dismiss();
}
onSubmit() {
this.isSaving = true;
this.saveBitstreamData();
}
/**
* Update `startDate`, 'groupUUID' and 'endDate' model
*
* @param model
* The [[DynamicFormControlModel]] object
* @param control
* The [[FormControl]] object
*/
public setOptions(model: DynamicFormControlModel, control: FormControl) {
let accessCondition: AccessConditionOption = null;
this.availableAccessConditionOptions.filter((element) => element.name === control.value)
.forEach((element) => accessCondition = element );
if (isNotEmpty(accessCondition)) {
const showGroups: boolean = accessCondition.hasStartDate === true || accessCondition.hasEndDate === true;
const startDateControl: FormControl = control.parent.get('startDate') as FormControl;
const endDateControl: FormControl = control.parent.get('endDate') as FormControl;
// Clear previous state
startDateControl?.markAsUntouched();
endDateControl?.markAsUntouched();
startDateControl?.setValue(null);
control.parent.markAsDirty();
endDateControl?.setValue(null);
if (showGroups) {
if (accessCondition.hasStartDate) {
const startDateModel = this.formBuilderService.findById(
'startDate',
(model.parent as DynamicFormArrayGroupModel).group) as DynamicDateControlModel;
const min = new Date(accessCondition.maxStartDate);
startDateModel.max = {
year: min.getUTCFullYear(),
month: min.getUTCMonth() + 1,
day: min.getUTCDate()
};
}
if (accessCondition.hasEndDate) {
const endDateModel = this.formBuilderService.findById(
'endDate',
(model.parent as DynamicFormArrayGroupModel).group) as DynamicDateControlModel;
const max = new Date(accessCondition.maxEndDate);
endDateModel.max = {
year: max.getUTCFullYear(),
month: max.getUTCMonth() + 1,
day: max.getUTCDate()
};
}
}
}
} }
/** /**
* Dispatch form model init * Dispatch form model init
*/ */
ngOnChanges() { ngOnInit() {
if (this.fileData && this.formId) { if (this.fileData && this.formId) {
this.formModel = this.buildFileEditForm(); this.formModel = this.buildFileEditForm();
this.cdr.detectChanges(); this.cdr.detectChanges();
} }
} }
ngOnDestroy(): void {
this.unsubscribeAll();
}
protected retrieveValueFromField(field: any) {
const temp = Array.isArray(field) ? field[0] : field;
return (temp) ? temp.value : undefined;
}
/** /**
* Initialize form model * Initialize form model
*/ */
@@ -193,17 +344,17 @@ export class SubmissionSectionUploadFileEditComponent implements OnChanges {
const showEnd: boolean = condition.hasEndDate === true; const showEnd: boolean = condition.hasEndDate === true;
const showGroups: boolean = showStart || showEnd; const showGroups: boolean = showStart || showEnd;
if (showStart) { if (showStart) {
hasStart.push({ id: 'name', value: condition.name }); hasStart.push({id: 'name', value: condition.name});
} }
if (showEnd) { if (showEnd) {
hasEnd.push({ id: 'name', value: condition.name }); hasEnd.push({id: 'name', value: condition.name});
} }
if (showGroups) { if (showGroups) {
hasGroups.push({ id: 'name', value: condition.name }); hasGroups.push({id: 'name', value: condition.name});
} }
}); });
const confStart = { relations: [{ match: MATCH_ENABLED, operator: OR_OPERATOR, when: hasStart }] }; const confStart = {relations: [{match: MATCH_ENABLED, operator: OR_OPERATOR, when: hasStart}]};
const confEnd = { relations: [{ match: MATCH_ENABLED, operator: OR_OPERATOR, when: hasEnd }] }; const confEnd = {relations: [{match: MATCH_ENABLED, operator: OR_OPERATOR, when: hasEnd}]};
accessConditionsArrayConfig.groupFactory = () => { accessConditionsArrayConfig.groupFactory = () => {
const type = new DynamicSelectModel(accessConditionTypeModelConfig, BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT); const type = new DynamicSelectModel(accessConditionTypeModelConfig, BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT);
@@ -213,7 +364,9 @@ export class SubmissionSectionUploadFileEditComponent implements OnChanges {
const startDate = new DynamicDatePickerModel(startDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_LAYOUT); const startDate = new DynamicDatePickerModel(startDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_LAYOUT);
const endDate = new DynamicDatePickerModel(endDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_LAYOUT); const endDate = new DynamicDatePickerModel(endDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_LAYOUT);
const accessConditionGroupConfig = Object.assign({}, BITSTREAM_ACCESS_CONDITION_GROUP_CONFIG); const accessConditionGroupConfig = Object.assign({}, BITSTREAM_ACCESS_CONDITION_GROUP_CONFIG);
accessConditionGroupConfig.group = [type, startDate, endDate]; accessConditionGroupConfig.group = [type];
if (hasStart.length > 0) { accessConditionGroupConfig.group.push(startDate); }
if (hasEnd.length > 0) { accessConditionGroupConfig.group.push(endDate); }
return [new DynamicFormGroupModel(accessConditionGroupConfig, BITSTREAM_ACCESS_CONDITION_GROUP_LAYOUT)]; return [new DynamicFormGroupModel(accessConditionGroupConfig, BITSTREAM_ACCESS_CONDITION_GROUP_LAYOUT)];
}; };
@@ -229,98 +382,95 @@ export class SubmissionSectionUploadFileEditComponent implements OnChanges {
} }
/** /**
* Initialize form model values * Save bitstream metadata
*
* @param formModel
* The form model
*/ */
public initModelData(formModel: DynamicFormControlModel[]) { saveBitstreamData() {
this.fileData.accessConditions.forEach((accessCondition, index) => { // validate form
Array.of('name', 'startDate', 'endDate') this.formService.validateAllFormFields(this.formRef.formGroup);
.filter((key) => accessCondition.hasOwnProperty(key) && isNotEmpty(accessCondition[key])) const saveBitstreamDataSubscription = this.formService.isValid(this.formId).pipe(
take(1),
filter((isValid) => isValid),
mergeMap(() => this.formService.getFormData(this.formId)),
take(1),
mergeMap((formData: any) => {
// collect bitstream metadata
Object.keys((formData.metadata))
.filter((key) => isNotEmpty(formData.metadata[key]))
.forEach((key) => { .forEach((key) => {
const metadataModel: any = this.formBuilderService.findById(key, formModel, index); const metadataKey = key.replace(/_/g, '.');
if (metadataModel) { const path = `metadata/${metadataKey}`;
if (metadataModel.type === DYNAMIC_FORM_CONTROL_TYPE_DATEPICKER) { this.operationsBuilder.add(this.pathCombiner.getPath(path), formData.metadata[key], true);
const date = new Date(accessCondition[key]); });
metadataModel.value = { Object.keys((this.fileData.metadata))
year: date.getUTCFullYear(), .filter((key) => isNotEmpty(this.fileData.metadata[key]))
month: date.getUTCMonth() + 1, .filter((key) => hasNoValue(formData.metadata[key]))
day: date.getUTCDate() .filter((key) => this.formMetadata.includes(key))
}; .forEach((key) => {
} else { const metadataKey = key.replace(/_/g, '.');
metadataModel.value = accessCondition[key]; const path = `metadata/${metadataKey}`;
this.operationsBuilder.remove(this.pathCombiner.getPath(path));
});
const accessConditionsToSave = [];
formData.accessConditions
.map((accessConditions) => accessConditions.accessConditionGroup)
.filter((accessCondition) => isNotEmpty(accessCondition))
.forEach((accessCondition) => {
let accessConditionOpt;
this.availableAccessConditionOptions
.filter((element) => isNotNull(accessCondition.name) && element.name === accessCondition.name[0].value)
.forEach((element) => accessConditionOpt = element);
if (accessConditionOpt) {
const currentAccessCondition = Object.assign({}, accessCondition);
currentAccessCondition.name = this.retrieveValueFromField(accessCondition.name);
/* When start and end date fields are deactivated, their values may be still present in formData,
therefore it is necessary to delete them if they're not allowed by the current access condition option. */
if (!accessConditionOpt.hasStartDate) {
delete currentAccessCondition.startDate;
} else if (accessCondition.startDate) {
const startDate = this.retrieveValueFromField(accessCondition.startDate);
currentAccessCondition.startDate = dateToISOFormat(startDate);
} }
if (!accessConditionOpt.hasEndDate) {
delete currentAccessCondition.endDate;
} else if (accessCondition.endDate) {
const endDate = this.retrieveValueFromField(accessCondition.endDate);
currentAccessCondition.endDate = dateToISOFormat(endDate);
}
accessConditionsToSave.push(currentAccessCondition);
} }
}); });
if (isNotEmpty(accessConditionsToSave)) {
this.operationsBuilder.add(this.pathCombiner.getPath('accessConditions'), accessConditionsToSave, true);
}
// dispatch a PATCH request to save metadata
return this.operationsService.jsonPatchByResourceID(
this.submissionService.getSubmissionObjectLinkName(),
this.submissionId,
this.pathCombiner.rootElement,
this.pathCombiner.subRootElement);
})
).subscribe((result: SubmissionObject[]) => {
if (result[0].sections[this.sectionId]) {
const uploadSection = (result[0].sections[this.sectionId] as WorkspaceitemSectionUploadObject);
Object.keys(uploadSection.files)
.filter((key) => uploadSection.files[key].uuid === this.fileId)
.forEach((key) => this.uploadService.updateFileData(
this.submissionId, this.sectionId, this.fileId, uploadSection.files[key])
);
}
this.isSaving = false;
this.activeModal.close();
}); });
this.subscriptions.push(saveBitstreamDataSubscription);
} }
/** private unsubscribeAll() {
* Dispatch form model update when changing an access condition this.subscriptions.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
*
* @param event
* The event emitted
*/
public onChange(event: DynamicFormControlEvent) {
if (event.model.id === 'name') {
this.setOptions(event.model, event.control);
}
}
/**
* Update `startDate`, 'groupUUID' and 'endDate' model
*
* @param model
* The [[DynamicFormControlModel]] object
* @param control
* The [[FormControl]] object
*/
public setOptions(model: DynamicFormControlModel, control: FormControl) {
let accessCondition: AccessConditionOption = null;
this.availableAccessConditionOptions.filter((element) => element.name === control.value)
.forEach((element) => accessCondition = element);
if (isNotEmpty(accessCondition)) {
const showGroups: boolean = accessCondition.hasStartDate === true || accessCondition.hasEndDate === true;
const startDateControl: FormControl = control.parent.get('startDate') as FormControl;
const endDateControl: FormControl = control.parent.get('endDate') as FormControl;
// Clear previous state
startDateControl.markAsUntouched();
endDateControl.markAsUntouched();
startDateControl.setValue(null);
control.parent.markAsDirty();
endDateControl.setValue(null);
if (showGroups) {
if (accessCondition.hasStartDate) {
const startDateModel = this.formBuilderService.findById(
'startDate',
(model.parent as DynamicFormArrayGroupModel).group) as DynamicDateControlModel;
const min = new Date(accessCondition.maxStartDate);
startDateModel.max = {
year: min.getUTCFullYear(),
month: min.getUTCMonth() + 1,
day: min.getUTCDate()
};
}
if (accessCondition.hasEndDate) {
const endDateModel = this.formBuilderService.findById(
'endDate',
(model.parent as DynamicFormArrayGroupModel).group) as DynamicDateControlModel;
const max = new Date(accessCondition.maxEndDate);
endDateModel.max = {
year: max.getUTCFullYear(),
month: max.getUTCMonth() + 1,
day: max.getUTCDate()
};
}
}
}
} }
} }

View File

@@ -8,15 +8,15 @@
<div class="float-left w-75"> <div class="float-left w-75">
<h3>{{fileName}} <span class="text-muted">({{fileData?.sizeBytes | dsFileSize}})</span></h3> <h3>{{fileName}} <span class="text-muted">({{fileData?.sizeBytes | dsFileSize}})</span></h3>
</div> </div>
<div class="float-right w-15" [class.sticky-buttons]="!readMode"> <div class="float-right w-15">
<ng-container *ngIf="readMode"> <ng-container>
<ds-file-download-link [cssClasses]="'btn btn-link-focus'" [isBlank]="true" [bitstream]="getBitstream()" [enableRequestACopy]="false"> <ds-file-download-link [cssClasses]="'btn btn-link-focus'" [isBlank]="true" [bitstream]="getBitstream()" [enableRequestACopy]="false">
<i class="fa fa-download fa-2x text-normal" aria-hidden="true"></i> <i class="fa fa-download fa-2x text-normal" aria-hidden="true"></i>
</ds-file-download-link> </ds-file-download-link>
<button class="btn btn-link-focus" <button class="btn btn-link-focus"
[attr.aria-label]="'submission.sections.upload.edit.title' | translate" [attr.aria-label]="'submission.sections.upload.edit.title' | translate"
title="{{ 'submission.sections.upload.edit.title' | translate }}" title="{{ 'submission.sections.upload.edit.title' | translate }}"
(click)="$event.preventDefault();switchMode();"> (click)="$event.preventDefault();editBitstreamData();">
<i class="fa fa-edit fa-2x text-normal"></i> <i class="fa fa-edit fa-2x text-normal"></i>
</button> </button>
<button class="btn btn-link-focus" <button class="btn btn-link-focus"
@@ -28,40 +28,9 @@
<i *ngIf="!(processingDelete$ | async)" class="fa fa-trash fa-2x text-danger"></i> <i *ngIf="!(processingDelete$ | async)" class="fa fa-trash fa-2x text-danger"></i>
</button> </button>
</ng-container> </ng-container>
<ng-container *ngIf="!readMode">
<button class="btn btn-link-focus"
[attr.aria-label]="'submission.sections.upload.save-metadata' | translate"
title="{{ 'submission.sections.upload.save-metadata' | translate }}"
(click)="saveBitstreamData($event);">
<i class="fa fa-save fa-2x text-success"></i>
</button>
<button class="btn btn-link-focus"
[attr.aria-label]="'submission.sections.upload.undo' | translate"
title="{{ 'submission.sections.upload.undo' | translate }}"
(click)="$event.preventDefault();switchMode();"><i class="fa fa-ban fa-2x text-warning"></i></button>
<button class="btn btn-link-focus"
[attr.aria-label]="'submission.sections.upload.delete.confirm.title' | translate"
title="{{ 'submission.sections.upload.delete.confirm.title' | translate }}"
[disabled]="(processingDelete$ | async)"
(click)="$event.preventDefault();confirmDelete(content);">
<i *ngIf="(processingDelete$ | async)" class="fas fa-circle-notch fa-spin fa-2x text-danger"></i>
<i *ngIf="!(processingDelete$ | async)" class="fa fa-trash fa-2x text-danger"></i>
</button>
</ng-container>
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
<ds-submission-section-upload-file-view *ngIf="readMode" <ds-submission-section-upload-file-view [fileData]="fileData"></ds-submission-section-upload-file-view>
[fileData]="fileData"></ds-submission-section-upload-file-view>
<ds-submission-section-upload-file-edit *ngIf="!readMode"
[availableAccessConditionOptions]="availableAccessConditionOptions"
[collectionId]="collectionId"
[collectionPolicyType]="collectionPolicyType"
[configMetadataForm]="configMetadataForm"
[fileData]="fileData"
[fileId]="fileId"
[fileIndex]="fileIndex"
[formId]="formId"
[sectionId]="sectionId"></ds-submission-section-upload-file-edit>
</div> </div>
</div> </div>
</ng-container> </ng-container>

View File

@@ -1,6 +0,0 @@
.sticky-buttons {
position: sticky;
top: calc(var(--bs-dropdown-item-padding-x) * 3);
z-index: var(--ds-submission-footer-z-index);
background-color: rgba(255, 255, 255, .97);
}

View File

@@ -1,9 +1,9 @@
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core'; import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
import { BrowserModule, By } from '@angular/platform-browser'; import { BrowserModule, By } from '@angular/platform-browser';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { of as observableOf } from 'rxjs'; import { of, of as observableOf } from 'rxjs';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { FormService } from '../../../../shared/form/form.service'; import { FormService } from '../../../../shared/form/form.service';
@@ -17,10 +17,8 @@ import { SubmissionJsonPatchOperationsService } from '../../../../core/submissio
import { SubmissionSectionUploadFileComponent } from './section-upload-file.component'; import { SubmissionSectionUploadFileComponent } from './section-upload-file.component';
import { SubmissionServiceStub } from '../../../../shared/testing/submission-service.stub'; import { SubmissionServiceStub } from '../../../../shared/testing/submission-service.stub';
import { import {
mockFileFormData,
mockSubmissionCollectionId, mockSubmissionCollectionId,
mockSubmissionId, mockSubmissionId,
mockSubmissionObject,
mockUploadConfigResponse, mockUploadConfigResponse,
mockUploadFiles mockUploadFiles
} from '../../../../shared/mocks/submission.mock'; } from '../../../../shared/mocks/submission.mock';
@@ -32,10 +30,19 @@ import { FileSizePipe } from '../../../../shared/utils/file-size-pipe';
import { POLICY_DEFAULT_WITH_LIST } from '../section-upload.component'; import { POLICY_DEFAULT_WITH_LIST } from '../section-upload.component';
import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/builder/json-patch-operation-path-combiner'; import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/builder/json-patch-operation-path-combiner';
import { getMockSectionUploadService } from '../../../../shared/mocks/section-upload.service.mock'; import { getMockSectionUploadService } from '../../../../shared/mocks/section-upload.service.mock';
import { FormFieldMetadataValueObject } from '../../../../shared/form/builder/models/form-field-metadata-value.model';
import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component'; import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component';
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
import { dateToISOFormat } from '../../../../shared/date.util';
const configMetadataFormMock = {
rows: [{
fields: [{
selectableMetadata: [
{metadata: 'dc.title', label: null, closed: false},
{metadata: 'dc.description', label: null, closed: false}
]
}]
}]
};
describe('SubmissionSectionUploadFileComponent test suite', () => { describe('SubmissionSectionUploadFileComponent test suite', () => {
@@ -117,6 +124,7 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>; testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
testComp = testFixture.componentInstance; testComp = testFixture.componentInstance;
}); });
afterEach(() => { afterEach(() => {
@@ -124,9 +132,7 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
}); });
it('should create SubmissionSectionUploadFileComponent', inject([SubmissionSectionUploadFileComponent], (app: SubmissionSectionUploadFileComponent) => { it('should create SubmissionSectionUploadFileComponent', inject([SubmissionSectionUploadFileComponent], (app: SubmissionSectionUploadFileComponent) => {
expect(app).toBeDefined(); expect(app).toBeDefined();
})); }));
}); });
@@ -135,6 +141,7 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
fixture = TestBed.createComponent(SubmissionSectionUploadFileComponent); fixture = TestBed.createComponent(SubmissionSectionUploadFileComponent);
comp = fixture.componentInstance; comp = fixture.componentInstance;
compAsAny = comp; compAsAny = comp;
compAsAny.configMetadataForm = configMetadataFormMock;
submissionServiceStub = TestBed.inject(SubmissionService as any); submissionServiceStub = TestBed.inject(SubmissionService as any);
uploadService = TestBed.inject(SectionUploadService); uploadService = TestBed.inject(SectionUploadService);
formService = TestBed.inject(FormService); formService = TestBed.inject(FormService);
@@ -210,96 +217,20 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
pathCombiner.subRootElement); pathCombiner.subRootElement);
}); });
it('should save Bitstream File data properly when form is valid', fakeAsync(() => { it('should open edit modal when edit button is clicked', () => {
compAsAny.fileEditComp = TestBed.inject(SubmissionSectionUploadFileEditComponent); spyOn(compAsAny, 'editBitstreamData').and.callThrough();
compAsAny.fileEditComp.formRef = {formGroup: null}; comp.fileData = fileData;
compAsAny.pathCombiner = pathCombiner;
const event = new Event('click', null);
spyOn(comp, 'switchMode');
formService.validateAllFormFields.and.callFake(() => null);
formService.isValid.and.returnValue(observableOf(true));
formService.getFormData.and.returnValue(observableOf(mockFileFormData));
const response = [ fixture.detectChanges();
Object.assign(mockSubmissionObject, {
sections: {
upload: {
files: mockUploadFiles
}
}
})
];
operationsService.jsonPatchByResourceID.and.returnValue(observableOf(response));
const accessConditionsToSave = [ const modalBtn = fixture.debugElement.query(By.css('.fa-edit '));
{ name: 'openaccess' },
{ name: 'lease', endDate: dateToISOFormat('2019-01-16T00:00:00Z') },
{ name: 'embargo', startDate: dateToISOFormat('2019-01-16T00:00:00Z') },
];
comp.saveBitstreamData(event);
tick();
let path = 'metadata/dc.title'; modalBtn.nativeElement.click();
expect(operationsBuilder.add).toHaveBeenCalledWith( fixture.detectChanges();
pathCombiner.getPath(path),
mockFileFormData.metadata['dc.title'],
true
);
path = 'metadata/dc.description'; expect(compAsAny.editBitstreamData).toHaveBeenCalled();
expect(operationsBuilder.add).toHaveBeenCalledWith(
pathCombiner.getPath(path),
mockFileFormData.metadata['dc.description'],
true
);
path = 'accessConditions';
expect(operationsBuilder.add).toHaveBeenCalledWith(
pathCombiner.getPath(path),
accessConditionsToSave,
true
);
expect(comp.switchMode).toHaveBeenCalled();
expect(uploadService.updateFileData).toHaveBeenCalledWith(submissionId, sectionId, mockUploadFiles[0].uuid, mockUploadFiles[0]);
}));
it('should not save Bitstream File data properly when form is not valid', fakeAsync(() => {
compAsAny.fileEditComp = TestBed.inject(SubmissionSectionUploadFileEditComponent);
compAsAny.fileEditComp.formRef = {formGroup: null};
compAsAny.pathCombiner = pathCombiner;
const event = new Event('click', null);
spyOn(comp, 'switchMode');
formService.validateAllFormFields.and.callFake(() => null);
formService.isValid.and.returnValue(observableOf(false));
expect(comp.switchMode).not.toHaveBeenCalled();
expect(uploadService.updateFileData).not.toHaveBeenCalled();
}));
it('should retrieve Value From Field properly', () => {
let field;
expect(compAsAny.retrieveValueFromField(field)).toBeUndefined();
field = new FormFieldMetadataValueObject('test');
expect(compAsAny.retrieveValueFromField(field)).toBe('test');
field = [new FormFieldMetadataValueObject('test')];
expect(compAsAny.retrieveValueFromField(field)).toBe('test');
}); });
it('should switch read mode', () => {
comp.readMode = false;
comp.switchMode();
expect(comp.readMode).toBeTruthy();
comp.switchMode();
expect(comp.readMode).toBeFalsy();
});
}); });
}); });
@@ -314,7 +245,7 @@ class TestComponent {
availableAccessConditionOptions; availableAccessConditionOptions;
collectionId = mockSubmissionCollectionId; collectionId = mockSubmissionCollectionId;
collectionPolicyType; collectionPolicyType;
configMetadataForm$; configMetadataForm$ = of(configMetadataFormMock);
fileIndexes = []; fileIndexes = [];
fileList = []; fileList = [];
fileNames = []; fileNames = [];

View File

@@ -1,25 +1,23 @@
import { ChangeDetectorRef, Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core'; import { ChangeDetectorRef, Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core';
import { BehaviorSubject, Subscription } from 'rxjs'; import { BehaviorSubject, Subscription } from 'rxjs';
import { filter, mergeMap, take } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { DynamicFormControlModel, } from '@ng-dynamic-forms/core'; import { DynamicFormControlModel, } from '@ng-dynamic-forms/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { SectionUploadService } from '../section-upload.service'; import { SectionUploadService } from '../section-upload.service';
import { isNotEmpty, isNotNull, isNotUndefined } from '../../../../shared/empty.util'; import { hasValue, isNotUndefined } from '../../../../shared/empty.util';
import { FormService } from '../../../../shared/form/form.service'; import { FormService } from '../../../../shared/form/form.service';
import { JsonPatchOperationsBuilder } from '../../../../core/json-patch/builder/json-patch-operations-builder'; import { JsonPatchOperationsBuilder } from '../../../../core/json-patch/builder/json-patch-operations-builder';
import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/builder/json-patch-operation-path-combiner'; import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/builder/json-patch-operation-path-combiner';
import { WorkspaceitemSectionUploadFileObject } from '../../../../core/submission/models/workspaceitem-section-upload-file.model'; import { WorkspaceitemSectionUploadFileObject } from '../../../../core/submission/models/workspaceitem-section-upload-file.model';
import { SubmissionFormsModel } from '../../../../core/config/models/config-submission-forms.model'; import { SubmissionFormsModel } from '../../../../core/config/models/config-submission-forms.model';
import { dateToISOFormat } from '../../../../shared/date.util';
import { SubmissionService } from '../../../submission.service'; import { SubmissionService } from '../../../submission.service';
import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service'; import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service';
import { SubmissionJsonPatchOperationsService } from '../../../../core/submission/submission-json-patch-operations.service'; import { SubmissionJsonPatchOperationsService } from '../../../../core/submission/submission-json-patch-operations.service';
import { SubmissionObject } from '../../../../core/submission/models/submission-object.model';
import { WorkspaceitemSectionUploadObject } from '../../../../core/submission/models/workspaceitem-section-upload.model';
import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component'; import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component';
import { Bitstream } from '../../../../core/shared/bitstream.model'; import { Bitstream } from '../../../../core/shared/bitstream.model';
import { NgbModalOptions } from '@ng-bootstrap/ng-bootstrap/modal/modal-config';
/** /**
* This component represents a single bitstream contained in the submission * This component represents a single bitstream contained in the submission
@@ -87,6 +85,13 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit {
*/ */
@Input() submissionId: string; @Input() submissionId: string;
/**
* The [[SubmissionSectionUploadFileEditComponent]] reference
* @type {SubmissionSectionUploadFileEditComponent}
*/
@ViewChild(SubmissionSectionUploadFileEditComponent) fileEditComp: SubmissionSectionUploadFileEditComponent;
/** /**
* The bitstream's metadata data * The bitstream's metadata data
* @type {WorkspaceitemSectionUploadFileObject} * @type {WorkspaceitemSectionUploadFileObject}
@@ -130,10 +135,10 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit {
protected subscriptions: Subscription[] = []; protected subscriptions: Subscription[] = [];
/** /**
* The [[SubmissionSectionUploadFileEditComponent]] reference * Array containing all the form metadata defined in configMetadataForm
* @type {SubmissionSectionUploadFileEditComponent} * @type {Array}
*/ */
@ViewChild(SubmissionSectionUploadFileEditComponent) fileEditComp: SubmissionSectionUploadFileEditComponent; protected formMetadata: string[] = [];
/** /**
* Initialize instance variables * Initialize instance variables
@@ -147,14 +152,16 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit {
* @param {SubmissionService} submissionService * @param {SubmissionService} submissionService
* @param {SectionUploadService} uploadService * @param {SectionUploadService} uploadService
*/ */
constructor(private cdr: ChangeDetectorRef, constructor(
private cdr: ChangeDetectorRef,
private formService: FormService, private formService: FormService,
private halService: HALEndpointService, private halService: HALEndpointService,
private modalService: NgbModal, private modalService: NgbModal,
private operationsBuilder: JsonPatchOperationsBuilder, private operationsBuilder: JsonPatchOperationsBuilder,
private operationsService: SubmissionJsonPatchOperationsService, private operationsService: SubmissionJsonPatchOperationsService,
private submissionService: SubmissionService, private submissionService: SubmissionService,
private uploadService: SectionUploadService) { private uploadService: SectionUploadService,
) {
this.readMode = true; this.readMode = true;
} }
@@ -182,22 +189,7 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit {
ngOnInit() { ngOnInit() {
this.formId = this.formService.getUniqueId(this.fileId); this.formId = this.formService.getUniqueId(this.fileId);
this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId, 'files', this.fileIndex); this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId, 'files', this.fileIndex);
} this.loadFormMetadata();
/**
* Delete bitstream from submission
*/
protected deleteFile() {
this.operationsBuilder.remove(this.pathCombiner.getPath());
this.subscriptions.push(this.operationsService.jsonPatchByResourceID(
this.submissionService.getSubmissionObjectLinkName(),
this.submissionId,
this.pathCombiner.rootElement,
this.pathCombiner.subRootElement)
.subscribe(() => {
this.uploadService.removeUploadedFile(this.submissionId, this.sectionId, this.fileId);
this.processingDelete$.next(false);
}));
} }
/** /**
@@ -225,98 +217,63 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit {
}); });
} }
editBitstreamData() {
const options: NgbModalOptions = {
size: 'xl',
backdrop: 'static',
};
const activeModal = this.modalService.open(SubmissionSectionUploadFileEditComponent, options);
activeModal.componentInstance.availableAccessConditionOptions = this.availableAccessConditionOptions;
activeModal.componentInstance.collectionId = this.collectionId;
activeModal.componentInstance.collectionPolicyType = this.collectionPolicyType;
activeModal.componentInstance.configMetadataForm = this.configMetadataForm;
activeModal.componentInstance.fileData = this.fileData;
activeModal.componentInstance.fileId = this.fileId;
activeModal.componentInstance.fileIndex = this.fileIndex;
activeModal.componentInstance.formId = this.formId;
activeModal.componentInstance.sectionId = this.sectionId;
activeModal.componentInstance.formMetadata = this.formMetadata;
activeModal.componentInstance.pathCombiner = this.pathCombiner;
activeModal.componentInstance.submissionId = this.submissionId;
}
ngOnDestroy(): void {
this.unsubscribeAll();
}
unsubscribeAll() {
this.subscriptions.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
}
protected loadFormMetadata() {
this.configMetadataForm.rows.forEach((row) => {
row.fields.forEach((field) => {
field.selectableMetadata.forEach((metadatum) => {
this.formMetadata.push(metadatum.metadata);
});
});
}
);
}
/** /**
* Save bitstream metadata * Delete bitstream from submission
*
* @param event
* the click event emitted
*/ */
public saveBitstreamData(event) { protected deleteFile() {
event.preventDefault(); this.operationsBuilder.remove(this.pathCombiner.getPath());
this.subscriptions.push(this.operationsService.jsonPatchByResourceID(
// validate form
this.formService.validateAllFormFields(this.fileEditComp.formRef.formGroup);
this.subscriptions.push(this.formService.isValid(this.formId).pipe(
take(1),
filter((isValid) => isValid),
mergeMap(() => this.formService.getFormData(this.formId)),
take(1),
mergeMap((formData: any) => {
// collect bitstream metadata
Object.keys((formData.metadata))
.filter((key) => isNotEmpty(formData.metadata[key]))
.forEach((key) => {
const metadataKey = key.replace(/_/g, '.');
const path = `metadata/${metadataKey}`;
this.operationsBuilder.add(this.pathCombiner.getPath(path), formData.metadata[key], true);
});
const accessConditionsToSave = [];
formData.accessConditions
.map((accessConditions) => accessConditions.accessConditionGroup)
.filter((accessCondition) => isNotEmpty(accessCondition))
.forEach((accessCondition) => {
let accessConditionOpt;
this.availableAccessConditionOptions
.filter((element) => isNotNull(accessCondition.name) && element.name === accessCondition.name[0].value)
.forEach((element) => accessConditionOpt = element);
if (accessConditionOpt) {
accessConditionOpt = Object.assign({}, accessCondition);
accessConditionOpt.name = this.retrieveValueFromField(accessCondition.name);
if (accessCondition.startDate) {
const startDate = this.retrieveValueFromField(accessCondition.startDate);
accessConditionOpt.startDate = dateToISOFormat(startDate);
}
if (accessCondition.endDate) {
const endDate = this.retrieveValueFromField(accessCondition.endDate);
accessConditionOpt.endDate = dateToISOFormat(endDate);
}
accessConditionsToSave.push(accessConditionOpt);
}
});
if (isNotEmpty(accessConditionsToSave)) {
this.operationsBuilder.add(this.pathCombiner.getPath('accessConditions'), accessConditionsToSave, true);
}
// dispatch a PATCH request to save metadata
return this.operationsService.jsonPatchByResourceID(
this.submissionService.getSubmissionObjectLinkName(), this.submissionService.getSubmissionObjectLinkName(),
this.submissionId, this.submissionId,
this.pathCombiner.rootElement, this.pathCombiner.rootElement,
this.pathCombiner.subRootElement); this.pathCombiner.subRootElement)
}) .subscribe(() => {
).subscribe((result: SubmissionObject[]) => { this.uploadService.removeUploadedFile(this.submissionId, this.sectionId, this.fileId);
if (result[0].sections[this.sectionId]) { this.processingDelete$.next(false);
const uploadSection = (result[0].sections[this.sectionId] as WorkspaceitemSectionUploadObject);
Object.keys(uploadSection.files)
.filter((key) => uploadSection.files[key].uuid === this.fileId)
.forEach((key) => this.uploadService.updateFileData(
this.submissionId, this.sectionId, this.fileId, uploadSection.files[key])
);
}
this.switchMode();
})); }));
} }
/**
* Retrieve field value
*
* @param field
* the specified field object
*/
private retrieveValueFromField(field: any) {
const temp = Array.isArray(field) ? field[0] : field;
return (temp) ? temp.value : undefined;
}
/**
* Switch from edit form to metadata view
*/
public switchMode() {
this.readMode = !this.readMode;
this.cdr.detectChanges();
}
} }