mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-15 22:13:02 +00:00
Merge pull request #2631 from vNovski/CST-12044-visualize-the-primary-bitstream
CST-12044 visualize the primary bitstream & CST-12043 primary bitstream flag
This commit is contained in:
@@ -6,7 +6,6 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<ds-form *ngIf="formModel"
|
||||
#formRef="formComponent"
|
||||
[formId]="formId"
|
||||
|
@@ -47,6 +47,7 @@ import {
|
||||
} from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner';
|
||||
import { dateToISOFormat } from '../../../../../shared/date.util';
|
||||
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', {
|
||||
add: jasmine.createSpy('add'),
|
||||
@@ -78,7 +79,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
||||
const fileIndex = '0';
|
||||
const fileId = '123456-test-upload';
|
||||
const fileData: any = mockUploadFiles[0];
|
||||
const pathCombiner = new JsonPatchOperationPathCombiner('sections', sectionId, 'files', fileIndex);
|
||||
const pathCombiner = new JsonPatchOperationPathCombiner('sections', sectionId);
|
||||
|
||||
let noAccessConditionsMock = Object.assign({}, mockFileFormData);
|
||||
delete noAccessConditionsMock.accessConditions;
|
||||
@@ -186,11 +187,15 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
||||
|
||||
comp.ngOnInit();
|
||||
|
||||
const models = [DynamicCustomSwitchModel, DynamicFormGroupModel, DynamicFormArrayModel];
|
||||
|
||||
expect(comp.formModel).toBeDefined();
|
||||
expect(comp.formModel.length).toBe(2);
|
||||
expect(comp.formModel[0] instanceof DynamicFormGroupModel).toBeTruthy();
|
||||
expect(comp.formModel[1] instanceof DynamicFormArrayModel).toBeTruthy();
|
||||
expect((comp.formModel[1] as DynamicFormArrayModel).groups.length).toBe(2);
|
||||
expect(comp.formModel.length).toBe(models.length);
|
||||
models.forEach((model, i) => {
|
||||
expect(comp.formModel[i] instanceof model).toBeTruthy();
|
||||
});
|
||||
|
||||
expect((comp.formModel[2] as DynamicFormArrayModel).groups.length).toBe(2);
|
||||
const startDateModel = formbuilderService.findById('startDate', comp.formModel);
|
||||
expect(startDateModel.max).toEqual(maxStartDate);
|
||||
const endDateModel = formbuilderService.findById('endDate', comp.formModel);
|
||||
@@ -254,6 +259,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
||||
compAsAny.formRef = {formGroup: null};
|
||||
compAsAny.fileData = fileData;
|
||||
compAsAny.pathCombiner = pathCombiner;
|
||||
compAsAny.isPrimary = null;
|
||||
formService.validateAllFormFields.and.callFake(() => null);
|
||||
formService.isValid.and.returnValue(of(true));
|
||||
formService.getFormData.and.returnValue(of(mockFileFormData));
|
||||
@@ -262,6 +268,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
||||
Object.assign(mockSubmissionObject, {
|
||||
sections: {
|
||||
upload: {
|
||||
primary: true,
|
||||
files: mockUploadFiles
|
||||
}
|
||||
}
|
||||
@@ -277,23 +284,28 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
||||
comp.saveBitstreamData();
|
||||
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(
|
||||
pathCombiner.getPath(path),
|
||||
pathCombiner.getPath([...pathFragment, path]),
|
||||
mockFileFormData.metadata['dc.title'],
|
||||
true
|
||||
);
|
||||
|
||||
path = 'metadata/dc.description';
|
||||
expect(operationsBuilder.add).toHaveBeenCalledWith(
|
||||
pathCombiner.getPath(path),
|
||||
pathCombiner.getPath([...pathFragment, path]),
|
||||
mockFileFormData.metadata['dc.description'],
|
||||
true
|
||||
);
|
||||
|
||||
path = 'accessConditions';
|
||||
expect(operationsBuilder.add).toHaveBeenCalledWith(
|
||||
pathCombiner.getPath(path),
|
||||
pathCombiner.getPath([...pathFragment, path]),
|
||||
accessConditionsToSave,
|
||||
true
|
||||
);
|
||||
|
@@ -28,6 +28,8 @@ import {
|
||||
BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_LAYOUT,
|
||||
BITSTREAM_FORM_ACCESS_CONDITION_TYPE_CONFIG,
|
||||
BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT,
|
||||
BITSTREAM_FORM_PRIMARY,
|
||||
BITSTREAM_FORM_PRIMARY_LAYOUT,
|
||||
BITSTREAM_METADATA_FORM_GROUP_CONFIG,
|
||||
BITSTREAM_METADATA_FORM_GROUP_LAYOUT
|
||||
} from './section-upload-file-edit.model';
|
||||
@@ -42,10 +44,8 @@ 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
|
||||
@@ -57,6 +57,9 @@ import { SectionUploadService } from '../../section-upload.service';
|
||||
import { Subscription } from 'rxjs';
|
||||
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 { 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
|
||||
@@ -74,6 +77,13 @@ export class SubmissionSectionUploadFileEditComponent
|
||||
*/
|
||||
@ViewChild('formRef') public formRef: FormComponent;
|
||||
|
||||
/**
|
||||
* The indicator is the primary bitstream
|
||||
* it will be null if no primary bitstream is set for the ORIGINAL bundle
|
||||
* @type {boolean, null}
|
||||
*/
|
||||
isPrimary: boolean;
|
||||
|
||||
/**
|
||||
* The list of available access condition
|
||||
* @type {Array}
|
||||
@@ -191,6 +201,10 @@ export class SubmissionSectionUploadFileEditComponent
|
||||
* The form model
|
||||
*/
|
||||
public initModelData(formModel: DynamicFormControlModel[]) {
|
||||
|
||||
const primaryBitstreamModel: any = this.formBuilderService.findById('primary', formModel, this.fileIndex);
|
||||
primaryBitstreamModel.value = this.isPrimary || false;
|
||||
|
||||
this.fileData.accessConditions.forEach((accessCondition, index) => {
|
||||
Array.of('name', 'startDate', 'endDate')
|
||||
.filter((key) => accessCondition.hasOwnProperty(key) && isNotEmpty(accessCondition[key]))
|
||||
@@ -291,6 +305,9 @@ export class SubmissionSectionUploadFileEditComponent
|
||||
])
|
||||
});
|
||||
const formModel: DynamicFormControlModel[] = [];
|
||||
|
||||
formModel.push(new DynamicCustomSwitchModel(BITSTREAM_FORM_PRIMARY, BITSTREAM_FORM_PRIMARY_LAYOUT));
|
||||
|
||||
const metadataGroupModelConfig = Object.assign({}, BITSTREAM_METADATA_FORM_GROUP_CONFIG);
|
||||
metadataGroupModelConfig.group = this.formBuilderService.modelFromConfiguration(
|
||||
this.submissionId,
|
||||
@@ -386,10 +403,14 @@ export class SubmissionSectionUploadFileEditComponent
|
||||
return formModel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save bitstream metadata
|
||||
*/
|
||||
saveBitstreamData() {
|
||||
|
||||
const pathFragment = ['files', this.fileIndex];
|
||||
|
||||
// validate form
|
||||
this.formService.validateAllFormFields(this.formRef.formGroup);
|
||||
const saveBitstreamDataSubscription = this.formService.isValid(this.formId).pipe(
|
||||
@@ -398,13 +419,15 @@ export class SubmissionSectionUploadFileEditComponent
|
||||
mergeMap(() => this.formService.getFormData(this.formId)),
|
||||
take(1),
|
||||
mergeMap((formData: any) => {
|
||||
this.uploadService.updatePrimaryBitstreamOperation(this.pathCombiner.getPath('primary'), this.isPrimary, formData.primary[0], this.fileId);
|
||||
|
||||
// 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);
|
||||
this.operationsBuilder.add(this.pathCombiner.getPath([...pathFragment, path]), formData.metadata[key], true);
|
||||
});
|
||||
Object.keys((this.fileData.metadata))
|
||||
.filter((key) => isNotEmpty(this.fileData.metadata[key]))
|
||||
@@ -413,7 +436,7 @@ export class SubmissionSectionUploadFileEditComponent
|
||||
.forEach((key) => {
|
||||
const metadataKey = key.replace(/_/g, '.');
|
||||
const path = `metadata/${metadataKey}`;
|
||||
this.operationsBuilder.remove(this.pathCombiner.getPath(path));
|
||||
this.operationsBuilder.remove(this.pathCombiner.getPath([...pathFragment, path]));
|
||||
});
|
||||
const accessConditionsToSave = [];
|
||||
if (formData.hasOwnProperty('accessConditions')) {
|
||||
@@ -469,25 +492,29 @@ export class SubmissionSectionUploadFileEditComponent
|
||||
});
|
||||
}
|
||||
if (isNotEmpty(accessConditionsToSave)) {
|
||||
this.operationsBuilder.add(this.pathCombiner.getPath('accessConditions'), accessConditionsToSave, true);
|
||||
this.operationsBuilder.add(this.pathCombiner.getPath([...pathFragment, '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);
|
||||
})
|
||||
// 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])
|
||||
);
|
||||
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();
|
||||
});
|
||||
|
@@ -4,6 +4,7 @@ import {
|
||||
DynamicFormControlLayout,
|
||||
DynamicFormGroupModelConfig,
|
||||
DynamicSelectModelConfig,
|
||||
DynamicSwitchModelConfig,
|
||||
MATCH_ENABLED,
|
||||
OR_OPERATOR,
|
||||
} from '@ng-dynamic-forms/core';
|
||||
@@ -56,6 +57,19 @@ export const BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT: DynamicFormControlLayo
|
||||
label: 'col-form-label name-label'
|
||||
}
|
||||
};
|
||||
export const BITSTREAM_FORM_PRIMARY_LAYOUT: DynamicFormControlLayout = {
|
||||
element: {
|
||||
host: 'col-12',
|
||||
container: 'text-right'
|
||||
},
|
||||
};
|
||||
|
||||
export const BITSTREAM_FORM_PRIMARY: DynamicSwitchModelConfig = {
|
||||
id: 'primary',
|
||||
name: 'primary',
|
||||
label: 'bitstream.edit.form.primaryBitstream.label'
|
||||
};
|
||||
|
||||
|
||||
export const BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_CONFIG: DynamicDatePickerModelConfig = {
|
||||
id: 'startDate',
|
||||
|
@@ -1,6 +1,22 @@
|
||||
<ng-container *ngIf="fileData">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<!-- Default switch -->
|
||||
<div class="col-md-2 d-flex justify-content-center align-items-center" >
|
||||
<div class="custom-control custom-switch">
|
||||
<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}}">
|
||||
<span class="sr-only" *ngIf="!isPrimary">{{'submission.sections.upload.primary.make' | translate:{ fileName: fileName } }}</span>
|
||||
<span class="sr-only" *ngIf="isPrimary">{{'submission.sections.upload.primary.remove' | translate:{ fileName: fileName } }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<div class="float-left w-75">
|
||||
<h3>{{fileName}} <span class="text-muted">({{fileData?.sizeBytes | dsFileSize}})</span></h3>
|
||||
</div>
|
||||
|
@@ -66,7 +66,7 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
|
||||
const fileName = '123456-test-upload.jpg';
|
||||
const fileId = '123456-test-upload';
|
||||
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', {
|
||||
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', () => {
|
||||
compAsAny.pathCombiner = pathCombiner;
|
||||
operationsService.jsonPatchByResourceID.and.returnValue(observableOf({}));
|
||||
@@ -209,7 +226,8 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
|
||||
compAsAny.deleteFile();
|
||||
|
||||
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(
|
||||
'workspaceitems',
|
||||
submissionId,
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
Input,
|
||||
OnChanges,
|
||||
@@ -9,7 +8,7 @@ import {
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
|
||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { DynamicFormControlModel, } from '@ng-dynamic-forms/core';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
@@ -22,7 +21,6 @@ import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/buil
|
||||
import { WorkspaceitemSectionUploadFileObject } from '../../../../core/submission/models/workspaceitem-section-upload-file.model';
|
||||
import { SubmissionFormsModel } from '../../../../core/config/models/config-submission-forms.model';
|
||||
import { SubmissionService } from '../../../submission.service';
|
||||
import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service';
|
||||
import { SubmissionJsonPatchOperationsService } from '../../../../core/submission/submission-json-patch-operations.service';
|
||||
import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component';
|
||||
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||
@@ -37,6 +35,12 @@ import { NgbModalOptions } from '@ng-bootstrap/ng-bootstrap/modal/modal-config';
|
||||
templateUrl: './section-upload-file.component.html',
|
||||
})
|
||||
export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit, OnDestroy {
|
||||
/**
|
||||
* The indicator is the primary bitstream
|
||||
* it will be null if no primary bitstream is set for the ORIGINAL bundle
|
||||
* @type {boolean, null}
|
||||
*/
|
||||
@Input() isPrimary: boolean | null;
|
||||
|
||||
/**
|
||||
* The list of available access condition
|
||||
@@ -100,6 +104,11 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
|
||||
*/
|
||||
@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
|
||||
@@ -137,6 +146,12 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
|
||||
*/
|
||||
protected pathCombiner: JsonPatchOperationPathCombiner;
|
||||
|
||||
/**
|
||||
* The [JsonPatchOperationPathCombiner] object
|
||||
* @type {JsonPatchOperationPathCombiner}
|
||||
*/
|
||||
protected primaryBitstreamPathCombiner: JsonPatchOperationPathCombiner;
|
||||
|
||||
/**
|
||||
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||
* @type {Array}
|
||||
@@ -162,9 +177,7 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
|
||||
* @param {SectionUploadService} uploadService
|
||||
*/
|
||||
constructor(
|
||||
private cdr: ChangeDetectorRef,
|
||||
private formService: FormService,
|
||||
private halService: HALEndpointService,
|
||||
private modalService: NgbModal,
|
||||
private operationsBuilder: JsonPatchOperationsBuilder,
|
||||
private operationsService: SubmissionJsonPatchOperationsService,
|
||||
@@ -197,7 +210,8 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.formId = this.formService.getUniqueId(this.fileId);
|
||||
this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId, 'files', this.fileIndex);
|
||||
this.processingSaveStatus$ = this.submissionService.getSubmissionSaveProcessingStatus(this.submissionId);
|
||||
this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId);
|
||||
this.loadFormMetadata();
|
||||
}
|
||||
|
||||
@@ -247,7 +261,12 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
|
||||
activeModal.componentInstance.formMetadata = this.formMetadata;
|
||||
activeModal.componentInstance.pathCombiner = this.pathCombiner;
|
||||
activeModal.componentInstance.submissionId = this.submissionId;
|
||||
activeModal.componentInstance.isPrimary = this.isPrimary;
|
||||
}
|
||||
|
||||
togglePrimaryBitstream(event) {
|
||||
this.uploadService.updatePrimaryBitstreamOperation(this.pathCombiner.getPath('primary'), this.isPrimary, event.target.checked, this.fileId);
|
||||
this.submissionService.dispatchSaveSection(this.submissionId, this.sectionId);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@@ -273,13 +292,20 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
|
||||
* Delete bitstream from submission
|
||||
*/
|
||||
protected deleteFile() {
|
||||
this.operationsBuilder.remove(this.pathCombiner.getPath());
|
||||
this.operationsBuilder.remove(this.pathCombiner.getPath(['files', this.fileIndex]));
|
||||
if (this.isPrimary) {
|
||||
this.operationsBuilder.remove(this.pathCombiner.getPath('primary'));
|
||||
}
|
||||
|
||||
this.subscriptions.push(this.operationsService.jsonPatchByResourceID(
|
||||
this.submissionService.getSubmissionObjectLinkName(),
|
||||
this.submissionId,
|
||||
this.pathCombiner.rootElement,
|
||||
this.pathCombiner.subRootElement)
|
||||
.subscribe(() => {
|
||||
if (this.isPrimary) {
|
||||
this.uploadService.updateFilePrimaryBitstream(this.submissionId, this.sectionId, null);
|
||||
}
|
||||
this.uploadService.removeUploadedFile(this.submissionId, this.sectionId, this.fileId);
|
||||
this.processingDelete$.next(false);
|
||||
}));
|
||||
|
@@ -17,6 +17,13 @@ export class ThemedSubmissionSectionUploadFileComponent
|
||||
*/
|
||||
@Input() availableAccessConditionOptions: any[];
|
||||
|
||||
/**
|
||||
* The indicator is the primary bitstream
|
||||
* it will be null if no primary bitstream is set for the ORIGINAL bundle
|
||||
* @type {boolean, null}
|
||||
*/
|
||||
@Input() isPrimary: boolean | null;
|
||||
|
||||
/**
|
||||
* The submission id
|
||||
* @type {string}
|
||||
@@ -69,6 +76,7 @@ export class ThemedSubmissionSectionUploadFileComponent
|
||||
|
||||
protected inAndOutputNames: (keyof SubmissionSectionUploadFileComponent & keyof this)[] = [
|
||||
'availableAccessConditionOptions',
|
||||
'isPrimary',
|
||||
'collectionId',
|
||||
'collectionPolicyType',
|
||||
'configMetadataForm',
|
||||
|
@@ -2,15 +2,7 @@
|
||||
[dismissible]="true"
|
||||
[type]="AlertTypeEnum.Info"></ds-alert>
|
||||
|
||||
<ng-container *ngIf="fileList.length == 0">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3 class="text-center"><span class="text-muted">{{'submission.sections.upload.no-file-uploaded' | translate}}</span></h3>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="fileList.length > 0">
|
||||
<ng-container *ngIf="fileList.length > 0; else noFileUploaded">
|
||||
|
||||
<div *ngIf="collectionDefaultAccessConditions.length > 0" class="row">
|
||||
<div class="col-sm-12" >
|
||||
@@ -26,16 +18,26 @@
|
||||
</ds-alert>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngFor="let fileEntry of fileList">
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<span class="text-left font-weight-bold">{{ 'bitstream.edit.form.primaryBitstream.label' | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<hr/>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngFor="let fileEntry of fileList; let i = index;">
|
||||
<ds-themed-submission-upload-section-file
|
||||
[isPrimary]="primaryBitstreamUUID ? primaryBitstreamUUID === fileEntry.uuid : null"
|
||||
[availableAccessConditionOptions]="availableAccessConditionOptions"
|
||||
[collectionId]="collectionId"
|
||||
[collectionPolicyType]="collectionPolicyType"
|
||||
[configMetadataForm]="(configMetadataForm$ | async)"
|
||||
[fileId]="fileIndexes[fileList.indexOf(fileEntry)]"
|
||||
[fileIndex]="fileList.indexOf(fileEntry)"
|
||||
[fileName]="fileNames[fileList.indexOf(fileEntry)]"
|
||||
[fileId]="fileEntry.uuid"
|
||||
[fileIndex]="i"
|
||||
[fileName]="fileNames[i]"
|
||||
[sectionId]="sectionData.id"
|
||||
[submissionId]="submissionId"></ds-themed-submission-upload-section-file>
|
||||
<div class="row">
|
||||
@@ -45,3 +47,11 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #noFileUploaded>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3 class="text-center"><span class="text-muted">{{'submission.sections.upload.no-file-uploaded' | translate}}</span></h3>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
@@ -25,6 +25,7 @@ import {
|
||||
mockUploadConfigResponse,
|
||||
mockUploadConfigResponseNotRequired,
|
||||
mockUploadFiles,
|
||||
mockUploadFilesData,
|
||||
} from '../../../shared/mocks/submission.mock';
|
||||
import { SubmissionUploadsConfigDataService } from '../../../core/config/submission-uploads-config-data.service';
|
||||
import { SectionUploadService } from './section-upload.service';
|
||||
@@ -160,6 +161,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
|
||||
);
|
||||
|
||||
bitstreamService.getUploadedFileList.and.returnValue(observableOf([]));
|
||||
bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
@@ -230,7 +232,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
|
||||
});
|
||||
|
||||
it('should init component properly', () => {
|
||||
|
||||
bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
|
||||
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
|
||||
|
||||
collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(Object.assign(new Collection(), mockCollection, {
|
||||
@@ -246,15 +248,8 @@ describe('SubmissionSectionUploadComponent test suite', () => {
|
||||
createSuccessfulRemoteDataObject$(Object.assign(new Group(), mockGroup))
|
||||
);
|
||||
|
||||
bitstreamService.getUploadedFileList.and.returnValue(observableOf([]));
|
||||
|
||||
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.collectionName).toBe(mockCollection.name);
|
||||
expect(comp.availableAccessConditionOptions.length).toBe(4);
|
||||
@@ -262,12 +257,12 @@ describe('SubmissionSectionUploadComponent test suite', () => {
|
||||
expect(comp.required$.getValue()).toBe(true);
|
||||
expect(compAsAny.subs.length).toBe(2);
|
||||
expect(compAsAny.fileList).toEqual([]);
|
||||
expect(compAsAny.fileIndexes).toEqual([]);
|
||||
expect(compAsAny.fileNames).toEqual([]);
|
||||
|
||||
expect(compAsAny.primaryBitstreamUUID).toEqual(null);
|
||||
});
|
||||
|
||||
it('should init file list properly', () => {
|
||||
bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
|
||||
|
||||
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
|
||||
|
||||
@@ -282,7 +277,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
|
||||
createSuccessfulRemoteDataObject$(Object.assign(new Group(), mockGroup))
|
||||
);
|
||||
|
||||
bitstreamService.getUploadedFileList.and.returnValue(observableOf(mockUploadFiles));
|
||||
bitstreamService.getUploadedFilesData.and.returnValue(observableOf(mockUploadFilesData));
|
||||
|
||||
comp.onSectionInit();
|
||||
|
||||
@@ -298,12 +293,14 @@ describe('SubmissionSectionUploadComponent test suite', () => {
|
||||
expect(comp.required$.getValue()).toBe(true);
|
||||
expect(compAsAny.subs.length).toBe(2);
|
||||
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']);
|
||||
|
||||
});
|
||||
|
||||
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));
|
||||
|
||||
collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockCollection));
|
||||
@@ -324,7 +321,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
|
||||
|
||||
comp.onSectionInit();
|
||||
|
||||
expect(comp.required$.getValue()).toBe(true);
|
||||
expect(comp.required$.getValue()).toBe(true);
|
||||
|
||||
expect(compAsAny.getSectionStatus()).toBeObservable(cold('-c-d', {
|
||||
c: false,
|
||||
@@ -335,6 +332,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
|
||||
it('should properly read the section status when required is false', () => {
|
||||
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
|
||||
|
||||
bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
|
||||
collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockCollection));
|
||||
|
||||
resourcePolicyService.findByHref.and.returnValue(createSuccessfulRemoteDataObject$(mockDefaultAccessCondition));
|
||||
|
@@ -4,7 +4,8 @@ import {
|
||||
BehaviorSubject,
|
||||
combineLatest as observableCombineLatest,
|
||||
Observable,
|
||||
Subscription
|
||||
Subscription,
|
||||
combineLatest
|
||||
} from 'rxjs';
|
||||
import { distinctUntilChanged, filter, map, mergeMap, switchMap, tap } from 'rxjs/operators';
|
||||
|
||||
@@ -31,6 +32,7 @@ import { AccessConditionOption } from '../../../core/config/models/config-access
|
||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||
import { getFirstSucceededRemoteData } from '../../../core/shared/operators';
|
||||
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
||||
import { WorkspaceitemSectionUploadObject } from 'src/app/core/submission/models/workspaceitem-section-upload.model';
|
||||
|
||||
export const POLICY_DEFAULT_NO_LIST = 1; // Banner1
|
||||
export const POLICY_DEFAULT_WITH_LIST = 2; // Banner2
|
||||
@@ -58,10 +60,10 @@ export class SubmissionSectionUploadComponent extends SectionModelComponent {
|
||||
public AlertTypeEnum = AlertType;
|
||||
|
||||
/**
|
||||
* The array containing the keys of file list array
|
||||
* The uuid of primary bitstream file
|
||||
* @type {Array}
|
||||
*/
|
||||
public fileIndexes: string[] = [];
|
||||
public primaryBitstreamUUID: string | null = null;
|
||||
|
||||
/**
|
||||
* The file list
|
||||
@@ -194,27 +196,18 @@ export class SubmissionSectionUploadComponent extends SectionModelComponent {
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}),
|
||||
|
||||
// retrieve submission's bitstreams from state
|
||||
observableCombineLatest(this.configMetadataForm$,
|
||||
this.bitstreamService.getUploadedFileList(this.submissionId, this.sectionData.id)).pipe(
|
||||
filter(([configMetadataForm, fileList]: [SubmissionFormsModel, any[]]) => {
|
||||
return isNotEmpty(configMetadataForm) && isNotUndefined(fileList);
|
||||
|
||||
// retrieve submission's bitstream data from state
|
||||
combineLatest([this.configMetadataForm$,
|
||||
this.bitstreamService.getUploadedFilesData(this.submissionId, this.sectionData.id)]).pipe(
|
||||
filter(([configMetadataForm, { files }]: [SubmissionFormsModel, WorkspaceitemSectionUploadObject]) => {
|
||||
return isNotEmpty(configMetadataForm) && isNotEmpty(files);
|
||||
}),
|
||||
distinctUntilChanged())
|
||||
.subscribe(([configMetadataForm, fileList]: [SubmissionFormsModel, any[]]) => {
|
||||
this.fileList = [];
|
||||
this.fileIndexes = [];
|
||||
this.fileNames = [];
|
||||
this.changeDetectorRef.detectChanges();
|
||||
if (isNotUndefined(fileList) && fileList.length > 0) {
|
||||
fileList.forEach((file) => {
|
||||
this.fileList.push(file);
|
||||
this.fileIndexes.push(file.uuid);
|
||||
this.fileNames.push(this.getFileName(configMetadataForm, file));
|
||||
});
|
||||
}
|
||||
|
||||
this.changeDetectorRef.detectChanges();
|
||||
.subscribe(([configMetadataForm, { primary, files }]: [SubmissionFormsModel, WorkspaceitemSectionUploadObject]) => {
|
||||
this.primaryBitstreamUUID = primary;
|
||||
this.fileList = files;
|
||||
this.fileNames = Array.from(files, file => this.getFileName(configMetadataForm, 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);
|
||||
});
|
||||
});
|
||||
});
|
@@ -8,11 +8,15 @@ import { SubmissionState } from '../../submission.reducers';
|
||||
import {
|
||||
DeleteUploadedFileAction,
|
||||
EditFileDataAction,
|
||||
EditFilePrimaryBitstreamAction,
|
||||
NewUploadedFileAction
|
||||
} from '../../objects/submission-objects.actions';
|
||||
import { submissionUploadedFileFromUuidSelector, submissionUploadedFilesFromIdSelector } from '../../selectors';
|
||||
import { submissionSectionDataFromIdSelector, submissionUploadedFileFromUuidSelector, submissionUploadedFilesFromIdSelector } from '../../selectors';
|
||||
import { isUndefined } from '../../../shared/empty.util';
|
||||
import { WorkspaceitemSectionUploadFileObject } from '../../../core/submission/models/workspaceitem-section-upload-file.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.
|
||||
@@ -24,8 +28,53 @@ export class SectionUploadService {
|
||||
* Initialize service variables
|
||||
*
|
||||
* @param {Store<SubmissionState>} store
|
||||
* @param {JsonPatchOperationsBuilder} operationsBuilder
|
||||
*/
|
||||
constructor(private store: Store<SubmissionState>) {}
|
||||
constructor(private store: Store<SubmissionState>, private operationsBuilder: JsonPatchOperationsBuilder) {}
|
||||
|
||||
/**
|
||||
* Define and add an operation based on a change
|
||||
*
|
||||
* @param path
|
||||
* The path to endpoint
|
||||
* @param intitialPrimary
|
||||
* The initial primary indicator
|
||||
* @param primary
|
||||
* the new primary indicator
|
||||
* @param fileId
|
||||
* The file id
|
||||
* @returns {void}
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @param submissionId
|
||||
* The submission id
|
||||
* @param sectionId
|
||||
* The section id
|
||||
* @returns {WorkspaceitemSectionUploadObject}
|
||||
* Returns submission's bitstream data
|
||||
*/
|
||||
public getUploadedFilesData(submissionId: string, sectionId: string): Observable<WorkspaceitemSectionUploadObject> {
|
||||
return this.store.select(submissionSectionDataFromIdSelector(submissionId, sectionId)).pipe(
|
||||
map((state) => state),
|
||||
distinctUntilChanged());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return submission's bitstream list from state
|
||||
@@ -104,6 +153,22 @@ export class SectionUploadService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update primary bitstream into the state
|
||||
*
|
||||
* @param submissionId
|
||||
* The submission id
|
||||
* @param sectionId
|
||||
* The section id
|
||||
* @param fileUUID
|
||||
* The bitstream UUID
|
||||
*/
|
||||
public updateFilePrimaryBitstream(submissionId: string, sectionId: string, fileUUID: string | null) {
|
||||
this.store.dispatch(
|
||||
new EditFilePrimaryBitstreamAction(submissionId, sectionId, fileUUID)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update bitstream metadata into the state
|
||||
*
|
||||
|
Reference in New Issue
Block a user