[CST-12043] feature: add primary bitstream switch

This commit is contained in:
Vlad Nouski
2023-10-20 16:07:31 +02:00
parent a55eb97a6a
commit ffdeca69f4
13 changed files with 303 additions and 77 deletions

View File

@@ -1,9 +1,9 @@
<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>
<button type="button" class="close" (click)="onModalClose()" aria-label="Close" [disabled]="isSaving">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">

View File

@@ -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';
@@ -40,12 +42,10 @@ import { SubmissionService } from '../../../../submission.service';
import { FormService } from '../../../../../shared/form/form.service';
import { FormComponent } from '../../../../../shared/form/form.component';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { filter, mergeMap, take } from 'rxjs/operators';
import { filter, mergeMap, take, tap } 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
@@ -54,9 +54,10 @@ import {
JsonPatchOperationPathCombiner
} from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner';
import { SectionUploadService } from '../../section-upload.service';
import { Subscription } from 'rxjs';
import { Observable, Subject, 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';
/**
* This component represents the edit form for bitstream
@@ -74,6 +75,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}
@@ -160,6 +168,12 @@ export class SubmissionSectionUploadFileEditComponent
protected subscriptions: Subscription[] = [];
private onSaveBitstreamData$: Subject<any> = new Subject();
get saveBitstreamDataEvent$(): Observable<void> {
return this.onSaveBitstreamData$.asObservable();
}
/**
* Initialize instance variables
*
@@ -191,6 +205,10 @@ export class SubmissionSectionUploadFileEditComponent
* The form model
*/
public initModelData(formModel: DynamicFormControlModel[]) {
const primaryBitstreammodel: any = this.formBuilderService.findById('primaryBitstream', 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 +309,9 @@ export class SubmissionSectionUploadFileEditComponent
])
});
const formModel: DynamicFormControlModel[] = [];
formModel.push(new DynamicCustomSwitchModel(BITSTREAM_FORM_PRIMARY, BITSTREAM_METADATA_FORM_GROUP_LAYOUT));
const metadataGroupModelConfig = Object.assign({}, BITSTREAM_METADATA_FORM_GROUP_CONFIG);
metadataGroupModelConfig.group = this.formBuilderService.modelFromConfiguration(
this.submissionId,
@@ -386,25 +407,29 @@ 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(
const subscription = this.formService.isValid(this.formId).pipe(
take(1),
filter((isValid) => isValid),
mergeMap(() => this.formService.getFormData(this.formId)),
take(1),
mergeMap((formData: any) => {
tap((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);
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 +438,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 = [];
formData.accessConditions
@@ -467,29 +492,11 @@ 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);
})
).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);
).subscribe((formData) => this.onSaveBitstreamData$.next(formData));
this.subscriptions.push(subscription);
}
private unsubscribeAll() {

View File

@@ -4,6 +4,7 @@ import {
DynamicFormControlLayout,
DynamicFormGroupModelConfig,
DynamicSelectModelConfig,
DynamicSwitchModelConfig,
MATCH_ENABLED,
OR_OPERATOR,
} from '@ng-dynamic-forms/core';
@@ -56,6 +57,18 @@ export const BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT: DynamicFormControlLayo
label: 'col-form-label name-label'
}
};
export const BITSTREAM_FORM_PRIMARY_LAYOUT: DynamicFormControlLayout = {
grid: {
host: 'col-6'
}
};
export const BITSTREAM_FORM_PRIMARY: DynamicSwitchModelConfig = {
id: 'primaryBitstream',
name: 'primaryBitstream',
label: 'Primary bitstream',
};
export const BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_CONFIG: DynamicDatePickerModelConfig = {
id: 'startDate',

View File

@@ -1,6 +1,13 @@
<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}}" [(ngModel)]="isPrimary" (change)="togglePrimaryBitstream()">
<label class="custom-control-label" for="primaryBitstream{{fileIndex}}"></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>

View File

@@ -10,7 +10,7 @@ import {
} from '@angular/core';
import { BehaviorSubject, Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
import { filter, switchMap, tap } from 'rxjs/operators';
import { DynamicFormControlModel, } from '@ng-dynamic-forms/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
@@ -27,6 +27,9 @@ import { SubmissionJsonPatchOperationsService } from '../../../../core/submissio
import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component';
import { Bitstream } from '../../../../core/shared/bitstream.model';
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
@@ -37,6 +40,19 @@ 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() set isPrimaryBitstream(status: boolean | null) {
this.initialPrimaryStatus = status;
this.isPrimary = status;
}
private initialPrimaryStatus = false;
isPrimary = false;
/**
* The list of available access condition
@@ -137,6 +153,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}
@@ -165,6 +187,7 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
private cdr: ChangeDetectorRef,
private formService: FormService,
private halService: HALEndpointService,
private notificationsService: NotificationsService,
private modalService: NgbModal,
private operationsBuilder: JsonPatchOperationsBuilder,
private operationsService: SubmissionJsonPatchOperationsService,
@@ -197,7 +220,7 @@ 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.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId);
this.loadFormMetadata();
}
@@ -226,6 +249,23 @@ 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() {
const options: NgbModalOptions = {
@@ -247,7 +287,52 @@ 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;
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[]) {
const section = result[0].sections[this.sectionId];
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);
}
ngOnDestroy(): void {
@@ -273,7 +358,13 @@ 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) {
// TODO: uncoment
// this.operationsBuilder.remove(this.pathCombiner.getPath('primary'));
}
this.subscriptions.push(this.operationsService.jsonPatchByResourceID(
this.submissionService.getSubmissionObjectLinkName(),
this.submissionId,

View File

@@ -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',