Merge commit 'bbc8b6ac8a15bdd619d4f7dac90655ab52dbad2c' into CST-12043-primary-bitstream-flag

This commit is contained in:
Vlad Nouski
2023-10-23 19:33:39 +02:00
12 changed files with 207 additions and 128 deletions

View File

@@ -5,6 +5,9 @@ import { SubmissionFormsConfigDataService } from '../../core/config/submission-f
*/ */
export function getMockSectionUploadService(): SubmissionFormsConfigDataService { export function getMockSectionUploadService(): SubmissionFormsConfigDataService {
return jasmine.createSpyObj('SectionUploadService', { return jasmine.createSpyObj('SectionUploadService', {
updatePrimaryBitstreamOperation: jasmine.createSpy('updatePrimaryBitstreamOperation'),
updateFilePrimaryBitstream: jasmine.createSpy('updateFilePrimaryBitstream'),
getUploadedFilesData: jasmine.createSpy('getUploadedFilesData'),
getUploadedFileList: jasmine.createSpy('getUploadedFileList'), getUploadedFileList: jasmine.createSpy('getUploadedFileList'),
getFileData: jasmine.createSpy('getFileData'), getFileData: jasmine.createSpy('getFileData'),
getDefaultPolicies: jasmine.createSpy('getDefaultPolicies'), getDefaultPolicies: jasmine.createSpy('getDefaultPolicies'),

View File

@@ -1612,7 +1612,13 @@ export const mockUploadFiles = [
} }
]; ];
export const mockUploadFilesData = {
primary: null,
files: JSON.parse(JSON.stringify(mockUploadFiles))
};
export const mockFileFormData = { export const mockFileFormData = {
primary: [true],
metadata: { metadata: {
'dc.title': [ 'dc.title': [
{ {

View File

@@ -766,7 +766,7 @@ export class EditFilePrimaryBitstreamAction implements Action {
payload: { payload: {
submissionId: string; submissionId: string;
sectionId: string; sectionId: string;
fileId: string; fileId: string | null;
}; };
/** /**
@@ -779,7 +779,7 @@ export class EditFilePrimaryBitstreamAction implements Action {
* @param fileId * @param fileId
* the file's ID * the file's ID
*/ */
constructor(submissionId: string, sectionId: string, fileId: string) { constructor(submissionId: string, sectionId: string, fileId: string | null) {
this.payload = { submissionId, sectionId, fileId: fileId }; this.payload = { submissionId, sectionId, fileId: fileId };
} }
} }

View File

@@ -47,6 +47,7 @@ import {
} from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner'; } from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner';
import { dateToISOFormat } from '../../../../../shared/date.util'; import { dateToISOFormat } from '../../../../../shared/date.util';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { DynamicCustomSwitchModel } from '../../../../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', { const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', {
add: jasmine.createSpy('add'), add: jasmine.createSpy('add'),
@@ -78,7 +79,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); const pathCombiner = new JsonPatchOperationPathCombiner('sections', sectionId);
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -183,11 +184,15 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
comp.ngOnInit(); comp.ngOnInit();
const models = [DynamicCustomSwitchModel, DynamicFormGroupModel, DynamicFormArrayModel];
expect(comp.formModel).toBeDefined(); expect(comp.formModel).toBeDefined();
expect(comp.formModel.length).toBe(2); expect(comp.formModel.length).toBe(models.length);
expect(comp.formModel[0] instanceof DynamicFormGroupModel).toBeTruthy(); models.forEach((model, i) => {
expect(comp.formModel[1] instanceof DynamicFormArrayModel).toBeTruthy(); expect(comp.formModel[i] instanceof model).toBeTruthy();
expect((comp.formModel[1] as DynamicFormArrayModel).groups.length).toBe(2); });
expect((comp.formModel[2] as DynamicFormArrayModel).groups.length).toBe(2);
const startDateModel = formbuilderService.findById('startDate', comp.formModel); const startDateModel = formbuilderService.findById('startDate', comp.formModel);
expect(startDateModel.max).toEqual(maxStartDate); expect(startDateModel.max).toEqual(maxStartDate);
const endDateModel = formbuilderService.findById('endDate', comp.formModel); const endDateModel = formbuilderService.findById('endDate', comp.formModel);
@@ -251,6 +256,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
compAsAny.formRef = {formGroup: null}; compAsAny.formRef = {formGroup: null};
compAsAny.fileData = fileData; compAsAny.fileData = fileData;
compAsAny.pathCombiner = pathCombiner; compAsAny.pathCombiner = pathCombiner;
compAsAny.isPrimary = null;
formService.validateAllFormFields.and.callFake(() => null); formService.validateAllFormFields.and.callFake(() => null);
formService.isValid.and.returnValue(of(true)); formService.isValid.and.returnValue(of(true));
formService.getFormData.and.returnValue(of(mockFileFormData)); formService.getFormData.and.returnValue(of(mockFileFormData));
@@ -259,6 +265,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
Object.assign(mockSubmissionObject, { Object.assign(mockSubmissionObject, {
sections: { sections: {
upload: { upload: {
primary: true,
files: mockUploadFiles files: mockUploadFiles
} }
} }
@@ -274,23 +281,28 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
comp.saveBitstreamData(); comp.saveBitstreamData();
tick(); tick();
let path = 'metadata/dc.title'; let path = 'primary';
expect(uploadService.updatePrimaryBitstreamOperation).toHaveBeenCalledWith(pathCombiner.getPath(path), compAsAny.isPrimary, mockFileFormData.primary[0], compAsAny.fileId);
const pathFragment = ['files', fileIndex];
path = 'metadata/dc.title';
expect(operationsBuilder.add).toHaveBeenCalledWith( expect(operationsBuilder.add).toHaveBeenCalledWith(
pathCombiner.getPath(path), pathCombiner.getPath([...pathFragment, path]),
mockFileFormData.metadata['dc.title'], mockFileFormData.metadata['dc.title'],
true true
); );
path = 'metadata/dc.description'; path = 'metadata/dc.description';
expect(operationsBuilder.add).toHaveBeenCalledWith( expect(operationsBuilder.add).toHaveBeenCalledWith(
pathCombiner.getPath(path), pathCombiner.getPath([...pathFragment, path]),
mockFileFormData.metadata['dc.description'], mockFileFormData.metadata['dc.description'],
true true
); );
path = 'accessConditions'; path = 'accessConditions';
expect(operationsBuilder.add).toHaveBeenCalledWith( expect(operationsBuilder.add).toHaveBeenCalledWith(
pathCombiner.getPath(path), pathCombiner.getPath([...pathFragment, path]),
accessConditionsToSave, accessConditionsToSave,
true true
); );

View File

@@ -29,7 +29,6 @@ import {
BITSTREAM_FORM_ACCESS_CONDITION_TYPE_CONFIG, BITSTREAM_FORM_ACCESS_CONDITION_TYPE_CONFIG,
BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT, BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT,
BITSTREAM_FORM_PRIMARY, BITSTREAM_FORM_PRIMARY,
BITSTREAM_FORM_PRIMARY_LAYOUT,
BITSTREAM_METADATA_FORM_GROUP_CONFIG, BITSTREAM_METADATA_FORM_GROUP_CONFIG,
BITSTREAM_METADATA_FORM_GROUP_LAYOUT BITSTREAM_METADATA_FORM_GROUP_LAYOUT
} from './section-upload-file-edit.model'; } from './section-upload-file-edit.model';
@@ -42,7 +41,7 @@ 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 { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { filter, mergeMap, take, tap } from 'rxjs/operators'; import { filter, mergeMap, take } from 'rxjs/operators';
import { dateToISOFormat } from '../../../../../shared/date.util'; import { dateToISOFormat } from '../../../../../shared/date.util';
@@ -54,10 +53,12 @@ import {
JsonPatchOperationPathCombiner JsonPatchOperationPathCombiner
} from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner'; } from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner';
import { SectionUploadService } from '../../section-upload.service'; import { SectionUploadService } from '../../section-upload.service';
import { Observable, Subject, Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { DynamicFormControlCondition } from '@ng-dynamic-forms/core/lib/model/misc/dynamic-form-control-relation.model'; import { DynamicFormControlCondition } from '@ng-dynamic-forms/core/lib/model/misc/dynamic-form-control-relation.model';
import { DynamicDateControlValue } from '@ng-dynamic-forms/core/lib/model/dynamic-date-control.model'; import { DynamicDateControlValue } from '@ng-dynamic-forms/core/lib/model/dynamic-date-control.model';
import { DynamicCustomSwitchModel } from 'src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model'; import { DynamicCustomSwitchModel } from 'src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
import { SubmissionObject } from 'src/app/core/submission/models/submission-object.model';
import { WorkspaceitemSectionUploadObject } from 'src/app/core/submission/models/workspaceitem-section-upload.model';
/** /**
* This component represents the edit form for bitstream * This component represents the edit form for bitstream
@@ -168,12 +169,6 @@ export class SubmissionSectionUploadFileEditComponent
protected subscriptions: Subscription[] = []; protected subscriptions: Subscription[] = [];
private onSaveBitstreamData$: Subject<any> = new Subject();
get saveBitstreamDataEvent$(): Observable<void> {
return this.onSaveBitstreamData$.asObservable();
}
/** /**
* Initialize instance variables * Initialize instance variables
* *
@@ -206,8 +201,8 @@ export class SubmissionSectionUploadFileEditComponent
*/ */
public initModelData(formModel: DynamicFormControlModel[]) { public initModelData(formModel: DynamicFormControlModel[]) {
const primaryBitstreammodel: any = this.formBuilderService.findById('primaryBitstream', formModel, this.fileIndex); const primaryBitstreamModel: any = this.formBuilderService.findById('primary', formModel, this.fileIndex);
primaryBitstreammodel.value = this.isPrimary || false; primaryBitstreamModel.value = this.isPrimary || false;
this.fileData.accessConditions.forEach((accessCondition, index) => { this.fileData.accessConditions.forEach((accessCondition, index) => {
Array.of('name', 'startDate', 'endDate') Array.of('name', 'startDate', 'endDate')
@@ -417,12 +412,14 @@ export class SubmissionSectionUploadFileEditComponent
// validate form // validate form
this.formService.validateAllFormFields(this.formRef.formGroup); this.formService.validateAllFormFields(this.formRef.formGroup);
const subscription = this.formService.isValid(this.formId).pipe( const saveBitstreamDataSubscription = this.formService.isValid(this.formId).pipe(
take(1), take(1),
filter((isValid) => isValid), filter((isValid) => isValid),
mergeMap(() => this.formService.getFormData(this.formId)), mergeMap(() => this.formService.getFormData(this.formId)),
take(1), take(1),
tap((formData: any) => { mergeMap((formData: any) => {
this.uploadService.updatePrimaryBitstreamOperation(this.pathCombiner.getPath('primary'), this.isPrimary, formData.primary[0], this.fileId);
// collect bitstream metadata // collect bitstream metadata
Object.keys((formData.metadata)) Object.keys((formData.metadata))
.filter((key) => isNotEmpty(formData.metadata[key])) .filter((key) => isNotEmpty(formData.metadata[key]))
@@ -494,9 +491,31 @@ export class SubmissionSectionUploadFileEditComponent
if (isNotEmpty(accessConditionsToSave)) { if (isNotEmpty(accessConditionsToSave)) {
this.operationsBuilder.add(this.pathCombiner.getPath([...pathFragment, 'accessConditions']), accessConditionsToSave, true); this.operationsBuilder.add(this.pathCombiner.getPath([...pathFragment, 'accessConditions']), accessConditionsToSave, true);
} }
}) // dispatch a PATCH request to save metadata
).subscribe((formData) => this.onSaveBitstreamData$.next(formData)); return this.operationsService.jsonPatchByResourceID(
this.subscriptions.push(subscription); this.submissionService.getSubmissionObjectLinkName(),
this.submissionId,
this.pathCombiner.rootElement,
this.pathCombiner.subRootElement);
})
).subscribe((result: SubmissionObject[]) => {
const section = result[0].sections[this.sectionId];
if (!section) {
return;
}
const uploadSection = (section as WorkspaceitemSectionUploadObject);
this.uploadService.updateFilePrimaryBitstream(this.submissionId, this.sectionId, uploadSection.primary);
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() { private unsubscribeAll() {

View File

@@ -64,8 +64,8 @@ export const BITSTREAM_FORM_PRIMARY_LAYOUT: DynamicFormControlLayout = {
}; };
export const BITSTREAM_FORM_PRIMARY: DynamicSwitchModelConfig = { export const BITSTREAM_FORM_PRIMARY: DynamicSwitchModelConfig = {
id: 'primaryBitstream', id: 'primary',
name: 'primaryBitstream', name: 'primary',
label: 'Primary bitstream', label: 'Primary bitstream',
}; };

View File

@@ -3,13 +3,19 @@
<!-- Default switch --> <!-- Default switch -->
<div class="col-md-2 d-flex justify-content-center align-items-center" > <div class="col-md-2 d-flex justify-content-center align-items-center" >
<div class="custom-control custom-switch"> <div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="primaryBitstream{{fileIndex}}" [(ngModel)]="isPrimary" (change)="togglePrimaryBitstream()"> <input
type="checkbox"
class="custom-control-input"
id="primaryBitstream{{fileIndex}}"
[disabled]="processingSaveStatus$ | async"
[checked]="isPrimary"
(change)="togglePrimaryBitstream($event)">
<label class="custom-control-label" for="primaryBitstream{{fileIndex}}"></label> <label class="custom-control-label" for="primaryBitstream{{fileIndex}}"></label>
</div> </div>
</div> </div>
<div class="col-md-10"> <div class="col-md-10">
<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"> <div class="float-right w-15">
<ng-container> <ng-container>

View File

@@ -66,7 +66,7 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
const fileName = '123456-test-upload.jpg'; const fileName = '123456-test-upload.jpg';
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); const pathCombiner = new JsonPatchOperationPathCombiner('sections', sectionId);
const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', { const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', {
add: jasmine.createSpy('add'), add: jasmine.createSpy('add'),
@@ -201,6 +201,23 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
}); });
}); });
it('should delete primary if file we delete is primary', () => {
compAsAny.isPrimary = true;
compAsAny.pathCombiner = pathCombiner;
operationsService.jsonPatchByResourceID.and.returnValue(observableOf({}));
compAsAny.deleteFile();
expect(operationsBuilder.remove).toHaveBeenCalledWith(pathCombiner.getPath('primary'));
expect(uploadService.updateFilePrimaryBitstream).toHaveBeenCalledWith(submissionId, sectionId, null);
});
it('should NOT delete primary if file we delete is NOT primary', () => {
compAsAny.isPrimary = false;
compAsAny.pathCombiner = pathCombiner;
operationsService.jsonPatchByResourceID.and.returnValue(observableOf({}));
compAsAny.deleteFile();
expect(uploadService.updateFilePrimaryBitstream).not.toHaveBeenCalledTimes(1);
});
it('should delete file properly', () => { it('should delete file properly', () => {
compAsAny.pathCombiner = pathCombiner; compAsAny.pathCombiner = pathCombiner;
operationsService.jsonPatchByResourceID.and.returnValue(observableOf({})); operationsService.jsonPatchByResourceID.and.returnValue(observableOf({}));
@@ -209,7 +226,8 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
compAsAny.deleteFile(); compAsAny.deleteFile();
expect(uploadService.removeUploadedFile).toHaveBeenCalledWith(submissionId, sectionId, fileId); expect(uploadService.removeUploadedFile).toHaveBeenCalledWith(submissionId, sectionId, fileId);
expect(operationsBuilder.remove).toHaveBeenCalledWith(pathCombiner.getPath()); expect(operationsBuilder.remove).toHaveBeenCalledWith(pathCombiner.getPath(['files', fileIndex]));
expect(operationsService.jsonPatchByResourceID).toHaveBeenCalledWith( expect(operationsService.jsonPatchByResourceID).toHaveBeenCalledWith(
'workspaceitems', 'workspaceitems',
submissionId, submissionId,

View File

@@ -1,5 +1,4 @@
import { import {
ChangeDetectorRef,
Component, Component,
Input, Input,
OnChanges, OnChanges,
@@ -9,8 +8,8 @@ import {
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { BehaviorSubject, Subscription } from 'rxjs'; import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { filter, switchMap, tap } 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';
@@ -22,14 +21,10 @@ import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/buil
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 { SubmissionService } from '../../../submission.service'; import { SubmissionService } from '../../../submission.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 { 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'; import { NgbModalOptions } from '@ng-bootstrap/ng-bootstrap/modal/modal-config';
import { WorkspaceitemSectionUploadObject } from 'src/app/core/submission/models/workspaceitem-section-upload.model';
import { SubmissionObject } from 'src/app/core/submission/models/submission-object.model';
import { NotificationsService } from 'src/app/shared/notifications/notifications.service';
/** /**
* This component represents a single bitstream contained in the submission * This component represents a single bitstream contained in the submission
@@ -45,14 +40,7 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
* it will be null if no primary bitstream is set for the ORIGINAL bundle * it will be null if no primary bitstream is set for the ORIGINAL bundle
* @type {boolean, null} * @type {boolean, null}
*/ */
@Input() set isPrimaryBitstream(status: boolean | null) { @Input() isPrimary: boolean | null;
this.initialPrimaryStatus = status;
this.isPrimary = status;
}
private initialPrimaryStatus = false;
isPrimary = false;
/** /**
* The list of available access condition * The list of available access condition
@@ -116,6 +104,11 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
*/ */
@ViewChild(SubmissionSectionUploadFileEditComponent) fileEditComp: SubmissionSectionUploadFileEditComponent; @ViewChild(SubmissionSectionUploadFileEditComponent) fileEditComp: SubmissionSectionUploadFileEditComponent;
/**
* A boolean representing if a submission save operation is pending
* @type {Observable<boolean>}
*/
public processingSaveStatus$: Observable<boolean>;
/** /**
* The bitstream's metadata data * The bitstream's metadata data
@@ -184,10 +177,7 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
* @param {SectionUploadService} uploadService * @param {SectionUploadService} uploadService
*/ */
constructor( constructor(
private cdr: ChangeDetectorRef,
private formService: FormService, private formService: FormService,
private halService: HALEndpointService,
private notificationsService: NotificationsService,
private modalService: NgbModal, private modalService: NgbModal,
private operationsBuilder: JsonPatchOperationsBuilder, private operationsBuilder: JsonPatchOperationsBuilder,
private operationsService: SubmissionJsonPatchOperationsService, private operationsService: SubmissionJsonPatchOperationsService,
@@ -220,6 +210,7 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
*/ */
ngOnInit() { ngOnInit() {
this.formId = this.formService.getUniqueId(this.fileId); this.formId = this.formService.getUniqueId(this.fileId);
this.processingSaveStatus$ = this.submissionService.getSubmissionSaveProcessingStatus(this.submissionId);
this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId); this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId);
this.loadFormMetadata(); this.loadFormMetadata();
} }
@@ -249,23 +240,6 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
}); });
} }
private updatePrimaryBitstream(primaryBitstream: boolean): void {
const path = this.pathCombiner.getPath('primary');
if (this.isPrimary === null && primaryBitstream) {
this.operationsBuilder.add(path, this.fileId, false, true);
return;
}
if (this.isPrimary !== primaryBitstream) {
if (primaryBitstream) {
this.operationsBuilder.replace(path, this.fileId, true);
return;
}
this.operationsBuilder.remove(path);
}
}
editBitstreamData() { editBitstreamData() {
const options: NgbModalOptions = { const options: NgbModalOptions = {
@@ -288,50 +262,10 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
activeModal.componentInstance.pathCombiner = this.pathCombiner; activeModal.componentInstance.pathCombiner = this.pathCombiner;
activeModal.componentInstance.submissionId = this.submissionId; activeModal.componentInstance.submissionId = this.submissionId;
activeModal.componentInstance.isPrimary = this.isPrimary; activeModal.componentInstance.isPrimary = this.isPrimary;
activeModal.componentInstance.saveBitstreamDataEvent$.pipe(
// TODO: uncoment
tap((data: any) => {
this.updatePrimaryBitstream(data.primaryBitstream[0]);
}),
switchMap(() => this.patchFileOperations())
).subscribe((result: SubmissionObject[]) => {
this.uploadFileData(result);
activeModal.componentInstance.isSaving = false;
activeModal.close();
});
} }
private uploadFileData(result: SubmissionObject[]) { togglePrimaryBitstream(event) {
const section = result[0].sections[this.sectionId]; this.uploadService.updatePrimaryBitstreamOperation(this.pathCombiner.getPath('primary'), this.isPrimary, event.target.checked, this.fileId);
if (!section) {
return;
}
let uploadSection = (section as WorkspaceitemSectionUploadObject);
uploadSection = {...uploadSection, primary: this.fileId};
// TODO: update only if primary bitstream changed
// TODO: if the result contains primary id of this file of null then update
this.uploadService.updateFilePrimaryBitstream(this.submissionId, this.sectionId, this.fileId);
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]);
});
}
private patchFileOperations() {
return this.operationsService.jsonPatchByResourceID(
this.submissionService.getSubmissionObjectLinkName(),
this.submissionId,
this.pathCombiner.rootElement,
this.pathCombiner.subRootElement);
}
togglePrimaryBitstream() {
this.updatePrimaryBitstream(!this.isPrimary);
this.submissionService.dispatchSaveSection(this.submissionId, this.sectionId); this.submissionService.dispatchSaveSection(this.submissionId, this.sectionId);
} }
@@ -359,10 +293,8 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
*/ */
protected deleteFile() { protected deleteFile() {
this.operationsBuilder.remove(this.pathCombiner.getPath(['files', this.fileIndex])); this.operationsBuilder.remove(this.pathCombiner.getPath(['files', this.fileIndex]));
if (this.isPrimary) { if (this.isPrimary) {
// TODO: uncoment this.operationsBuilder.remove(this.pathCombiner.getPath('primary'));
// this.operationsBuilder.remove(this.pathCombiner.getPath('primary'));
} }
this.subscriptions.push(this.operationsService.jsonPatchByResourceID( this.subscriptions.push(this.operationsService.jsonPatchByResourceID(
@@ -371,6 +303,9 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
this.pathCombiner.rootElement, this.pathCombiner.rootElement,
this.pathCombiner.subRootElement) this.pathCombiner.subRootElement)
.subscribe(() => { .subscribe(() => {
if (this.isPrimary) {
this.uploadService.updateFilePrimaryBitstream(this.submissionId, this.sectionId, null);
}
this.uploadService.removeUploadedFile(this.submissionId, this.sectionId, this.fileId); this.uploadService.removeUploadedFile(this.submissionId, this.sectionId, this.fileId);
this.processingDelete$.next(false); this.processingDelete$.next(false);
})); }));

View File

@@ -25,6 +25,7 @@ import {
mockUploadConfigResponse, mockUploadConfigResponse,
mockUploadConfigResponseNotRequired, mockUploadConfigResponseNotRequired,
mockUploadFiles, mockUploadFiles,
mockUploadFilesData,
} from '../../../shared/mocks/submission.mock'; } from '../../../shared/mocks/submission.mock';
import { SubmissionUploadsConfigDataService } from '../../../core/config/submission-uploads-config-data.service'; import { SubmissionUploadsConfigDataService } from '../../../core/config/submission-uploads-config-data.service';
import { SectionUploadService } from './section-upload.service'; import { SectionUploadService } from './section-upload.service';
@@ -160,6 +161,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
); );
bitstreamService.getUploadedFileList.and.returnValue(observableOf([])); bitstreamService.getUploadedFileList.and.returnValue(observableOf([]));
bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
}; };
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -230,7 +232,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
}); });
it('should init component properly', () => { it('should init component properly', () => {
bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState)); submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(Object.assign(new Collection(), mockCollection, { collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(Object.assign(new Collection(), mockCollection, {
@@ -246,15 +248,8 @@ describe('SubmissionSectionUploadComponent test suite', () => {
createSuccessfulRemoteDataObject$(Object.assign(new Group(), mockGroup)) createSuccessfulRemoteDataObject$(Object.assign(new Group(), mockGroup))
); );
bitstreamService.getUploadedFileList.and.returnValue(observableOf([]));
comp.onSectionInit(); comp.onSectionInit();
const expectedGroupsMap = new Map([
[mockUploadConfigResponse.accessConditionOptions[1].name, [mockGroup as any]],
[mockUploadConfigResponse.accessConditionOptions[2].name, [mockGroup as any]],
]);
expect(comp.collectionId).toBe(collectionId); expect(comp.collectionId).toBe(collectionId);
expect(comp.collectionName).toBe(mockCollection.name); expect(comp.collectionName).toBe(mockCollection.name);
expect(comp.availableAccessConditionOptions.length).toBe(4); expect(comp.availableAccessConditionOptions.length).toBe(4);
@@ -262,12 +257,12 @@ describe('SubmissionSectionUploadComponent test suite', () => {
expect(comp.required$.getValue()).toBe(true); expect(comp.required$.getValue()).toBe(true);
expect(compAsAny.subs.length).toBe(2); expect(compAsAny.subs.length).toBe(2);
expect(compAsAny.fileList).toEqual([]); expect(compAsAny.fileList).toEqual([]);
expect(compAsAny.fileIndexes).toEqual([]);
expect(compAsAny.fileNames).toEqual([]); expect(compAsAny.fileNames).toEqual([]);
expect(compAsAny.primaryBitstreamUUID).toEqual(null);
}); });
it('should init file list properly', () => { it('should init file list properly', () => {
bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState)); submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
@@ -282,7 +277,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
createSuccessfulRemoteDataObject$(Object.assign(new Group(), mockGroup)) createSuccessfulRemoteDataObject$(Object.assign(new Group(), mockGroup))
); );
bitstreamService.getUploadedFileList.and.returnValue(observableOf(mockUploadFiles)); bitstreamService.getUploadedFilesData.and.returnValue(observableOf(mockUploadFilesData));
comp.onSectionInit(); comp.onSectionInit();
@@ -298,12 +293,14 @@ describe('SubmissionSectionUploadComponent test suite', () => {
expect(comp.required$.getValue()).toBe(true); expect(comp.required$.getValue()).toBe(true);
expect(compAsAny.subs.length).toBe(2); expect(compAsAny.subs.length).toBe(2);
expect(compAsAny.fileList).toEqual(mockUploadFiles); expect(compAsAny.fileList).toEqual(mockUploadFiles);
expect(compAsAny.fileIndexes).toEqual(['123456-test-upload']); expect(compAsAny.primaryBitstreamUUID).toEqual(null);
expect(compAsAny.fileNames).toEqual(['123456-test-upload.jpg']); expect(compAsAny.fileNames).toEqual(['123456-test-upload.jpg']);
}); });
it('should properly read the section status when required is true', () => { it('should properly read the section status when required is true', () => {
bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState)); submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockCollection)); collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockCollection));
@@ -324,7 +321,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
comp.onSectionInit(); comp.onSectionInit();
expect(comp.required$.getValue()).toBe(true); expect(comp.required$.getValue()).toBe(true);
expect(compAsAny.getSectionStatus()).toBeObservable(cold('-c-d', { expect(compAsAny.getSectionStatus()).toBeObservable(cold('-c-d', {
c: false, c: false,
@@ -335,6 +332,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
it('should properly read the section status when required is false', () => { it('should properly read the section status when required is false', () => {
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState)); submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockCollection)); collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockCollection));
resourcePolicyService.findByHref.and.returnValue(createSuccessfulRemoteDataObject$(mockDefaultAccessCondition)); resourcePolicyService.findByHref.and.returnValue(createSuccessfulRemoteDataObject$(mockDefaultAccessCondition));

View File

@@ -0,0 +1,63 @@
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { JsonPatchOperationPathCombiner } from 'src/app/core/json-patch/builder/json-patch-operation-path-combiner';
import { JsonPatchOperationsBuilder } from 'src/app/core/json-patch/builder/json-patch-operations-builder';
import { SectionUploadService } from './section-upload.service';
import { Store, StoreModule } from '@ngrx/store';
const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', {
add: jasmine.createSpy('add'),
replace: jasmine.createSpy('replace'),
remove: jasmine.createSpy('remove'),
});
describe('SectionUploadService test suite', () => {
let sectionUploadService: SectionUploadService;
let operationsBuilder: any;
const pathCombiner = new JsonPatchOperationPathCombiner('sections', 'upload');
const primaryPath = pathCombiner.getPath('primary');
const fileId = 'test';
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [StoreModule],
providers: [
{ provide: Store, useValue: {} },
SectionUploadService,
{ provide: JsonPatchOperationsBuilder, useValue: jsonPatchOpBuilder },
],
schemas: [NO_ERRORS_SCHEMA]
});
}));
beforeEach(() => {
sectionUploadService = TestBed.inject(SectionUploadService);
operationsBuilder = TestBed.inject(JsonPatchOperationsBuilder);
});
[
{
initialPrimary: null,
primary: true,
operationName: 'add',
expected: [primaryPath, fileId, false, true]
},
{
initialPrimary: true,
primary: false,
operationName: 'remove',
expected: [primaryPath]
},
{
initialPrimary: false,
primary: true,
operationName: 'replace',
expected: [primaryPath, fileId, true]
}
].forEach(({ initialPrimary, primary, operationName, expected }) => {
it(`updatePrimaryBitstreamOperation should add ${operationName} operation`, () => {
const path = pathCombiner.getPath('primary');
sectionUploadService.updatePrimaryBitstreamOperation(path, initialPrimary, primary, fileId);
expect(operationsBuilder[operationName]).toHaveBeenCalledWith(...expected);
});
});
});

View File

@@ -15,6 +15,8 @@ import { submissionSectionDataFromIdSelector, submissionUploadedFileFromUuidSele
import { isUndefined } from '../../../shared/empty.util'; import { isUndefined } from '../../../shared/empty.util';
import { WorkspaceitemSectionUploadFileObject } from '../../../core/submission/models/workspaceitem-section-upload-file.model'; import { WorkspaceitemSectionUploadFileObject } from '../../../core/submission/models/workspaceitem-section-upload-file.model';
import { WorkspaceitemSectionUploadObject } from 'src/app/core/submission/models/workspaceitem-section-upload.model'; import { WorkspaceitemSectionUploadObject } from 'src/app/core/submission/models/workspaceitem-section-upload.model';
import { JsonPatchOperationPathObject } from 'src/app/core/json-patch/builder/json-patch-operation-path-combiner';
import { JsonPatchOperationsBuilder } from 'src/app/core/json-patch/builder/json-patch-operations-builder';
/** /**
* A service that provides methods to handle submission's bitstream state. * A service that provides methods to handle submission's bitstream state.
@@ -26,8 +28,25 @@ export class SectionUploadService {
* Initialize service variables * Initialize service variables
* *
* @param {Store<SubmissionState>} store * @param {Store<SubmissionState>} store
* @param {JsonPatchOperationsBuilder} operationsBuilder
*/ */
constructor(private store: Store<SubmissionState>) {} constructor(private store: Store<SubmissionState>, private operationsBuilder: JsonPatchOperationsBuilder) {}
public updatePrimaryBitstreamOperation(path: JsonPatchOperationPathObject, intitialPrimary: boolean | null, primary: boolean | null, fileId: string): void {
if (intitialPrimary === null && primary) {
this.operationsBuilder.add(path, fileId, false, true);
return;
}
if (intitialPrimary !== primary) {
if (primary) {
this.operationsBuilder.replace(path, fileId, true);
return;
}
this.operationsBuilder.remove(path);
}
}
/** /**
* Return submission's bitstream data from state * Return submission's bitstream data from state
@@ -132,7 +151,7 @@ export class SectionUploadService {
* @param fileUUID * @param fileUUID
* The bitstream UUID * The bitstream UUID
*/ */
public updateFilePrimaryBitstream(submissionId: string, sectionId: string, fileUUID: string) { public updateFilePrimaryBitstream(submissionId: string, sectionId: string, fileUUID: string | null) {
this.store.dispatch( this.store.dispatch(
new EditFilePrimaryBitstreamAction(submissionId, sectionId, fileUUID) new EditFilePrimaryBitstreamAction(submissionId, sectionId, fileUUID)
); );