From ad52dc815ed5bcd7e468a299a24a58eceeaf0202 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Sun, 17 Mar 2019 16:30:19 +0100 Subject: [PATCH 01/27] Added mandatory property for date and group selection in bitstream metadata edit form --- .../upload/file/edit/files-edit.model.ts | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/app/submission/sections/upload/file/edit/files-edit.model.ts b/src/app/submission/sections/upload/file/edit/files-edit.model.ts index 185d3a8b89..ec72adf786 100644 --- a/src/app/submission/sections/upload/file/edit/files-edit.model.ts +++ b/src/app/submission/sections/upload/file/edit/files-edit.model.ts @@ -56,7 +56,14 @@ export const BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_CONFIG: DynamicDatePicke connective: 'OR', when: [] } - ] + ], + required: true, + validators: { + required: null + }, + errorMessages: { + required: 'submission.sections.upload.form.date-required' + } }; export const BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_LAYOUT: DynamicFormControlLayout = { element: { @@ -80,7 +87,14 @@ export const BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_CONFIG: DynamicDatePickerM connective: 'OR', when: [] } - ] + ], + required: true, + validators: { + required: null + }, + errorMessages: { + required: 'submission.sections.upload.form.date-required' + } }; export const BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_LAYOUT: DynamicFormControlLayout = { element: { @@ -102,7 +116,14 @@ export const BITSTREAM_FORM_ACCESS_CONDITION_GROUPS_CONFIG: DynamicSelectModelCo connective: 'OR', when: [] } - ] + ], + required: true, + validators: { + required: null + }, + errorMessages: { + required: 'submission.sections.upload.form.group-required' + } }; export const BITSTREAM_FORM_ACCESS_CONDITION_GROUPS_LAYOUT: DynamicFormControlLayout = { element: { From e90d80bce70fed6197e9fb66801eaa5e4e1b6a59 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Sun, 17 Mar 2019 16:30:43 +0100 Subject: [PATCH 02/27] Added mandatory property for date and group selection in bitstream metadata edit form --- resources/i18n/en.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 9217cab619..42847f8ec8 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -750,7 +750,9 @@ "from-placeholder": "From", "until-label": "Access grant until", "until-placeholder": "Until", - "group-label": "Group" + "group-label": "Group", + "group-required": "Group is required.", + "date-required": "Date is required." }, "save-metadata": "Save metadata", "undo": "Cancel", From fa218abada576a032fea095cc89f7313ef424d1b Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Sun, 17 Mar 2019 16:52:07 +0100 Subject: [PATCH 03/27] Fixed property groups form Group and EPerson models and added comments --- src/app/core/eperson/models/eperson.model.ts | 30 ++++++++++++++- src/app/core/eperson/models/group.model.ts | 18 ++++++++- .../models/normalized-eperson.model.ts | 37 ++++++++++++++++--- .../eperson/models/normalized-group.model.ts | 23 ++++++++++-- 4 files changed, 96 insertions(+), 12 deletions(-) diff --git a/src/app/core/eperson/models/eperson.model.ts b/src/app/core/eperson/models/eperson.model.ts index 7d2138b633..32286929ee 100644 --- a/src/app/core/eperson/models/eperson.model.ts +++ b/src/app/core/eperson/models/eperson.model.ts @@ -1,22 +1,50 @@ +import { Observable } from 'rxjs'; + import { DSpaceObject } from '../../shared/dspace-object.model'; import { Group } from './group.model'; +import { RemoteData } from '../../data/remote-data'; +import { PaginatedList } from '../../data/paginated-list'; export class EPerson extends DSpaceObject { + /** + * A string representing the unique handle of this Collection + */ public handle: string; - public groups: Group[]; + /** + * List of Groups that this EPerson belong to + */ + public groups: Observable>>; + /** + * A string representing the netid of this EPerson + */ public netid: string; + /** + * A string representing the last active date for this EPerson + */ public lastActive: string; + /** + * A boolean representing if this EPerson can log in + */ public canLogIn: boolean; + /** + * The EPerson email address + */ public email: string; + /** + * A boolean representing if this EPerson require certificate + */ public requireCertificate: boolean; + /** + * A boolean representing if this EPerson registered itself + */ public selfRegistered: boolean; /** Getter to retrieve the EPerson's full name as a string */ diff --git a/src/app/core/eperson/models/group.model.ts b/src/app/core/eperson/models/group.model.ts index 27fa0ef595..91ce5d90f3 100644 --- a/src/app/core/eperson/models/group.model.ts +++ b/src/app/core/eperson/models/group.model.ts @@ -1,12 +1,28 @@ +import { Observable } from 'rxjs'; + import { DSpaceObject } from '../../shared/dspace-object.model'; +import { PaginatedList } from '../../data/paginated-list'; +import { RemoteData } from '../../data/remote-data'; export class Group extends DSpaceObject { - public groups: Group[]; + /** + * List of Groups that this Group belong to + */ + public groups: Observable>>; + /** + * A string representing the unique handle of this Group + */ public handle: string; + /** + * A string representing the name of this Group + */ public name: string; + /** + * A string representing the name of this Group is permanent + */ public permanent: boolean; } diff --git a/src/app/core/eperson/models/normalized-eperson.model.ts b/src/app/core/eperson/models/normalized-eperson.model.ts index 6bb66e93e6..ad4b20ee80 100644 --- a/src/app/core/eperson/models/normalized-eperson.model.ts +++ b/src/app/core/eperson/models/normalized-eperson.model.ts @@ -1,37 +1,62 @@ -import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize'; +import { autoserialize, deserialize, inheritSerialization } from 'cerialize'; + import { CacheableObject } from '../../cache/object-cache.reducer'; import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model'; import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model'; import { EPerson } from './eperson.model'; -import { mapsTo } from '../../cache/builders/build-decorators'; -import { Group } from './group.model'; -import { NormalizedGroup } from './normalized-group.model'; +import { mapsTo, relationship } from '../../cache/builders/build-decorators'; +import { ResourceType } from '../../shared/resource-type'; @mapsTo(EPerson) @inheritSerialization(NormalizedDSpaceObject) export class NormalizedEPerson extends NormalizedDSpaceObject implements CacheableObject, ListableObject { + /** + * A string representing the unique handle of this EPerson + */ @autoserialize public handle: string; - @autoserializeAs(NormalizedGroup) - groups: Group[]; + /** + * List of Groups that this EPerson belong to + */ + @deserialize + @relationship(ResourceType.Group, true) + groups: string[]; + /** + * A string representing the netid of this EPerson + */ @autoserialize public netid: string; + /** + * A string representing the last active date for this EPerson + */ @autoserialize public lastActive: string; + /** + * A boolean representing if this EPerson can log in + */ @autoserialize public canLogIn: boolean; + /** + * The EPerson email address + */ @autoserialize public email: string; + /** + * A boolean representing if this EPerson require certificate + */ @autoserialize public requireCertificate: boolean; + /** + * A boolean representing if this EPerson registered itself + */ @autoserialize public selfRegistered: boolean; } diff --git a/src/app/core/eperson/models/normalized-group.model.ts b/src/app/core/eperson/models/normalized-group.model.ts index 8cfd24524c..f86bec8628 100644 --- a/src/app/core/eperson/models/normalized-group.model.ts +++ b/src/app/core/eperson/models/normalized-group.model.ts @@ -1,23 +1,38 @@ -import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize'; +import { autoserialize, deserialize, inheritSerialization } from 'cerialize'; + import { CacheableObject } from '../../cache/object-cache.reducer'; import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model'; import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model'; -import { mapsTo } from '../../cache/builders/build-decorators'; +import { mapsTo, relationship } from '../../cache/builders/build-decorators'; import { Group } from './group.model'; +import { ResourceType } from '../../shared/resource-type'; @mapsTo(Group) @inheritSerialization(NormalizedDSpaceObject) export class NormalizedGroup extends NormalizedDSpaceObject implements CacheableObject, ListableObject { - @autoserializeAs(NormalizedGroup) - groups: Group[]; + /** + * List of Groups that this Group belong to + */ + @deserialize + @relationship(ResourceType.Group, true) + groups: string[]; + /** + * A string representing the unique handle of this Group + */ @autoserialize public handle: string; + /** + * A string representing the name of this Group + */ @autoserialize public name: string; + /** + * A string representing the name of this Group is permanent + */ @autoserialize public permanent: boolean; } From e90212703e37d4f99873a0993052fcbcd42bfa99 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Sun, 17 Mar 2019 16:54:00 +0100 Subject: [PATCH 04/27] Fixed validateAllFormFields FormService method --- src/app/shared/form/form.service.ts | 7 ++++--- src/app/shared/mocks/mock-form-service.ts | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/app/shared/form/form.service.ts b/src/app/shared/form/form.service.ts index 9356f86e8c..ee354d504f 100644 --- a/src/app/shared/form/form.service.ts +++ b/src/app/shared/form/form.service.ts @@ -1,6 +1,6 @@ import { map, distinctUntilChanged, filter } from 'rxjs/operators'; import { Inject, Injectable } from '@angular/core'; -import { AbstractControl, FormControl, FormGroup } from '@angular/forms'; +import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms'; import { Observable } from 'rxjs'; import { select, Store } from '@ngrx/store'; @@ -82,12 +82,13 @@ export class FormService { /** * Method to validate form's fields */ - public validateAllFormFields(formGroup: FormGroup) { + public validateAllFormFields(formGroup: FormGroup | FormArray) { Object.keys(formGroup.controls).forEach((field) => { const control = formGroup.get(field); if (control instanceof FormControl) { control.markAsTouched({ onlySelf: true }); - } else if (control instanceof FormGroup) { + control.markAsDirty({ onlySelf: true }); + } else if (control instanceof FormGroup || control instanceof FormArray) { this.validateAllFormFields(control); } }); diff --git a/src/app/shared/mocks/mock-form-service.ts b/src/app/shared/mocks/mock-form-service.ts index 31455f03da..bae76eb69f 100644 --- a/src/app/shared/mocks/mock-form-service.ts +++ b/src/app/shared/mocks/mock-form-service.ts @@ -12,8 +12,8 @@ export function getMockFormService( getForm: observableOf({}), getUniqueId: id$, resetForm: {}, - validateAllFormFields: {}, - isValid: observableOf(true), + validateAllFormFields: jasmine.createSpy('validateAllFormFields'), + isValid: jasmine.createSpy('isValid'), isFormInitialized: observableOf(true) }); From 1bdc1d799df8a465656f998fc928b880b3b147b6 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Sun, 17 Mar 2019 16:57:24 +0100 Subject: [PATCH 05/27] Added form validation before saving bitstream data --- .../form/section-form.component.spec.ts | 1 + .../upload/file/file.component.spec.ts | 30 +++++++++++++++++-- .../sections/upload/file/file.component.ts | 18 +++++++---- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/app/submission/sections/form/section-form.component.spec.ts b/src/app/submission/sections/form/section-form.component.spec.ts index ff134c620a..5e559a9e4a 100644 --- a/src/app/submission/sections/form/section-form.component.spec.ts +++ b/src/app/submission/sections/form/section-form.component.spec.ts @@ -236,6 +236,7 @@ describe('FormSectionComponent test suite', () => { it('should init section properly', () => { const sectionData = {}; + formService.isValid.and.returnValue(observableOf(true)); formConfigService.getConfigByHref.and.returnValue(observableOf(formConfigData)); sectionsServiceStub.getSectionData.and.returnValue(observableOf(sectionData)); spyOn(comp, 'initForm'); diff --git a/src/app/submission/sections/upload/file/file.component.spec.ts b/src/app/submission/sections/upload/file/file.component.spec.ts index 8557efd451..4500b4bfe9 100644 --- a/src/app/submission/sections/upload/file/file.component.spec.ts +++ b/src/app/submission/sections/upload/file/file.component.spec.ts @@ -35,6 +35,8 @@ import { POLICY_DEFAULT_WITH_LIST } from '../section-upload.component'; import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/builder/json-patch-operation-path-combiner'; import { getMockSectionUploadService } from '../../../../shared/mocks/mock-section-upload.service'; import { FormFieldMetadataValueObject } from '../../../../shared/form/builder/models/form-field-metadata-value.model'; +import { Group } from '../../../../core/eperson/models/group.model'; +import { UploadSectionFileEditComponent } from './edit/file-edit.component'; function getMockFileService(): FileService { return jasmine.createSpyObj('FileService', { @@ -61,7 +63,10 @@ describe('UploadSectionFileComponent test suite', () => { const sectionId = 'upload'; const collectionId = mockSubmissionCollectionId; const availableAccessConditionOptions = mockUploadConfigResponse.accessConditionOptions; - const availableGroupsMap = new Map([[mockGroup.id, { name: mockGroup.name, uuid: mockGroup.uuid }]]); + const availableGroupsMap: Map = new Map([ + [mockUploadConfigResponse.accessConditionOptions[1].name, [mockGroup as any]], + [mockUploadConfigResponse.accessConditionOptions[2].name, [mockGroup as any]], + ]); const collectionPolicyType = POLICY_DEFAULT_WITH_LIST; const fileIndex = '0'; const fileName = '123456-test-upload.jpg'; @@ -98,7 +103,8 @@ describe('UploadSectionFileComponent test suite', () => { { provide: SectionUploadService, useValue: getMockSectionUploadService() }, ChangeDetectorRef, NgbModal, - UploadSectionFileComponent + UploadSectionFileComponent, + UploadSectionFileEditComponent ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents().then(); @@ -228,10 +234,14 @@ describe('UploadSectionFileComponent test suite', () => { expect(fileService.downloadFile).toHaveBeenCalled() })); - it('should download Bitstream File properly', fakeAsync(() => { + it('should save Bitstream File data properly when form is valid', fakeAsync(() => { + compAsAny.fileEditComp = TestBed.get(UploadSectionFileEditComponent); + compAsAny.fileEditComp.formRef = {formGroup: null}; compAsAny.pathCombiner = pathCombiner; const event = new Event('click', null); spyOn(comp, 'switchMode'); + formService.validateAllFormFields.and.callFake(() => null); + formService.isValid.and.returnValue(observableOf(true)); formService.getFormData.and.returnValue(observableOf(mockFileFormData)); const response = [ @@ -279,6 +289,20 @@ describe('UploadSectionFileComponent test suite', () => { })); + it('should not save Bitstream File data properly when form is not valid', fakeAsync(() => { + compAsAny.fileEditComp = TestBed.get(UploadSectionFileEditComponent); + compAsAny.fileEditComp.formRef = {formGroup: null}; + compAsAny.pathCombiner = pathCombiner; + const event = new Event('click', null); + spyOn(comp, 'switchMode'); + formService.validateAllFormFields.and.callFake(() => null); + formService.isValid.and.returnValue(observableOf(false)); + + expect(comp.switchMode).not.toHaveBeenCalled(); + expect(uploadService.updateFileData).not.toHaveBeenCalled(); + + })); + it('should retrieve Value From Field properly', () => { let field; expect(compAsAny.retrieveValueFromField(field)).toBeUndefined(); diff --git a/src/app/submission/sections/upload/file/file.component.ts b/src/app/submission/sections/upload/file/file.component.ts index adf035bf23..71d4585e85 100644 --- a/src/app/submission/sections/upload/file/file.component.ts +++ b/src/app/submission/sections/upload/file/file.component.ts @@ -1,7 +1,7 @@ -import { ChangeDetectorRef, Component, Input, OnChanges, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; -import { filter, first, flatMap } from 'rxjs/operators'; +import { filter, first, flatMap, take } from 'rxjs/operators'; import { DynamicFormControlModel, } from '@ng-dynamic-forms/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; @@ -20,6 +20,8 @@ import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service import { SubmissionJsonPatchOperationsService } from '../../../../core/submission/submission-json-patch-operations.service'; import { SubmissionObject } from '../../../../core/submission/models/submission-object.model'; import { WorkspaceitemSectionUploadObject } from '../../../../core/submission/models/workspaceitem-section-upload.model'; +import { UploadSectionFileEditComponent } from './edit/file-edit.component'; +import { Group } from '../../../../core/eperson/models/group.model'; @Component({ selector: 'ds-submission-upload-section-file', @@ -29,7 +31,7 @@ import { WorkspaceitemSectionUploadObject } from '../../../../core/submission/mo export class UploadSectionFileComponent implements OnChanges, OnInit { @Input() availableAccessConditionOptions: any[]; - @Input() availableAccessConditionGroups: Map; + @Input() availableAccessConditionGroups: Map; @Input() collectionId; @Input() collectionPolicyType; @Input() configMetadataForm: SubmissionFormsModel; @@ -48,6 +50,8 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { protected pathCombiner: JsonPatchOperationPathCombiner; protected subscriptions = []; + @ViewChild(UploadSectionFileEditComponent) fileEditComp: UploadSectionFileEditComponent; + constructor(private cdr: ChangeDetectorRef, private fileService: FileService, private formService: FormService, @@ -116,8 +120,12 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { public saveBitstreamData(event) { event.preventDefault(); - this.subscriptions.push(this.formService.getFormData(this.formId).pipe( - first(), + this.formService.validateAllFormFields(this.fileEditComp.formRef.formGroup); + this.subscriptions.push(this.formService.isValid(this.formId).pipe( + take(1), + filter((isValid) => isValid), + flatMap(() => this.formService.getFormData(this.formId)), + take(1), flatMap((formData: any) => { Object.keys((formData.metadata)) .filter((key) => isNotEmpty(formData.metadata[key])) From c6d16abbbdf0b33523eb58a88f36487ebcb5f653 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Sun, 17 Mar 2019 17:01:47 +0100 Subject: [PATCH 06/27] Fixed issue with bitstream edit form that not show access condition groups when were more than one --- .../config-access-condition-option.model.ts | 5 ++ ...ynamic-form-control-container.component.ts | 4 +- src/app/shared/mocks/mock-submission.ts | 5 +- .../file/edit/file-edit.component.spec.ts | 6 +- .../upload/file/edit/file-edit.component.ts | 33 +++++++--- .../upload/section-upload.component.spec.ts | 16 +++-- .../upload/section-upload.component.ts | 63 ++++++++++++------- 7 files changed, 86 insertions(+), 46 deletions(-) diff --git a/src/app/core/config/models/config-access-condition-option.model.ts b/src/app/core/config/models/config-access-condition-option.model.ts index 0c25d9aa0f..46bf1b60ce 100644 --- a/src/app/core/config/models/config-access-condition-option.model.ts +++ b/src/app/core/config/models/config-access-condition-option.model.ts @@ -13,6 +13,11 @@ export class AccessConditionOption { */ groupUUID: string; + /** + * The uuid of the Group that contains set of groups this Resource Policy applies to + */ + selectGroupUUID: string; + /** * A boolean representing if this Access Condition has a start date */ diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts index fc2c788c02..b24f4a4865 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts @@ -180,9 +180,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo if (changes) { super.ngOnChanges(changes); if (this.model && this.model.placeholder) { - this.translateService.get(this.model.placeholder).subscribe((placeholder) => { - this.model.placeholder = placeholder; - }) + this.model.placeholder = this.translateService.instant(this.model.placeholder); } } } diff --git a/src/app/shared/mocks/mock-submission.ts b/src/app/shared/mocks/mock-submission.ts index 26f9c757ca..922e6ad02d 100644 --- a/src/app/shared/mocks/mock-submission.ts +++ b/src/app/shared/mocks/mock-submission.ts @@ -3,6 +3,7 @@ import { SubmissionDefinitionsModel } from '../../core/config/models/config-subm import { PaginatedList } from '../../core/data/paginated-list'; import { PageInfo } from '../../core/shared/page-info.model'; import { FormFieldMetadataValueObject } from '../form/builder/models/form-field-metadata-value.model'; +import { Group } from '../../core/eperson/models/group.model'; export const mockSectionsData = { traditionalpageone:{ @@ -1364,7 +1365,7 @@ export const mockAccessConditionOptions = [ } ]; -export const mockGroup = { +export const mockGroup = Object.assign(new Group(), { handle: null, permanent: true, self: 'https://rest.api/dspace-spring-rest/api/eperson/groups/123456-g1', @@ -1386,7 +1387,7 @@ export const mockGroup = { }, page: [] } -}; +}); export const mockUploadFiles = [ { diff --git a/src/app/submission/sections/upload/file/edit/file-edit.component.spec.ts b/src/app/submission/sections/upload/file/edit/file-edit.component.spec.ts index 46ed1a57af..3809730abe 100644 --- a/src/app/submission/sections/upload/file/edit/file-edit.component.spec.ts +++ b/src/app/submission/sections/upload/file/edit/file-edit.component.spec.ts @@ -30,6 +30,7 @@ import { FormService } from '../../../../../shared/form/form.service'; import { GLOBAL_CONFIG } from '../../../../../../config'; import { MOCK_SUBMISSION_CONFIG } from '../../../../../shared/testing/mock-submission-config'; import { getMockFormService } from '../../../../../shared/mocks/mock-form-service'; +import { Group } from '../../../../../core/eperson/models/group.model'; describe('UploadSectionFileEditComponent test suite', () => { @@ -44,7 +45,10 @@ describe('UploadSectionFileEditComponent test suite', () => { const sectionId = 'upload'; const collectionId = mockSubmissionCollectionId; const availableAccessConditionOptions = mockUploadConfigResponse.accessConditionOptions; - const availableGroupsMap = new Map([[mockGroup.id, { name: mockGroup.name, uuid: mockGroup.uuid }]]); + const availableGroupsMap: Map = new Map([ + [mockUploadConfigResponse.accessConditionOptions[1].name, [mockGroup as any]], + [mockUploadConfigResponse.accessConditionOptions[2].name, [mockGroup as any]], + ]); const collectionPolicyType = POLICY_DEFAULT_WITH_LIST; const configMetadataForm: any = mockUploadConfigResponse.metadata; const fileIndex = '0'; diff --git a/src/app/submission/sections/upload/file/edit/file-edit.component.ts b/src/app/submission/sections/upload/file/edit/file-edit.component.ts index c44383abc6..ed30b850ac 100644 --- a/src/app/submission/sections/upload/file/edit/file-edit.component.ts +++ b/src/app/submission/sections/upload/file/edit/file-edit.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, Input, OnChanges } from '@angular/core'; +import { ChangeDetectorRef, Component, Input, OnChanges, ViewChild } from '@angular/core'; import { WorkspaceitemSectionUploadFileObject } from '../../../../../core/submission/models/workspaceitem-section-upload-file.model'; import { @@ -33,6 +33,10 @@ import { SubmissionFormsModel } from '../../../../../core/config/models/config-s import { FormFieldModel } from '../../../../../shared/form/builder/models/form-field.model'; import { AccessConditionOption } from '../../../../../core/config/models/config-access-condition-option.model'; import { SubmissionService } from '../../../../submission.service'; +import { FormService } from '../../../../../shared/form/form.service'; +import { FormComponent } from '../../../../../shared/form/form.component'; +import { FormControl } from '@angular/forms'; +import { Group } from '../../../../../core/eperson/models/group.model'; @Component({ selector: 'ds-submission-upload-section-file-edit', @@ -41,7 +45,7 @@ import { SubmissionService } from '../../../../submission.service'; export class UploadSectionFileEditComponent implements OnChanges { @Input() availableAccessConditionOptions: any[]; - @Input() availableAccessConditionGroups: Map; + @Input() availableAccessConditionGroups: Map; @Input() collectionId; @Input() collectionPolicyType; @Input() configMetadataForm: SubmissionFormsModel; @@ -54,8 +58,11 @@ export class UploadSectionFileEditComponent implements OnChanges { public formModel: DynamicFormControlModel[]; + @ViewChild('formRef') public formRef: FormComponent; + constructor(private cdr: ChangeDetectorRef, private formBuilderService: FormBuilderService, + private formService: FormService, private submissionService: SubmissionService) { } @@ -153,8 +160,8 @@ export class UploadSectionFileEditComponent implements OnChanges { .forEach((key) => { const metadataModel: any = this.formBuilderService.findById(key, formModel, index); if (metadataModel) { - if (key === 'groupUUID') { - this.availableAccessConditionGroups.forEach((group) => { + if (key === 'groupUUID' && this.availableAccessConditionGroups.get(accessCondition.name)) { + this.availableAccessConditionGroups.get(accessCondition.name).forEach((group) => { metadataModel.options.push({ label: group.name, value: group.uuid @@ -189,14 +196,20 @@ export class UploadSectionFileEditComponent implements OnChanges { if (isNotEmpty(accessCondition)) { const showGroups: boolean = accessCondition.hasStartDate === true || accessCondition.hasEndDate === true; - const groupControl = control.parent.get('groupUUID'); - const startDateControl = control.parent.get('startDate'); - const endDateControl = control.parent.get('endDate'); + const groupControl: FormControl = control.parent.get('groupUUID'); + const startDateControl: FormControl = control.parent.get('startDate'); + const endDateControl: FormControl = control.parent.get('endDate'); + + // Clear previous state + groupControl.markAsUntouched(); + startDateControl.markAsUntouched(); + endDateControl.markAsUntouched(); // Clear previous values if (showGroups) { groupControl.setValue(null); } else { + groupControl.clearValidators(); groupControl.setValue(accessCondition.groupUUID); } startDateControl.setValue(null); @@ -204,15 +217,15 @@ export class UploadSectionFileEditComponent implements OnChanges { endDateControl.setValue(null); if (showGroups) { - if (isNotUndefined(accessCondition.groupUUID)) { + if (isNotUndefined(accessCondition.groupUUID) || isNotUndefined(accessCondition.selectGroupUUID)) { const groupOptions = []; - if (isNotUndefined(this.availableAccessConditionGroups.get(accessCondition.groupUUID))) { + if (isNotUndefined(this.availableAccessConditionGroups.get(accessCondition.name))) { const groupModel = this.formBuilderService.findById( 'groupUUID', (model.parent as DynamicFormArrayGroupModel).group) as DynamicSelectModel; - this.availableAccessConditionGroups.forEach((group) => { + this.availableAccessConditionGroups.get(accessCondition.name).forEach((group) => { groupOptions.push({ label: group.name, value: group.uuid diff --git a/src/app/submission/sections/upload/section-upload.component.spec.ts b/src/app/submission/sections/upload/section-upload.component.spec.ts index 1c64d673d3..345b78d358 100644 --- a/src/app/submission/sections/upload/section-upload.component.spec.ts +++ b/src/app/submission/sections/upload/section-upload.component.spec.ts @@ -204,15 +204,17 @@ describe('UploadSectionComponent test suite', () => { comp.onSectionInit(); - const expectedGroupsMap = new Map(); - expectedGroupsMap.set(mockGroup.id, { name: mockGroup.name, uuid: mockGroup.uuid }); + 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); expect(comp.availableAccessConditionOptions).toEqual(mockUploadConfigResponse.accessConditionOptions as any); expect(compAsAny.subs.length).toBe(2); - expect(compAsAny.availableGroups.size).toBe(1); + expect(compAsAny.availableGroups.size).toBe(2); expect(compAsAny.availableGroups).toEqual(expectedGroupsMap); expect(compAsAny.fileList).toEqual([]); expect(compAsAny.fileIndexes).toEqual([]); @@ -248,15 +250,17 @@ describe('UploadSectionComponent test suite', () => { comp.onSectionInit(); - const expectedGroupsMap = new Map(); - expectedGroupsMap.set(mockGroup.id, { name: mockGroup.name, uuid: mockGroup.uuid }); + 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); expect(comp.availableAccessConditionOptions).toEqual(mockUploadConfigResponse.accessConditionOptions as any); expect(compAsAny.subs.length).toBe(2); - expect(compAsAny.availableGroups.size).toBe(1); + expect(compAsAny.availableGroups.size).toBe(2); expect(compAsAny.availableGroups).toEqual(expectedGroupsMap); expect(compAsAny.fileList).toEqual(mockUploadFiles); expect(compAsAny.fileIndexes).toEqual(['123456-test-upload']); diff --git a/src/app/submission/sections/upload/section-upload.component.ts b/src/app/submission/sections/upload/section-upload.component.ts index 1caff1b726..ff620cfa65 100644 --- a/src/app/submission/sections/upload/section-upload.component.ts +++ b/src/app/submission/sections/upload/section-upload.component.ts @@ -23,10 +23,16 @@ import { SubmissionService } from '../../submission.service'; import { Collection } from '../../../core/shared/collection.model'; import { ResourcePolicy } from '../../../core/shared/resource-policy.model'; import { AccessConditionOption } from '../../../core/config/models/config-access-condition-option.model'; +import { PaginatedList } from '../../../core/data/paginated-list'; export const POLICY_DEFAULT_NO_LIST = 1; // Banner1 export const POLICY_DEFAULT_WITH_LIST = 2; // Banner2 +export interface AccessConditionGroupsMapEntry { + accessCondition: string; + groups: Group[] +} + @Component({ selector: 'ds-submission-section-upload', styleUrls: ['./section-upload.component.scss'], @@ -62,7 +68,7 @@ export class UploadSectionComponent extends SectionModelComponent { /* * List of Groups available for every access condition */ - protected availableGroups: Map; // Groups for any policy + protected availableGroups: Map; // Groups for any policy protected subs = []; @@ -117,38 +123,47 @@ export class UploadSectionComponent extends SectionModelComponent { : POLICY_DEFAULT_NO_LIST; this.availableGroups = new Map(); - const groups$ = []; + const mapGroups$: Array> = []; // Retrieve Groups for accessConditionPolicies this.availableAccessConditionOptions.forEach((accessCondition: AccessConditionOption) => { if (accessCondition.hasEndDate === true || accessCondition.hasStartDate === true) { - groups$.push( - this.groupService.findById(accessCondition.groupUUID).pipe( - find((rd: RemoteData) => !rd.isResponsePending && rd.hasSucceeded)) - ); + if (accessCondition.groupUUID) { + mapGroups$.push( + this.groupService.findById(accessCondition.groupUUID).pipe( + find((rd: RemoteData) => !rd.isResponsePending && rd.hasSucceeded), + map((rd: RemoteData) => ({ + accessCondition: accessCondition.name, + groups: [rd.payload] + } as AccessConditionGroupsMapEntry))) + ); + } else if (accessCondition.selectGroupUUID) { + mapGroups$.push( + this.groupService.findById(accessCondition.selectGroupUUID).pipe( + find((rd: RemoteData) => !rd.isResponsePending && rd.hasSucceeded), + tap((group: RemoteData) => console.log(group.payload.groups)), + flatMap((group: RemoteData) => group.payload.groups), + find((rd: RemoteData>) => !rd.isResponsePending && rd.hasSucceeded), + tap((group) => console.log(group)), + map((rd: RemoteData>) => ({ + accessCondition: accessCondition.name, + groups: rd.payload.page + } as AccessConditionGroupsMapEntry)) + )); + } } }); - return groups$; + return mapGroups$; }), - flatMap((group) => group), - reduce((acc: Group[], group: RemoteData) => { - acc.push(group.payload); + flatMap((entry) => entry), + reduce((acc: any[], entry: AccessConditionGroupsMapEntry) => { + acc.push(entry); return acc; }, []), - ).subscribe((groups: Group[]) => { - groups.forEach((group: Group) => { - if (isUndefined(this.availableGroups.get(group.uuid))) { - if (Array.isArray(group.groups)) { - const groupArrayData = []; - for (const groupData of group.groups) { - groupArrayData.push({ name: groupData.name, uuid: groupData.uuid }); - } - this.availableGroups.set(group.uuid, groupArrayData); - } else { - this.availableGroups.set(group.uuid, { name: group.name, uuid: group.uuid }); - } - } + ).subscribe((entries: AccessConditionGroupsMapEntry[]) => { + console.log(entries); + entries.forEach((entry: AccessConditionGroupsMapEntry) => { + this.availableGroups.set(entry.accessCondition, entry.groups); }); - this.changeDetectorRef.detectChanges(); }) , From d90f69d15efe731859248ff12fa5e5a78c93af37 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Sun, 17 Mar 2019 17:16:53 +0100 Subject: [PATCH 07/27] renamed section upload inner components --- ...=> section-upload-access-conditions.component.html} | 0 ...s => section-upload-access-conditions.component.ts} | 6 +++--- ...nt.html => section-upload-file-edit.component.html} | 0 ...c.ts => section-upload-file-edit.component.spec.ts} | 2 +- ...ponent.ts => section-upload-file-edit.component.ts} | 4 ++-- ...edit.model.ts => section-upload-file-edit.model.ts} | 0 ...mponent.html => section-upload-file.component.html} | 0 ...mponent.scss => section-upload-file.component.scss} | 0 ...t.spec.ts => section-upload-file.component.spec.ts} | 4 ++-- ...e.component.ts => section-upload-file.component.ts} | 6 +++--- ...nt.html => section-upload-file-view.component.html} | 2 +- ...c.ts => section-upload-file-view.component.spec.ts} | 2 +- ...ponent.ts => section-upload-file-view.component.ts} | 2 +- .../sections/upload/section-upload.component.html | 2 +- src/app/submission/submission.module.ts | 10 +++++----- 15 files changed, 20 insertions(+), 20 deletions(-) rename src/app/submission/sections/upload/accessConditions/{accessConditions.component.html => section-upload-access-conditions.component.html} (100%) rename src/app/submission/sections/upload/accessConditions/{accessConditions.component.ts => section-upload-access-conditions.component.ts} (87%) rename src/app/submission/sections/upload/file/edit/{file-edit.component.html => section-upload-file-edit.component.html} (100%) rename src/app/submission/sections/upload/file/edit/{file-edit.component.spec.ts => section-upload-file-edit.component.spec.ts} (98%) rename src/app/submission/sections/upload/file/edit/{file-edit.component.ts => section-upload-file-edit.component.ts} (99%) rename src/app/submission/sections/upload/file/edit/{files-edit.model.ts => section-upload-file-edit.model.ts} (100%) rename src/app/submission/sections/upload/file/{file.component.html => section-upload-file.component.html} (100%) rename src/app/submission/sections/upload/file/{file.component.scss => section-upload-file.component.scss} (100%) rename src/app/submission/sections/upload/file/{file.component.spec.ts => section-upload-file.component.spec.ts} (98%) rename src/app/submission/sections/upload/file/{file.component.ts => section-upload-file.component.ts} (97%) rename src/app/submission/sections/upload/file/view/{file-view.component.html => section-upload-file-view.component.html} (89%) rename src/app/submission/sections/upload/file/view/{file-view.component.spec.ts => section-upload-file-view.component.spec.ts} (96%) rename src/app/submission/sections/upload/file/view/{file-view.component.ts => section-upload-file-view.component.ts} (94%) diff --git a/src/app/submission/sections/upload/accessConditions/accessConditions.component.html b/src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.html similarity index 100% rename from src/app/submission/sections/upload/accessConditions/accessConditions.component.html rename to src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.html diff --git a/src/app/submission/sections/upload/accessConditions/accessConditions.component.ts b/src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.ts similarity index 87% rename from src/app/submission/sections/upload/accessConditions/accessConditions.component.ts rename to src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.ts index 30a0c82b2b..a5252a46f3 100644 --- a/src/app/submission/sections/upload/accessConditions/accessConditions.component.ts +++ b/src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.ts @@ -9,10 +9,10 @@ import { Group } from '../../../../core/eperson/models/group.model'; import { RemoteData } from '../../../../core/data/remote-data'; @Component({ - selector: 'ds-access-conditions', - templateUrl: './accessConditions.component.html', + selector: 'ds-section-upload-access-conditions', + templateUrl: './section-upload-access-conditions.component.html', }) -export class AccessConditionsComponent implements OnInit { +export class SectionUploadAccessConditionsComponent implements OnInit { @Input() accessConditions: ResourcePolicy[]; diff --git a/src/app/submission/sections/upload/file/edit/file-edit.component.html b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html similarity index 100% rename from src/app/submission/sections/upload/file/edit/file-edit.component.html rename to src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html diff --git a/src/app/submission/sections/upload/file/edit/file-edit.component.spec.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts similarity index 98% rename from src/app/submission/sections/upload/file/edit/file-edit.component.spec.ts rename to src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts index 3809730abe..acc4e5edcd 100644 --- a/src/app/submission/sections/upload/file/edit/file-edit.component.spec.ts +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts @@ -15,7 +15,7 @@ import { createTestComponent } from '../../../../../shared/testing/utils'; import { FormBuilderService } from '../../../../../shared/form/builder/form-builder.service'; import { SubmissionServiceStub } from '../../../../../shared/testing/submission-service-stub'; import { SubmissionService } from '../../../../submission.service'; -import { UploadSectionFileEditComponent } from './file-edit.component'; +import { UploadSectionFileEditComponent } from './section-upload-file-edit.component'; import { POLICY_DEFAULT_WITH_LIST } from '../../section-upload.component'; import { mockGroup, diff --git a/src/app/submission/sections/upload/file/edit/file-edit.component.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts similarity index 99% rename from src/app/submission/sections/upload/file/edit/file-edit.component.ts rename to src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts index ed30b850ac..ff441d6102 100644 --- a/src/app/submission/sections/upload/file/edit/file-edit.component.ts +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts @@ -26,7 +26,7 @@ import { BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT, BITSTREAM_METADATA_FORM_GROUP_CONFIG, BITSTREAM_METADATA_FORM_GROUP_LAYOUT -} from './files-edit.model'; +} from './section-upload-file-edit.model'; import { POLICY_DEFAULT_WITH_LIST } from '../../section-upload.component'; import { isNotEmpty, isNotUndefined } from '../../../../../shared/empty.util'; import { SubmissionFormsModel } from '../../../../../core/config/models/config-submission-forms.model'; @@ -40,7 +40,7 @@ import { Group } from '../../../../../core/eperson/models/group.model'; @Component({ selector: 'ds-submission-upload-section-file-edit', - templateUrl: './file-edit.component.html', + templateUrl: './section-upload-file-edit.component.html', }) export class UploadSectionFileEditComponent implements OnChanges { diff --git a/src/app/submission/sections/upload/file/edit/files-edit.model.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.model.ts similarity index 100% rename from src/app/submission/sections/upload/file/edit/files-edit.model.ts rename to src/app/submission/sections/upload/file/edit/section-upload-file-edit.model.ts diff --git a/src/app/submission/sections/upload/file/file.component.html b/src/app/submission/sections/upload/file/section-upload-file.component.html similarity index 100% rename from src/app/submission/sections/upload/file/file.component.html rename to src/app/submission/sections/upload/file/section-upload-file.component.html diff --git a/src/app/submission/sections/upload/file/file.component.scss b/src/app/submission/sections/upload/file/section-upload-file.component.scss similarity index 100% rename from src/app/submission/sections/upload/file/file.component.scss rename to src/app/submission/sections/upload/file/section-upload-file.component.scss diff --git a/src/app/submission/sections/upload/file/file.component.spec.ts b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts similarity index 98% rename from src/app/submission/sections/upload/file/file.component.spec.ts rename to src/app/submission/sections/upload/file/section-upload-file.component.spec.ts index 4500b4bfe9..ff2b3071f3 100644 --- a/src/app/submission/sections/upload/file/file.component.spec.ts +++ b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts @@ -15,7 +15,7 @@ import { NgbModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { JsonPatchOperationsBuilder } from '../../../../core/json-patch/builder/json-patch-operations-builder'; import { SubmissionJsonPatchOperationsServiceStub } from '../../../../shared/testing/submission-json-patch-operations-service-stub'; import { SubmissionJsonPatchOperationsService } from '../../../../core/submission/submission-json-patch-operations.service'; -import { UploadSectionFileComponent } from './file.component'; +import { UploadSectionFileComponent } from './section-upload-file.component'; import { SubmissionServiceStub } from '../../../../shared/testing/submission-service-stub'; import { mockFileFormData, @@ -36,7 +36,7 @@ import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/buil import { getMockSectionUploadService } from '../../../../shared/mocks/mock-section-upload.service'; import { FormFieldMetadataValueObject } from '../../../../shared/form/builder/models/form-field-metadata-value.model'; import { Group } from '../../../../core/eperson/models/group.model'; -import { UploadSectionFileEditComponent } from './edit/file-edit.component'; +import { UploadSectionFileEditComponent } from './edit/section-upload-file-edit.component'; function getMockFileService(): FileService { return jasmine.createSpyObj('FileService', { diff --git a/src/app/submission/sections/upload/file/file.component.ts b/src/app/submission/sections/upload/file/section-upload-file.component.ts similarity index 97% rename from src/app/submission/sections/upload/file/file.component.ts rename to src/app/submission/sections/upload/file/section-upload-file.component.ts index 71d4585e85..45d91428fe 100644 --- a/src/app/submission/sections/upload/file/file.component.ts +++ b/src/app/submission/sections/upload/file/section-upload-file.component.ts @@ -20,13 +20,13 @@ import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service import { SubmissionJsonPatchOperationsService } from '../../../../core/submission/submission-json-patch-operations.service'; import { SubmissionObject } from '../../../../core/submission/models/submission-object.model'; import { WorkspaceitemSectionUploadObject } from '../../../../core/submission/models/workspaceitem-section-upload.model'; -import { UploadSectionFileEditComponent } from './edit/file-edit.component'; +import { UploadSectionFileEditComponent } from './edit/section-upload-file-edit.component'; import { Group } from '../../../../core/eperson/models/group.model'; @Component({ selector: 'ds-submission-upload-section-file', - styleUrls: ['./file.component.scss'], - templateUrl: './file.component.html', + styleUrls: ['./section-upload-file.component.scss'], + templateUrl: './section-upload-file.component.html', }) export class UploadSectionFileComponent implements OnChanges, OnInit { diff --git a/src/app/submission/sections/upload/file/view/file-view.component.html b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.html similarity index 89% rename from src/app/submission/sections/upload/file/view/file-view.component.html rename to src/app/submission/sections/upload/file/view/section-upload-file-view.component.html index 838e6d3b97..7f5250778c 100644 --- a/src/app/submission/sections/upload/file/view/file-view.component.html +++ b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.html @@ -25,5 +25,5 @@ - + diff --git a/src/app/submission/sections/upload/file/view/file-view.component.spec.ts b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.spec.ts similarity index 96% rename from src/app/submission/sections/upload/file/view/file-view.component.spec.ts rename to src/app/submission/sections/upload/file/view/section-upload-file-view.component.spec.ts index f5e924f309..af2bb587ac 100644 --- a/src/app/submission/sections/upload/file/view/file-view.component.spec.ts +++ b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.spec.ts @@ -6,7 +6,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { createTestComponent } from '../../../../../shared/testing/utils'; import { mockUploadFiles } from '../../../../../shared/mocks/mock-submission'; import { FormComponent } from '../../../../../shared/form/form.component'; -import { UploadSectionFileViewComponent } from './file-view.component'; +import { UploadSectionFileViewComponent } from './section-upload-file-view.component'; import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe'; import { Metadata } from '../../../../../core/shared/metadata.utils'; diff --git a/src/app/submission/sections/upload/file/view/file-view.component.ts b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts similarity index 94% rename from src/app/submission/sections/upload/file/view/file-view.component.ts rename to src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts index 0e94608069..a61979e74f 100644 --- a/src/app/submission/sections/upload/file/view/file-view.component.ts +++ b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts @@ -7,7 +7,7 @@ import { MetadataMap, MetadataValue } from '../../../../../core/shared/metadata. @Component({ selector: 'ds-submission-upload-section-file-view', - templateUrl: './file-view.component.html', + templateUrl: './section-upload-file-view.component.html', }) export class UploadSectionFileViewComponent implements OnInit { @Input() fileData: WorkspaceitemSectionUploadFileObject; diff --git a/src/app/submission/sections/upload/section-upload.component.html b/src/app/submission/sections/upload/section-upload.component.html index 053f4e1d8e..080f7a1f04 100644 --- a/src/app/submission/sections/upload/section-upload.component.html +++ b/src/app/submission/sections/upload/section-upload.component.html @@ -25,7 +25,7 @@ {{ 'submission.sections.upload.header.policy.default.withlist' | translate:{ "collectionName": collectionName } }} - + diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index 0cd290a82f..80a11cb27d 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -23,10 +23,10 @@ import { SubmissionRestService } from './submission-rest.service'; import { LicenseSectionComponent } from './sections/license/section-license.component'; import { SubmissionUploadsConfigService } from '../core/config/submission-uploads-config.service'; import { SubmissionEditComponent } from './edit/submission-edit.component'; -import { UploadSectionFileComponent } from './sections/upload/file/file.component'; -import { UploadSectionFileEditComponent } from './sections/upload/file/edit/file-edit.component'; -import { UploadSectionFileViewComponent } from './sections/upload/file/view/file-view.component'; -import { AccessConditionsComponent } from './sections/upload/accessConditions/accessConditions.component'; +import { UploadSectionFileComponent } from './sections/upload/file/section-upload-file.component'; +import { UploadSectionFileEditComponent } from './sections/upload/file/edit/section-upload-file-edit.component'; +import { UploadSectionFileViewComponent } from './sections/upload/file/view/section-upload-file-view.component'; +import { SectionUploadAccessConditionsComponent } from './sections/upload/accessConditions/section-upload-access-conditions.component'; import { SubmissionSubmitComponent } from './submit/submission-submit.component'; @NgModule({ @@ -39,7 +39,7 @@ import { SubmissionSubmitComponent } from './submit/submission-submit.component' TranslateModule ], declarations: [ - AccessConditionsComponent, + SectionUploadAccessConditionsComponent, UploadSectionComponent, FormSectionComponent, LicenseSectionComponent, From 299c7f7e2c4a9764f3ade8233c389a9526e45178 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Sun, 17 Mar 2019 19:21:02 +0100 Subject: [PATCH 08/27] Added more comments --- .../objects/submission-objects.effects.ts | 74 ++++- .../objects/submission-objects.reducer.ts | 96 +++++++ .../submission/sections/sections.service.ts | 178 +++++++++++- src/app/submission/submission.service.spec.ts | 4 +- src/app/submission/submission.service.ts | 255 +++++++++++++++++- .../utils/parseSectionErrorPaths.ts | 21 +- 6 files changed, 604 insertions(+), 24 deletions(-) diff --git a/src/app/submission/objects/submission-objects.effects.ts b/src/app/submission/objects/submission-objects.effects.ts index bebec482c2..f4b74807cf 100644 --- a/src/app/submission/objects/submission-objects.effects.ts +++ b/src/app/submission/objects/submission-objects.effects.ts @@ -24,7 +24,7 @@ import { SaveSubmissionFormSuccessAction, SaveSubmissionSectionFormAction, SaveSubmissionSectionFormErrorAction, - SaveSubmissionSectionFormSuccessAction, + SaveSubmissionSectionFormSuccessAction, SubmissionObjectAction, SubmissionObjectActionTypes, UpdateSectionDataAction } from './submission-objects.actions'; @@ -48,6 +48,9 @@ import { SubmissionJsonPatchOperationsService } from '../../core/submission/subm @Injectable() export class SubmissionObjectEffects { + /** + * Dispatch a [InitSectionAction] for every submission sections and dispatch a [CompleteInitSubmissionFormAction] + */ @Effect() loadForm$ = this.actions$.pipe( ofType(SubmissionObjectActionTypes.INIT_SUBMISSION_FORM), map((action: InitSubmissionFormAction) => { @@ -83,6 +86,9 @@ export class SubmissionObjectEffects { )); })); + /** + * Dispatch a [InitSubmissionFormAction] + */ @Effect() resetForm$ = this.actions$.pipe( ofType(SubmissionObjectActionTypes.RESET_SUBMISSION_FORM), map((action: ResetSubmissionFormAction) => @@ -95,6 +101,9 @@ export class SubmissionObjectEffects { null ))); + /** + * Dispatch a [SaveSubmissionFormSuccessAction] or a [SaveSubmissionFormErrorAction] on error + */ @Effect() saveSubmission$ = this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM), switchMap((action: SaveSubmissionFormAction) => { @@ -106,6 +115,9 @@ export class SubmissionObjectEffects { catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); })); + /** + * Dispatch a [SaveForLaterSubmissionFormSuccessAction] or a [SaveSubmissionFormErrorAction] on error + */ @Effect() saveForLaterSubmission$ = this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_FOR_LATER_SUBMISSION_FORM), switchMap((action: SaveForLaterSubmissionFormAction) => { @@ -117,6 +129,9 @@ export class SubmissionObjectEffects { catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); })); + /** + * Call parseSaveResponse and dispatch actions + */ @Effect() saveSubmissionSuccess$ = this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_SUCCESS, SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_SUCCESS), withLatestFrom(this.store$), @@ -125,6 +140,9 @@ export class SubmissionObjectEffects { }), mergeMap((actions) => observableFrom(actions))); + /** + * Dispatch a [SaveSubmissionSectionFormSuccessAction] or a [SaveSubmissionSectionFormErrorAction] on error + */ @Effect() saveSection$ = this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM), switchMap((action: SaveSubmissionSectionFormAction) => { @@ -137,11 +155,17 @@ export class SubmissionObjectEffects { catchError(() => observableOf(new SaveSubmissionSectionFormErrorAction(action.payload.submissionId)))); })); + /** + * Show a notification on error + */ @Effect({dispatch: false}) saveError$ = this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_ERROR, SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_ERROR), withLatestFrom(this.store$), tap(() => this.notificationsService.error(null, this.translate.get('submission.sections.general.save_error_notice')))); + /** + * Call parseSaveResponse and dispatch actions or dispatch [SaveSubmissionFormErrorAction] on error + */ @Effect() saveAndDeposit$ = this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_AND_DEPOSIT_SUBMISSION), withLatestFrom(this.store$), @@ -161,6 +185,9 @@ export class SubmissionObjectEffects { catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); })); + /** + * Dispatch a [DepositSubmissionSuccessAction] or a [DepositSubmissionErrorAction] on error + */ @Effect() depositSubmission$ = this.actions$.pipe( ofType(SubmissionObjectActionTypes.DEPOSIT_SUBMISSION), withLatestFrom(this.store$), @@ -170,20 +197,32 @@ export class SubmissionObjectEffects { catchError(() => observableOf(new DepositSubmissionErrorAction(action.payload.submissionId)))); })); + /** + * Show a notification on success and redirect to MyDSpace page + */ @Effect({dispatch: false}) saveForLaterSubmissionSuccess$ = this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_FOR_LATER_SUBMISSION_FORM_SUCCESS), tap(() => this.notificationsService.success(null, this.translate.get('submission.sections.general.save_success_notice'))), tap(() => this.submissionService.redirectToMyDSpace())); + /** + * Show a notification on success and redirect to MyDSpace page + */ @Effect({dispatch: false}) depositSubmissionSuccess$ = this.actions$.pipe( ofType(SubmissionObjectActionTypes.DEPOSIT_SUBMISSION_SUCCESS), tap(() => this.notificationsService.success(null, this.translate.get('submission.sections.general.deposit_success_notice'))), tap(() => this.submissionService.redirectToMyDSpace())); + /** + * Show a notification on error + */ @Effect({dispatch: false}) depositSubmissionError$ = this.actions$.pipe( ofType(SubmissionObjectActionTypes.DEPOSIT_SUBMISSION_ERROR), tap(() => this.notificationsService.error(null, this.translate.get('submission.sections.general.deposit_error_notice')))); + /** + * Dispatch a [DiscardSubmissionSuccessAction] or a [DiscardSubmissionErrorAction] on error + */ @Effect() discardSubmission$ = this.actions$.pipe( ofType(SubmissionObjectActionTypes.DISCARD_SUBMISSION), switchMap((action: DepositSubmissionAction) => { @@ -192,11 +231,17 @@ export class SubmissionObjectEffects { catchError(() => observableOf(new DiscardSubmissionErrorAction(action.payload.submissionId)))); })); + /** + * Show a notification on success and redirect to MyDSpace page + */ @Effect({dispatch: false}) discardSubmissionSuccess$ = this.actions$.pipe( ofType(SubmissionObjectActionTypes.DISCARD_SUBMISSION_SUCCESS), tap(() => this.notificationsService.success(null, this.translate.get('submission.sections.general.discard_success_notice'))), tap(() => this.submissionService.redirectToMyDSpace())); + /** + * Show a notification on error + */ @Effect({dispatch: false}) discardSubmissionError$ = this.actions$.pipe( ofType(SubmissionObjectActionTypes.DISCARD_SUBMISSION_ERROR), tap(() => this.notificationsService.error(null, this.translate.get('submission.sections.general.discard_error_notice')))); @@ -210,6 +255,12 @@ export class SubmissionObjectEffects { private translate: TranslateService) { } + /** + * Check if the submission object retrieved from REST haven't section errors + * + * @param response + * The submission object retrieved from REST + */ protected canDeposit(response: SubmissionObject[]) { let canDeposit = true; @@ -225,7 +276,26 @@ export class SubmissionObjectEffects { return canDeposit; } - protected parseSaveResponse(currentState: SubmissionObjectEntry, response: SubmissionObject[], submissionId: string, notify: boolean = true) { + /** + * Parse the submission object retrieved from REST haven't section errors and return actions to dispatch + * + * @param currentState + * The current SubmissionObjectEntry + * @param response + * The submission object retrieved from REST + * @param submissionId + * The submission id + * @param notify + * A boolean that indicate if show notification or not + * @return SubmissionObjectAction[] + * List of SubmissionObjectAction to dispatch + */ + protected parseSaveResponse( + currentState: SubmissionObjectEntry, + response: SubmissionObject[], + submissionId: string, + notify: boolean = true): SubmissionObjectAction[] { + const mappedActions = []; if (isNotEmpty(response)) { diff --git a/src/app/submission/objects/submission-objects.reducer.ts b/src/app/submission/objects/submission-objects.reducer.ts index c02b2fd0f1..68ac7d56b9 100644 --- a/src/app/submission/objects/submission-objects.reducer.ts +++ b/src/app/submission/objects/submission-objects.reducer.ts @@ -38,42 +38,138 @@ import { WorkspaceitemSectionDataType } from '../../core/submission/models/works import { WorkspaceitemSectionUploadObject } from '../../core/submission/models/workspaceitem-section-upload.model'; import { SectionsType } from '../sections/sections-type'; +/** + * An interface to represent section visibility + */ export interface SectionVisibility { main: any; other: any; } +/** + * An interface to represent section object state + */ export interface SubmissionSectionObject { + /** + * The section header + */ header: string; + + /** + * The section configuration url + */ config: string; + + /** + * A boolean representing if this section is mandatory + */ mandatory: boolean; + + /** + * The section type + */ sectionType: SectionsType; + + /** + * The section visibility + */ visibility: SectionVisibility; + + /** + * A boolean representing if this section is collapsed + */ collapsed: boolean, + + /** + * A boolean representing if this section is enabled + */ enabled: boolean; + + /** + * The section data object + */ data: WorkspaceitemSectionDataType; + + /** + * The list of the section errors + */ errors: SubmissionSectionError[]; + + /** + * A boolean representing if this section is loading + */ isLoading: boolean; + + /** + * A boolean representing if this section is valid + */ isValid: boolean; } +/** + * An interface to represent section error + */ export interface SubmissionSectionError { + /** + * A string representing error path + */ path: string; + + /** + * The error message + */ message: string; } +/** + * An interface to represent SubmissionSectionObject entry + */ export interface SubmissionSectionEntry { [sectionId: string]: SubmissionSectionObject; } +/** + * An interface to represent submission object state + */ export interface SubmissionObjectEntry { + /** + * The collection this submission belonging to + */ collection?: string, + + /** + * The configuration name tha define this submission + */ definition?: string, + + /** + * The submission self url + */ selfUrl?: string; + + /** + * The submission active section + */ activeSection?: string; + + /** + * The list of submission's sections + */ sections?: SubmissionSectionEntry; + + /** + * A boolean representing if this submission is loading + */ isLoading?: boolean; + + /** + * A boolean representing if a submission save operation is pending + */ savePending?: boolean; + + /** + * A boolean representing if a submission deposit operation is pending + */ depositPending?: boolean; } diff --git a/src/app/submission/sections/sections.service.ts b/src/app/submission/sections/sections.service.ts index 00c2b5a690..aed83143a5 100644 --- a/src/app/submission/sections/sections.service.ts +++ b/src/app/submission/sections/sections.service.ts @@ -35,9 +35,20 @@ import { NotificationsService } from '../../shared/notifications/notifications.s import { SubmissionService } from '../submission.service'; import { WorkspaceitemSectionDataType } from '../../core/submission/models/workspaceitem-sections.model'; +/** + * A service that provides methods used in submission process. + */ @Injectable() export class SectionsService { + /** + * Initialize service variables + * @param {NotificationsService} notificationsService + * @param {ScrollToService} scrollToService + * @param {SubmissionService} submissionService + * @param {Store} store + * @param {TranslateService} translate + */ constructor(private notificationsService: NotificationsService, private scrollToService: ScrollToService, private submissionService: SubmissionService, @@ -45,17 +56,35 @@ export class SectionsService { private translate: TranslateService) { } + /** + * Compare the list of the current section errors with the previous one, + * and dispatch actions to add/remove to/from the section state + * + * @param submissionId + * The submission id + * @param sectionId + * The workspaceitem self url + * @param formId + * The [SubmissionDefinitionsModel] that define submission configuration + * @param currentErrors + * The [SubmissionSectionError] that define submission sections init data + * @param prevErrors + * The [SubmissionSectionError] that define submission sections init errors + */ public checkSectionErrors( submissionId: string, sectionId: string, formId: string, currentErrors: SubmissionSectionError[], prevErrors: SubmissionSectionError[] = []) { + // Remove previous error list if the current is empty if (isEmpty(currentErrors)) { this.store.dispatch(new RemoveSectionErrorsAction(submissionId, sectionId)); this.store.dispatch(new FormClearErrorsAction(formId)); - } else if (!isEqual(currentErrors, prevErrors)) { + } else if (!isEqual(currentErrors, prevErrors)) { // compare previous error list with the current one const dispatchedErrors = []; + + // Itereate over the current error list currentErrors.forEach((error: SubmissionSectionError) => { const errorPaths: SectionErrorPath[] = parseSectionErrorPaths(error.path); @@ -63,7 +92,7 @@ export class SectionsService { if (path.fieldId) { const fieldId = path.fieldId.replace(/\./g, '_'); - // Dispatch action to the form state; + // Dispatch action to add form error to the state; const formAddErrorAction = new FormAddError(formId, fieldId, path.fieldIndex, error.message); this.store.dispatch(formAddErrorAction); dispatchedErrors.push(fieldId); @@ -71,6 +100,7 @@ export class SectionsService { }); }); + // Itereate over the previous error list prevErrors.forEach((error: SubmissionSectionError) => { const errorPaths: SectionErrorPath[] = parseSectionErrorPaths(error.path); @@ -79,6 +109,7 @@ export class SectionsService { const fieldId = path.fieldId.replace(/\./g, '_'); if (!dispatchedErrors.includes(fieldId)) { + // Dispatch action to remove form error from the state; const formRemoveErrorAction = new FormRemoveErrorAction(formId, fieldId, path.fieldIndex); this.store.dispatch(formRemoveErrorAction); } @@ -88,20 +119,58 @@ export class SectionsService { } } + /** + * Dispatch a new [RemoveSectionErrorsAction] + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + */ public dispatchRemoveSectionErrors(submissionId, sectionId) { this.store.dispatch(new RemoveSectionErrorsAction(submissionId, sectionId)); } + /** + * Return the data object for the specified section + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @return Observable + * observable of [WorkspaceitemSectionDataType] + */ public getSectionData(submissionId: string, sectionId: string): Observable { return this.store.select(submissionSectionDataFromIdSelector(submissionId, sectionId)).pipe( distinctUntilChanged()); } + /** + * Return the error list object data for the specified section + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @return Observable + * observable of array of [SubmissionSectionError] + */ public getSectionErrors(submissionId: string, sectionId: string): Observable { return this.store.select(submissionSectionErrorsFromIdSelector(submissionId, sectionId)).pipe( distinctUntilChanged()); } + /** + * Return the state object for the specified section + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @return Observable + * observable of [SubmissionSectionObject] + */ public getSectionState(submissionId: string, sectionId: string): Observable { return this.store.select(submissionSectionFromIdSelector(submissionId, sectionId)).pipe( filter((sectionObj: SubmissionSectionObject) => hasValue(sectionObj)), @@ -109,6 +178,16 @@ export class SectionsService { distinctUntilChanged()); } + /** + * Check if a given section is valid + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @return Observable + * Emits true whenever a given section should be valid + */ public isSectionValid(submissionId: string, sectionId: string): Observable { return this.store.select(submissionSectionFromIdSelector(submissionId, sectionId)).pipe( filter((sectionObj) => hasValue(sectionObj)), @@ -116,12 +195,32 @@ export class SectionsService { distinctUntilChanged()); } + /** + * Check if a given section is active + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @return Observable + * Emits true whenever a given section should be active + */ public isSectionActive(submissionId: string, sectionId: string): Observable { return this.submissionService.getActiveSectionId(submissionId).pipe( map((activeSectionId: string) => sectionId === activeSectionId), distinctUntilChanged()); } + /** + * Check if a given section is enabled + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @return Observable + * Emits true whenever a given section should be enabled + */ public isSectionEnabled(submissionId: string, sectionId: string): Observable { return this.store.select(submissionSectionFromIdSelector(submissionId, sectionId)).pipe( filter((sectionObj) => hasValue(sectionObj)), @@ -129,6 +228,18 @@ export class SectionsService { distinctUntilChanged()); } + /** + * Check if a given section is a read only section + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @param submissionScope + * The submission scope + * @return Observable + * Emits true whenever a given section should be read only + */ public isSectionReadOnly(submissionId: string, sectionId: string, submissionScope: SubmissionScopeType): Observable { return this.store.select(submissionSectionFromIdSelector(submissionId, sectionId)).pipe( filter((sectionObj) => hasValue(sectionObj)), @@ -140,6 +251,16 @@ export class SectionsService { distinctUntilChanged()); } + /** + * Check if a given section is a read only available + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @return Observable + * Emits true whenever a given section should be available + */ public isSectionAvailable(submissionId: string, sectionId: string): Observable { return this.store.select(submissionObjectFromIdSelector(submissionId)).pipe( filter((submissionState: SubmissionObjectEntry) => isNotUndefined(submissionState)), @@ -149,8 +270,15 @@ export class SectionsService { distinctUntilChanged()); } - public addSection(submissionId: string, - sectionId: string) { + /** + * Dispatch a new [EnableSectionAction] to add a new section and move page target to it + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + */ + public addSection(submissionId: string, sectionId: string) { this.store.dispatch(new EnableSectionAction(submissionId, sectionId)); const config: ScrollToConfigOptions = { target: sectionId, @@ -160,11 +288,31 @@ export class SectionsService { this.scrollToService.scrollTo(config); } + /** + * Dispatch a new [DisableSectionAction] to remove section + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + */ public removeSection(submissionId: string, sectionId: string) { this.store.dispatch(new DisableSectionAction(submissionId, sectionId)) } - public updateSectionData(submissionId: string, sectionId: string, data, errors = []) { + /** + * Dispatch a new [UpdateSectionDataAction] to update section state with new data and errors + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @param data + * The section data + * @param errors + * The list of section errors + */ + public updateSectionData(submissionId: string, sectionId: string, data: WorkspaceitemSectionDataType, errors: SubmissionSectionError[] = []) { if (isNotEmpty(data)) { const isAvailable$ = this.isSectionAvailable(submissionId, sectionId); const isEnabled$ = this.isSectionEnabled(submissionId, sectionId); @@ -182,10 +330,30 @@ export class SectionsService { } } + /** + * Dispatch a new [InertSectionErrorsAction] to update section state with new error + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @param error + * The section error + */ public setSectionError(submissionId: string, sectionId: string, error: SubmissionSectionError) { this.store.dispatch(new InertSectionErrorsAction(submissionId, sectionId, error)); } + /** + * Dispatch a new [SectionStatusChangeAction] to update section state with new status + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @param status + * The section status + */ public setSectionStatus(submissionId: string, sectionId: string, status: boolean) { this.store.dispatch(new SectionStatusChangeAction(submissionId, sectionId, status)); } diff --git a/src/app/submission/submission.service.spec.ts b/src/app/submission/submission.service.spec.ts index 7dde19a306..0522f279dd 100644 --- a/src/app/submission/submission.service.spec.ts +++ b/src/app/submission/submission.service.spec.ts @@ -777,7 +777,7 @@ describe('SubmissionService test suite', () => { service.notifyNewSection(submissionId, sectionId); flush(); - expect((service as any).notificationsService.info).toHaveBeenCalledWith(null, 'test', null, true); + expect((service as any).notificationsService.info).toHaveBeenCalledWith(null, 'submission.sections.general.metadata-extracted-new-section', null, true); })); }); @@ -890,7 +890,7 @@ describe('SubmissionService test suite', () => { const duration = config.submission.autosave.timer * (1000 * 60); service.startAutoSave('826'); - const sub = (service as any).timerObs.subscribe(); + const sub = (service as any).timer$.subscribe(); tick(duration / 2); expect((service as any).store.dispatch).not.toHaveBeenCalled(); diff --git a/src/app/submission/submission.service.ts b/src/app/submission/submission.service.ts index dab6a9d9d5..d086f6f3d4 100644 --- a/src/app/submission/submission.service.ts +++ b/src/app/submission/submission.service.ts @@ -44,12 +44,32 @@ import { RemoteData } from '../core/data/remote-data'; import { ErrorResponse } from '../core/cache/response.models'; import { RemoteDataError } from '../core/data/remote-data-error'; +/** + * A service that provides methods used in submission process. + */ @Injectable() export class SubmissionService { + /** + * Subscription + */ protected autoSaveSub: Subscription; - protected timerObs: Observable; + /** + * Observable used as timer + */ + protected timer$: Observable; + + /** + * Initialize service variables + * @param {GlobalConfig} EnvConfig + * @param {NotificationsService} notificationsService + * @param {SubmissionRestService} restService + * @param {Router} restSerroutervice + * @param {RouteService} routeService + * @param {Store} store + * @param {TranslateService} translate + */ constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, protected notificationsService: NotificationsService, protected restService: SubmissionRestService, @@ -59,28 +79,74 @@ export class SubmissionService { protected translate: TranslateService) { } + /** + * Dispatch a new [ChangeSubmissionCollectionAction] + * + * @param submissionId + * The submission id + * @param collectionId + * The collection id + */ changeSubmissionCollection(submissionId, collectionId) { this.store.dispatch(new ChangeSubmissionCollectionAction(submissionId, collectionId)); } + /** + * Perform a REST call to create a new workspaceitem and return response + * + * @return Observable + * observable of SubmissionObject + */ createSubmission(): Observable { return this.restService.postToEndpoint('workspaceitems', {}).pipe( map((workspaceitem: SubmissionObject) => workspaceitem[0]), catchError(() => observableOf({}))) } - depositSubmission(selfUrl: string): Observable { + /** + * Perform a REST call to deposit a workspaceitem and return response + * + * @param selfUrl + * The workspaceitem self url + * @return Observable + * observable of SubmissionObject + */ + depositSubmission(selfUrl: string): Observable { const options: HttpOptions = Object.create({}); let headers = new HttpHeaders(); headers = headers.append('Content-Type', 'text/uri-list'); options.headers = headers; - return this.restService.postToEndpoint('workflowitems', selfUrl, null, options); + return this.restService.postToEndpoint('workflowitems', selfUrl, null, options) as Observable; } - discardSubmission(submissionId: string): Observable { - return this.restService.deleteById(submissionId); + /** + * Perform a REST call to delete a workspaceitem and return response + * + * @param submissionId + * The submission id + * @return Observable + * observable of SubmissionObject + */ + discardSubmission(submissionId: string): Observable { + return this.restService.deleteById(submissionId) as Observable; } + /** + * Dispatch a new [InitSubmissionFormAction] + * + * @param collectionId + * The collection id + * @param submissionId + * The submission id + * @param selfUrl + * The workspaceitem self url + * @param submissionDefinition + * The [SubmissionDefinitionsModel] that define submission configuration + * @param sections + * The [WorkspaceitemSectionsObject] that define submission sections init data + * @param errors + * The [SubmissionSectionError] that define submission sections init errors + */ dispatchInit( collectionId: string, submissionId: string, @@ -91,36 +157,90 @@ export class SubmissionService { this.store.dispatch(new InitSubmissionFormAction(collectionId, submissionId, selfUrl, submissionDefinition, sections, errors)); } + /** + * Dispatch a new [SaveAndDepositSubmissionAction] + * + * @param submissionId + * The submission id + */ dispatchDeposit(submissionId) { this.store.dispatch(new SaveAndDepositSubmissionAction(submissionId)); } + /** + * Dispatch a new [DiscardSubmissionAction] + * + * @param submissionId + * The submission id + */ dispatchDiscard(submissionId) { this.store.dispatch(new DiscardSubmissionAction(submissionId)); } + /** + * Dispatch a new [SaveSubmissionFormAction] + * + * @param submissionId + * The submission id + */ dispatchSave(submissionId) { this.store.dispatch(new SaveSubmissionFormAction(submissionId)); } + /** + * Dispatch a new [SaveForLaterSubmissionFormAction] + * + * @param submissionId + * The submission id + */ dispatchSaveForLater(submissionId) { this.store.dispatch(new SaveForLaterSubmissionFormAction(submissionId)); } + /** + * Dispatch a new [SaveSubmissionSectionFormAction] + * + * @param submissionId + * The submission id + */ dispatchSaveSection(submissionId, sectionId) { this.store.dispatch(new SaveSubmissionSectionFormAction(submissionId, sectionId)); } + /** + * Return the id of the current focused section for the specified submission + * + * @param submissionId + * The submission id + * @return Observable + * observable of section id + */ getActiveSectionId(submissionId: string): Observable { return this.getSubmissionObject(submissionId).pipe( map((submission: SubmissionObjectEntry) => submission.activeSection)); } + /** + * Return the [SubmissionObjectEntry] for the specified submission + * + * @param submissionId + * The submission id + * @return Observable + * observable of SubmissionObjectEntry + */ getSubmissionObject(submissionId: string): Observable { return this.store.select(submissionObjectFromIdSelector(submissionId)).pipe( filter((submission: SubmissionObjectEntry) => isNotUndefined(submission))); } + /** + * Return a list of the active [SectionDataObject] belonging to the specified submission + * + * @param submissionId + * The submission id + * @return Observable + * observable with the list of active submission's sections + */ getSubmissionSections(submissionId: string): Observable { return this.getSubmissionObject(submissionId).pipe( find((submission: SubmissionObjectEntry) => isNotUndefined(submission.sections) && !submission.isLoading), @@ -146,6 +266,14 @@ export class SubmissionService { distinctUntilChanged()); } + /** + * Return a list of the disabled [SectionDataObject] belonging to the specified submission + * + * @param submissionId + * The submission id + * @return Observable + * observable with the list of disabled submission's sections + */ getDisabledSectionsList(submissionId: string): Observable { return this.getSubmissionObject(submissionId).pipe( filter((submission: SubmissionObjectEntry) => isNotUndefined(submission.sections) && !submission.isLoading), @@ -167,6 +295,12 @@ export class SubmissionService { distinctUntilChanged()); } + /** + * Return the correct REST endpoint link path depending on the page route + * + * @return string + * link path + */ getSubmissionObjectLinkName(): string { const url = this.router.routerState.snapshot.url; if (url.startsWith('/workspaceitems') || url.startsWith('/submit')) { @@ -178,6 +312,12 @@ export class SubmissionService { } } + /** + * Return the submission scope + * + * @return SubmissionScopeType + * the SubmissionScopeType + */ getSubmissionScope(): SubmissionScopeType { let scope: SubmissionScopeType; switch (this.getSubmissionObjectLinkName()) { @@ -194,6 +334,14 @@ export class SubmissionService { return scope; } + /** + * Return the validity status of the submission + * + * @param submissionId + * The submission id + * @return Observable + * observable with submission validity status + */ getSubmissionStatus(submissionId: string): Observable { return this.store.select(submissionSelector).pipe( map((submissions: SubmissionState) => submissions.objects[submissionId]), @@ -219,6 +367,14 @@ export class SubmissionService { startWith(false)); } + /** + * Return the save processing status of the submission + * + * @param submissionId + * The submission id + * @return Observable + * observable with submission save processing status + */ getSubmissionSaveProcessingStatus(submissionId: string): Observable { return this.getSubmissionObject(submissionId).pipe( map((state: SubmissionObjectEntry) => state.savePending), @@ -226,6 +382,14 @@ export class SubmissionService { startWith(false)); } + /** + * Return the deposit processing status of the submission + * + * @param submissionId + * The submission id + * @return Observable + * observable with submission deposit processing status + */ getSubmissionDepositProcessingStatus(submissionId: string): Observable { return this.getSubmissionObject(submissionId).pipe( map((state: SubmissionObjectEntry) => state.depositPending), @@ -233,27 +397,50 @@ export class SubmissionService { startWith(false)); } - isSectionHidden(sectionData: SubmissionSectionObject) { + /** + * Return the visibility status of the specified section + * + * @param submissionId + * The submission id + * @return boolean + * true if section is hidden, false otherwise + */ + isSectionHidden(sectionData: SubmissionSectionObject): boolean { return (isNotUndefined(sectionData.visibility) && sectionData.visibility.main === 'HIDDEN' && sectionData.visibility.other === 'HIDDEN'); - } + /** + * Return the loading status of the submission + * + * @param submissionId + * The submission id + * @return Observable + * observable with submission loading status + */ isSubmissionLoading(submissionId: string): Observable { return this.getSubmissionObject(submissionId).pipe( map((submission: SubmissionObjectEntry) => submission.isLoading), distinctUntilChanged()); } + /** + * Show a notification when a new section is added to submission form + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + */ notifyNewSection(submissionId: string, sectionId: string, sectionType?: SectionsType) { - this.translate.get('submission.sections.general.metadata-extracted-new-section', { sectionId }).pipe( - first()) - .subscribe((m) => { - this.notificationsService.info(null, m, null, true); - }); + const m = this.translate.instant('submission.sections.general.metadata-extracted-new-section', { sectionId }); + this.notificationsService.info(null, m, null, true); } + /** + * Redirect to MyDspace page + */ redirectToMyDSpace() { this.routeService.getPreviousUrl().pipe( first() @@ -267,10 +454,27 @@ export class SubmissionService { } + /** + * Dispatch a new [CancelSubmissionFormAction] + */ resetAllSubmissionObjects() { this.store.dispatch(new CancelSubmissionFormAction()); } + /** + * Dispatch a new [ResetSubmissionFormAction] + * + * @param collectionId + * The collection id + * @param submissionId + * The submission id + * @param selfUrl + * The workspaceitem self url + * @param submissionDefinition + * The [SubmissionDefinitionsModel] that define submission configuration + * @param sections + * The [WorkspaceitemSectionsObject] that define submission sections init data + */ resetSubmissionObject( collectionId: string, submissionId: string, @@ -281,6 +485,12 @@ export class SubmissionService { this.store.dispatch(new ResetSubmissionFormAction(collectionId, submissionId, selfUrl, sections, submissionDefinition)); } + /** + * Perform a REST call to retrieve an existing workspaceitem/workflowitem and return response + * + * @return Observable> + * observable of RemoteData + */ retrieveSubmission(submissionId): Observable> { return this.restService.getDataById(this.getSubmissionObjectLinkName(), submissionId).pipe( find((submissionObjects: SubmissionObject[]) => isNotUndefined(submissionObjects)), @@ -302,21 +512,38 @@ export class SubmissionService { ); } + /** + * Dispatch a new [SetActiveSectionAction] + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + */ setActiveSection(submissionId, sectionId) { this.store.dispatch(new SetActiveSectionAction(submissionId, sectionId)); } + /** + * Allow to save automatically the submission + * + * @param submissionId + * The submission id + */ startAutoSave(submissionId) { this.stopAutoSave(); // AUTOSAVE submission // Retrieve interval from config and convert to milliseconds const duration = this.EnvConfig.submission.autosave.timer * (1000 * 60); // Dispatch save action after given duration - this.timerObs = observableTimer(duration, duration); - this.autoSaveSub = this.timerObs + this.timer$ = observableTimer(duration, duration); + this.autoSaveSub = this.timer$ .subscribe(() => this.store.dispatch(new SaveSubmissionFormAction(submissionId))); } + /** + * Unsubscribe subscription to timer + */ stopAutoSave() { if (hasValue(this.autoSaveSub)) { this.autoSaveSub.unsubscribe(); diff --git a/src/app/submission/utils/parseSectionErrorPaths.ts b/src/app/submission/utils/parseSectionErrorPaths.ts index b47b9d0b05..4c973dedcf 100644 --- a/src/app/submission/utils/parseSectionErrorPaths.ts +++ b/src/app/submission/utils/parseSectionErrorPaths.ts @@ -1,9 +1,28 @@ import { hasValue } from '../../shared/empty.util'; +/** + * An interface to represent the path of a section error + */ export interface SectionErrorPath { + + /** + * The section id + */ sectionId: string; + + /** + * The form field id + */ fieldId?: string; + + /** + * The form field index + */ fieldIndex?: number; + + /** + * The complete path + */ originalPath: string; } @@ -12,7 +31,7 @@ const regex = /([^\/]+)/g; const regexShort = /\/sections\/(.*)/; /** - * the following method accept an array of section path strings and return a path object + * The following method accept an array of section path strings and return a path object * @param {string | string[]} path * @returns {SectionErrorPath[]} */ From bbd6225cd44beb79a1c6a4b54984b92eaf8db467 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 18 Mar 2019 09:57:31 +0100 Subject: [PATCH 09/27] Move SubmissionRestService to submission core dir --- src/app/core/core.module.ts | 2 +- .../submission-rest.service.spec.ts | 14 +-- .../submission/submission-rest.service.ts | 93 ++++++++++++++++--- .../submission-form-footer.component.spec.ts | 2 +- .../submission-form-footer.component.ts | 2 +- .../upload/section-upload.component.ts | 1 - src/app/submission/submission.module.ts | 6 +- src/app/submission/submission.service.spec.ts | 2 +- src/app/submission/submission.service.ts | 2 +- 9 files changed, 95 insertions(+), 29 deletions(-) rename src/app/{ => core}/submission/submission-rest.service.spec.ts (83%) rename src/app/{ => core}/submission/submission-rest.service.ts (60%) diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 6aa58f791e..dfcb2b4fa1 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -68,7 +68,7 @@ import { WorkflowitemDataService } from './submission/workflowitem-data.service' import { NotificationsService } from '../shared/notifications/notifications.service'; import { UploaderService } from '../shared/uploader/uploader.service'; import { FileService } from './shared/file.service'; -import { SubmissionRestService } from '../submission/submission-rest.service'; +import { SubmissionRestService } from './submission/submission-rest.service'; import { BrowseItemsResponseParsingService } from './data/browse-items-response-parsing-service'; import { DSpaceObjectDataService } from './data/dspace-object-data.service'; import { MetadataschemaParsingService } from './data/metadataschema-parsing.service'; diff --git a/src/app/submission/submission-rest.service.spec.ts b/src/app/core/submission/submission-rest.service.spec.ts similarity index 83% rename from src/app/submission/submission-rest.service.spec.ts rename to src/app/core/submission/submission-rest.service.spec.ts index c5992d7d10..6e748c5575 100644 --- a/src/app/submission/submission-rest.service.spec.ts +++ b/src/app/core/submission/submission-rest.service.spec.ts @@ -2,18 +2,18 @@ import { TestScheduler } from 'rxjs/testing'; import { getTestScheduler } from 'jasmine-marbles'; import { SubmissionRestService } from './submission-rest.service'; -import { RequestService } from '../core/data/request.service'; -import { RemoteDataBuildService } from '../core/cache/builders/remote-data-build.service'; -import { getMockRequestService } from '../shared/mocks/mock-request.service'; -import { getMockRemoteDataBuildService } from '../shared/mocks/mock-remote-data-build.service'; -import { HALEndpointServiceStub } from '../shared/testing/hal-endpoint-service-stub'; +import { RequestService } from '../data/request.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { getMockRequestService } from '../../shared/mocks/mock-request.service'; +import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service'; +import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub'; import { SubmissionDeleteRequest, SubmissionPatchRequest, SubmissionPostRequest, SubmissionRequest -} from '../core/data/request.models'; -import { FormFieldMetadataValueObject } from '../shared/form/builder/models/form-field-metadata-value.model'; +} from '../data/request.models'; +import { FormFieldMetadataValueObject } from '../../shared/form/builder/models/form-field-metadata-value.model'; describe('SubmissionRestService test suite', () => { let scheduler: TestScheduler; diff --git a/src/app/submission/submission-rest.service.ts b/src/app/core/submission/submission-rest.service.ts similarity index 60% rename from src/app/submission/submission-rest.service.ts rename to src/app/core/submission/submission-rest.service.ts index b5d563549f..a249f10c6f 100644 --- a/src/app/submission/submission-rest.service.ts +++ b/src/app/core/submission/submission-rest.service.ts @@ -3,8 +3,8 @@ import { Injectable } from '@angular/core'; import { merge as observableMerge, Observable, throwError as observableThrowError } from 'rxjs'; import { distinctUntilChanged, filter, flatMap, map, mergeMap, tap } from 'rxjs/operators'; -import { RequestService } from '../core/data/request.service'; -import { isNotEmpty } from '../shared/empty.util'; +import { RequestService } from '../data/request.service'; +import { isNotEmpty } from '../../shared/empty.util'; import { DeleteRequest, PostRequest, @@ -13,14 +13,17 @@ import { SubmissionPatchRequest, SubmissionPostRequest, SubmissionRequest -} from '../core/data/request.models'; -import { SubmitDataResponseDefinitionObject } from '../core/shared/submit-data-response-definition.model'; -import { HttpOptions } from '../core/dspace-rest-v2/dspace-rest-v2.service'; -import { HALEndpointService } from '../core/shared/hal-endpoint.service'; -import { RemoteDataBuildService } from '../core/cache/builders/remote-data-build.service'; -import { ErrorResponse, RestResponse, SubmissionSuccessResponse } from '../core/cache/response.models'; -import { getResponseFromEntry } from '../core/shared/operators'; +} from '../data/request.models'; +import { SubmitDataResponseDefinitionObject } from '../shared/submit-data-response-definition.model'; +import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { ErrorResponse, RestResponse, SubmissionSuccessResponse } from '../cache/response.models'; +import { getResponseFromEntry } from '../shared/operators'; +/** + * The service handling all submission requests + */ @Injectable() export class SubmissionRestService { protected linkPath = 'workspaceitems'; @@ -31,6 +34,14 @@ export class SubmissionRestService { protected halService: HALEndpointService) { } + /** + * Fetch a RestRequest + * + * @param requestId + * The base endpoint for the type of object + * @return Observable + * server response + */ protected fetchRequest(requestId: string): Observable { const responses = this.requestService.getByUUID(requestId).pipe( getResponseFromEntry() @@ -47,10 +58,28 @@ export class SubmissionRestService { return observableMerge(errorResponses, successResponses); } + /** + * Create the HREF for a specific submission object based on its identifier + * + * @param endpoint + * The base endpoint for the type of object + * @param resourceID + * The identifier for the object + */ protected getEndpointByIDHref(endpoint, resourceID): string { return isNotEmpty(resourceID) ? `${endpoint}/${resourceID}` : `${endpoint}`; } + /** + * Delete an existing submission Object on the server + * + * @param scopeId + * The submission Object to be removed + * @param linkName + * The endpoint link name + * @return Observable + * server response + */ public deleteById(scopeId: string, linkName?: string): Observable { const requestId = this.requestService.generateRequestId(); return this.halService.getEndpoint(linkName || this.linkPath).pipe( @@ -59,11 +88,21 @@ export class SubmissionRestService { map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, scopeId)), map((endpointURL: string) => new SubmissionDeleteRequest(requestId, endpointURL)), tap((request: DeleteRequest) => this.requestService.configure(request)), - flatMap((request: DeleteRequest) => this.fetchRequest(requestId)), + flatMap(() => this.fetchRequest(requestId)), distinctUntilChanged()); } - public getDataById(linkName: string, id: string): Observable { + /** + * Return an existing submission Object from the server + * + * @param linkName + * The endpoint link name + * @param id + * The submission Object to retrieve + * @return Observable + * server response + */ + public getDataById(linkName: string, id: string): Observable { const requestId = this.requestService.generateRequestId(); return this.halService.getEndpoint(linkName).pipe( map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, id)), @@ -71,10 +110,24 @@ export class SubmissionRestService { distinctUntilChanged(), map((endpointURL: string) => new SubmissionRequest(requestId, endpointURL)), tap((request: RestRequest) => this.requestService.configure(request, true)), - flatMap((request: RestRequest) => this.fetchRequest(requestId)), + flatMap(() => this.fetchRequest(requestId)), distinctUntilChanged()); } + /** + * Make a new post request + * + * @param linkName + * The endpoint link name + * @param body + * The post request body + * @param scopeId + * The submission Object id + * @param options + * The [HttpOptions] object + * @return Observable + * server response + */ public postToEndpoint(linkName: string, body: any, scopeId?: string, options?: HttpOptions): Observable { const requestId = this.requestService.generateRequestId(); return this.halService.getEndpoint(linkName).pipe( @@ -83,10 +136,22 @@ export class SubmissionRestService { distinctUntilChanged(), map((endpointURL: string) => new SubmissionPostRequest(requestId, endpointURL, body, options)), tap((request: PostRequest) => this.requestService.configure(request)), - flatMap((request: PostRequest) => this.fetchRequest(requestId)), + flatMap(() => this.fetchRequest(requestId)), distinctUntilChanged()); } + /** + * Make a new patch to a specified object + * + * @param linkName + * The endpoint link name + * @param body + * The post request body + * @param scopeId + * The submission Object id + * @return Observable + * server response + */ public patchToEndpoint(linkName: string, body: any, scopeId?: string): Observable { const requestId = this.requestService.generateRequestId(); return this.halService.getEndpoint(linkName).pipe( @@ -95,7 +160,7 @@ export class SubmissionRestService { distinctUntilChanged(), map((endpointURL: string) => new SubmissionPatchRequest(requestId, endpointURL, body)), tap((request: PostRequest) => this.requestService.configure(request)), - flatMap((request: PostRequest) => this.fetchRequest(requestId)), + flatMap(() => this.fetchRequest(requestId)), distinctUntilChanged()); } diff --git a/src/app/submission/form/footer/submission-form-footer.component.spec.ts b/src/app/submission/form/footer/submission-form-footer.component.spec.ts index fb3a810501..5fbfd84cb8 100644 --- a/src/app/submission/form/footer/submission-form-footer.component.spec.ts +++ b/src/app/submission/form/footer/submission-form-footer.component.spec.ts @@ -13,7 +13,7 @@ import { mockSubmissionId } from '../../../shared/mocks/mock-submission'; import { SubmissionService } from '../../submission.service'; import { SubmissionRestServiceStub } from '../../../shared/testing/submission-rest-service-stub'; import { SubmissionFormFooterComponent } from './submission-form-footer.component'; -import { SubmissionRestService } from '../../submission-rest.service'; +import { SubmissionRestService } from '../../../core/submission/submission-rest.service'; import { createTestComponent } from '../../../shared/testing/utils'; describe('SubmissionFormFooterComponent Component', () => { diff --git a/src/app/submission/form/footer/submission-form-footer.component.ts b/src/app/submission/form/footer/submission-form-footer.component.ts index 5f2f36cc22..7245234fb4 100644 --- a/src/app/submission/form/footer/submission-form-footer.component.ts +++ b/src/app/submission/form/footer/submission-form-footer.component.ts @@ -4,7 +4,7 @@ import { Observable, of as observableOf } from 'rxjs'; import { map } from 'rxjs/operators'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { SubmissionRestService } from '../../submission-rest.service'; +import { SubmissionRestService } from '../../../core/submission/submission-rest.service'; import { SubmissionService } from '../../submission.service'; import { SubmissionScopeType } from '../../../core/submission/submission-scope-type'; import { isNotEmpty } from '../../../shared/empty.util'; diff --git a/src/app/submission/sections/upload/section-upload.component.ts b/src/app/submission/sections/upload/section-upload.component.ts index ff620cfa65..0c2eda5d77 100644 --- a/src/app/submission/sections/upload/section-upload.component.ts +++ b/src/app/submission/sections/upload/section-upload.component.ts @@ -160,7 +160,6 @@ export class UploadSectionComponent extends SectionModelComponent { return acc; }, []), ).subscribe((entries: AccessConditionGroupsMapEntry[]) => { - console.log(entries); entries.forEach((entry: AccessConditionGroupsMapEntry) => { this.availableGroups.set(entry.accessCondition, entry.groups); }); diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index 80a11cb27d..1b29f43a51 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -19,7 +19,6 @@ import { submissionEffects } from './submission.effects'; import { UploadSectionComponent } from './sections/upload/section-upload.component'; import { SectionUploadService } from './sections/upload/section-upload.service'; import { SubmissionUploadFilesComponent } from './form/submission-upload-files/submission-upload-files.component'; -import { SubmissionRestService } from './submission-rest.service'; import { LicenseSectionComponent } from './sections/license/section-license.component'; import { SubmissionUploadsConfigService } from '../core/config/submission-uploads-config.service'; import { SubmissionEditComponent } from './edit/submission-edit.component'; @@ -69,9 +68,12 @@ import { SubmissionSubmitComponent } from './submit/submission-submit.component' providers: [ SectionUploadService, SectionsService, - SubmissionRestService, SubmissionUploadsConfigService ] }) + +/** + * This module handles all components that are necessary for the submission process + */ export class SubmissionModule { } diff --git a/src/app/submission/submission.service.spec.ts b/src/app/submission/submission.service.spec.ts index 0522f279dd..a35cf8329c 100644 --- a/src/app/submission/submission.service.spec.ts +++ b/src/app/submission/submission.service.spec.ts @@ -11,7 +11,7 @@ import { cold, getTestScheduler, hot, } from 'jasmine-marbles'; import { MockRouter } from '../shared/mocks/mock-router'; import { SubmissionService } from './submission.service'; import { submissionReducers } from './submission.reducers'; -import { SubmissionRestService } from './submission-rest.service'; +import { SubmissionRestService } from '../core/submission/submission-rest.service'; import { RouteService } from '../shared/services/route.service'; import { SubmissionRestServiceStub } from '../shared/testing/submission-rest-service-stub'; import { MockActivatedRoute } from '../shared/mocks/mock-active-router'; diff --git a/src/app/submission/submission.service.ts b/src/app/submission/submission.service.ts index d086f6f3d4..11b26e2b5e 100644 --- a/src/app/submission/submission.service.ts +++ b/src/app/submission/submission.service.ts @@ -31,7 +31,7 @@ import { submissionObjectFromIdSelector } from './selectors'; import { GlobalConfig } from '../../config/global-config.interface'; import { GLOBAL_CONFIG } from '../../config'; import { HttpOptions } from '../core/dspace-rest-v2/dspace-rest-v2.service'; -import { SubmissionRestService } from './submission-rest.service'; +import { SubmissionRestService } from '../core/submission/submission-rest.service'; import { SectionDataObject } from './sections/models/section-data.model'; import { SubmissionScopeType } from '../core/submission/submission-scope-type'; import { SubmissionObject } from '../core/submission/models/submission-object.model'; From 2473ef307755fd7913feb9fa7565425017f72ea4 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 18 Mar 2019 11:11:01 +0100 Subject: [PATCH 10/27] Fixed an issue with showing form error in FormSectionComponent and added comments --- .../form/section-form.component.spec.ts | 17 ++ .../sections/form/section-form.component.ts | 186 ++++++++++++++---- 2 files changed, 166 insertions(+), 37 deletions(-) diff --git a/src/app/submission/sections/form/section-form.component.spec.ts b/src/app/submission/sections/form/section-form.component.spec.ts index 5e559a9e4a..b877771b73 100644 --- a/src/app/submission/sections/form/section-form.component.spec.ts +++ b/src/app/submission/sections/form/section-form.component.spec.ts @@ -318,6 +318,23 @@ describe('FormSectionComponent test suite', () => { }); + it('should update form error properly', () => { + spyOn(comp, 'initForm'); + spyOn(comp, 'checksForErrors'); + const sectionData: any = { + 'dc.title': [new FormFieldMetadataValueObject('test')] + }; + comp.sectionData.data = {}; + comp.sectionData.errors = []; + compAsAny.formData = sectionData; + + comp.updateForm(sectionData, parsedSectionErrors); + + expect(comp.initForm).not.toHaveBeenCalled(); + expect(comp.checksForErrors).toHaveBeenCalled(); + expect(comp.sectionData.data).toEqual(sectionData); + }); + it('should update form error properly', () => { spyOn(comp, 'initForm'); spyOn(comp, 'checksForErrors'); diff --git a/src/app/submission/sections/form/section-form.component.ts b/src/app/submission/sections/form/section-form.component.ts index c097374038..a62c52f634 100644 --- a/src/app/submission/sections/form/section-form.component.ts +++ b/src/app/submission/sections/form/section-form.component.ts @@ -17,7 +17,6 @@ import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder import { SubmissionFormsModel } from '../../../core/config/models/config-submission-forms.model'; import { SubmissionSectionError, SubmissionSectionObject } from '../../objects/submission-objects.reducer'; import { FormFieldPreviousValueObject } from '../../../shared/form/builder/models/form-field-previous-value-object'; -import { WorkspaceitemSectionDataType } from '../../../core/submission/models/workspaceitem-sections.model'; import { GLOBAL_CONFIG } from '../../../../config'; import { GlobalConfig } from '../../../../config/global-config.interface'; import { SectionDataObject } from '../models/section-data.model'; @@ -28,7 +27,11 @@ import { SectionFormOperationsService } from './section-form-operations.service' import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { SectionsService } from '../sections.service'; import { difference } from '../../../shared/object.util'; +import { WorkspaceitemSectionFormObject } from '../../../core/submission/models/workspaceitem-section-form.model'; +/** + * This component represents a section that contains a Form. + */ @Component({ selector: 'ds-submission-section-form', styleUrls: ['./section-form.component.scss'], @@ -37,19 +40,72 @@ import { difference } from '../../../shared/object.util'; @renderSectionFor(SectionsType.SubmissionForm) export class FormSectionComponent extends SectionModelComponent { + /** + * The form id + */ public formId; + + /** + * The form model + */ public formModel: DynamicFormControlModel[]; + + /** + * A boolean representing if this section is updating + */ public isUpdating = false; + + /** + * A boolean representing if this section is loading + */ public isLoading = true; + /** + * The form config + */ protected formConfig: SubmissionFormsModel; + + /** + * The form data + */ protected formData: any = Object.create({}); + + /** + * The [JsonPatchOperationPathCombiner] object + */ protected pathCombiner: JsonPatchOperationPathCombiner; + + /** + * The [FormFieldPreviousValueObject] object + */ protected previousValue: FormFieldPreviousValueObject = new FormFieldPreviousValueObject(); + + /** + * The list of Subscription + */ protected subs: Subscription[] = []; + /** + * The FormComponent reference + */ @ViewChild('formRef') private formRef: FormComponent; + /** + * Initialize instance variables + * @param {ChangeDetectorRef} cdr + * @param {FormBuilderService} formBuilderService + * @param {SectionFormOperationsService} formOperationsService + * @param {FormService} formService + * @param {SubmissionFormsConfigService} formConfigService + * @param {NotificationsService} notificationsService + * @param {SectionsService} sectionService + * @param {SubmissionService} submissionService + * @param {TranslateService} translate + * @param {GlobalConfig} EnvConfig + * @param {string} injectedCollectionId + * @param {SectionDataObject} injectedSectionData + * @param {string} injectedSubmissionId + */ constructor(protected cdr: ChangeDetectorRef, protected formBuilderService: FormBuilderService, protected formOperationsService: SectionFormOperationsService, @@ -66,6 +122,9 @@ export class FormSectionComponent extends SectionModelComponent { super(injectedCollectionId, injectedSectionData, injectedSubmissionId); } + /** + * Initialize all instance variables and retrieve form configuration + */ onSectionInit() { this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionData.id); this.formId = this.formService.getUniqueId(this.sectionData.id); @@ -75,30 +134,42 @@ export class FormSectionComponent extends SectionModelComponent { tap((config: SubmissionFormsModel) => this.formConfig = config), flatMap(() => this.sectionService.getSectionData(this.submissionId, this.sectionData.id)), take(1)) - .subscribe((sectionData: WorkspaceitemSectionDataType) => { - if (isUndefined(this.formModel)) { - this.sectionData.errors = []; - // Is the first loading so init form - this.initForm(sectionData); - this.sectionData.data = sectionData; - this.subscriptions(); - this.isLoading = false; - this.cdr.detectChanges(); - } - }) + .subscribe((sectionData: WorkspaceitemSectionFormObject) => { + if (isUndefined(this.formModel)) { + this.sectionData.errors = []; + // Is the first loading so init form + this.initForm(sectionData); + this.sectionData.data = sectionData; + this.subscriptions(); + this.isLoading = false; + this.cdr.detectChanges(); + } + }) } + /** + * Unsubscribe from all subscriptions + */ onSectionDestroy() { this.subs .filter((subscription) => hasValue(subscription)) .forEach((subscription) => subscription.unsubscribe()); } + /** + * Get section status + */ protected getSectionStatus(): Observable { return this.formService.isValid(this.formId); } - hasMetadataEnrichment(sectionData): boolean { + /** + * Check if the section data has been enriched by the server + * + * @param sectionData + * the section data retrieved from the server + */ + hasMetadataEnrichment(sectionData: WorkspaceitemSectionFormObject): boolean { const diffResult = []; // compare current form data state with section data retrieved from store @@ -116,7 +187,13 @@ export class FormSectionComponent extends SectionModelComponent { return isNotEmpty(diffResult); } - initForm(sectionData: WorkspaceitemSectionDataType) { + /** + * Initialize form model + * + * @param sectionData + * the section data retrieved from the server + */ + initForm(sectionData: WorkspaceitemSectionFormObject): void { try { this.formModel = this.formBuilderService.modelFromConfiguration( this.formConfig, @@ -124,28 +201,32 @@ export class FormSectionComponent extends SectionModelComponent { sectionData, this.submissionService.getSubmissionScope()); } catch (e) { - this.translate.get('error.submission.sections.init-form-error') - .subscribe((msg) => { - const sectionError: SubmissionSectionError = { - message: msg + e.toString(), - path: '/sections/' + this.sectionData.id - }; - this.sectionService.setSectionError(this.submissionId, this.sectionData.id, sectionError); - }) - + const msg = this.translate.instant('error.submission.sections.init-form-error'); + const sectionError: SubmissionSectionError = { + message: msg + e.toString(), + path: '/sections/' + this.sectionData.id + }; + this.sectionService.setSectionError(this.submissionId, this.sectionData.id, sectionError); } } - updateForm(sectionData: WorkspaceitemSectionDataType, errors: SubmissionSectionError[]) { + /** + * Update form model + * + * @param sectionData + * the section data retrieved from the server + * @param errors + * the section errors retrieved from the server + */ + updateForm(sectionData: WorkspaceitemSectionFormObject, errors: SubmissionSectionError[]): void { if (isNotEmpty(sectionData) && !isEqual(sectionData, this.sectionData.data)) { this.sectionData.data = sectionData; if (this.hasMetadataEnrichment(sectionData)) { - this.translate.get('submission.sections.general.metadata-extracted', { sectionId: this.sectionData.id }) - .pipe(take(1)) - .subscribe((m) => { - this.notificationsService.info(null, m, null, true); - }); + const msg = this.translate.instant( + 'submission.sections.general.metadata-extracted', + { sectionId: this.sectionData.id }); + this.notificationsService.info(null, msg, null, true); this.isUpdating = true; this.formModel = null; this.cdr.detectChanges(); @@ -153,6 +234,8 @@ export class FormSectionComponent extends SectionModelComponent { this.checksForErrors(errors); this.isUpdating = false; this.cdr.detectChanges(); + } else if (isNotEmpty(errors) || isNotEmpty(this.sectionData.errors)) { + this.checksForErrors(errors); } } else if (isNotEmpty(errors) || isNotEmpty(this.sectionData.errors)) { this.checksForErrors(errors); @@ -160,7 +243,13 @@ export class FormSectionComponent extends SectionModelComponent { } - checksForErrors(errors: SubmissionSectionError[]) { + /** + * Check if there are form validation error retrieved from server + * + * @param errors + * the section errors retrieved from the server + */ + checksForErrors(errors: SubmissionSectionError[]): void { this.formService.isFormInitialized(this.formId).pipe( find((status: boolean) => status === true && !this.isUpdating)) .subscribe(() => { @@ -170,9 +259,11 @@ export class FormSectionComponent extends SectionModelComponent { }); } - subscriptions() { + /** + * Initialize all subscriptions + */ + subscriptions(): void { this.subs.push( - /** * Subscribe to form's data */ @@ -181,6 +272,7 @@ export class FormSectionComponent extends SectionModelComponent { .subscribe((formData) => { this.formData = formData; }), + /** * Subscribe to section state */ @@ -190,12 +282,16 @@ export class FormSectionComponent extends SectionModelComponent { }), distinctUntilChanged()) .subscribe((sectionState: SubmissionSectionObject) => { - this.updateForm(sectionState.data, sectionState.errors); + this.updateForm(sectionState.data as WorkspaceitemSectionFormObject, sectionState.errors); }) ) } - onChange(event: DynamicFormControlEvent) { + /** + * Method called when a form dfChange event is fired. + * Dispatch form operations based on changes. + */ + onChange(event: DynamicFormControlEvent): void { this.formOperationsService.dispatchOperationsFromEvent( this.pathCombiner, event, @@ -209,7 +305,11 @@ export class FormSectionComponent extends SectionModelComponent { } } - onFocus(event: DynamicFormControlEvent) { + /** + * Method called when a form dfFocus event is fired. + * Initialize [FormFieldPreviousValueObject] instance. + */ + onFocus(event: DynamicFormControlEvent): void { const value = this.formOperationsService.getFieldValueFromChangeEvent(event); const path = this.formBuilderService.getPath(event.model); if (this.formBuilderService.hasMappedGroupValue(event.model)) { @@ -221,7 +321,11 @@ export class FormSectionComponent extends SectionModelComponent { } } - onRemove(event: DynamicFormControlEvent) { + /** + * Method called when a form remove event is fired. + * Dispatch form operations based on changes. + */ + onRemove(event: DynamicFormControlEvent): void { this.formOperationsService.dispatchOperationsFromEvent( this.pathCombiner, event, @@ -229,7 +333,15 @@ export class FormSectionComponent extends SectionModelComponent { this.hasStoredValue(this.formBuilderService.getId(event.model), this.formOperationsService.getArrayIndexFromEvent(event))); } - hasStoredValue(fieldId, index) { + /** + * Check if the specified form field has already a value stored + * + * @param fieldId + * the section data retrieved from the serverù + * @param index + * the section data retrieved from the server + */ + hasStoredValue(fieldId, index): boolean { if (isNotEmpty(this.sectionData.data)) { return this.sectionData.data.hasOwnProperty(fieldId) && isNotEmpty(this.sectionData.data[fieldId][index]); } else { From 825464bb9cb7cedaa23a8bf956d4c6ce3064acd5 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 18 Mar 2019 11:38:38 +0100 Subject: [PATCH 11/27] Fixed test --- .../submission/sections/form/section-form.component.spec.ts | 1 + src/app/submission/sections/form/section-form.component.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/submission/sections/form/section-form.component.spec.ts b/src/app/submission/sections/form/section-form.component.spec.ts index b877771b73..cf16cc9317 100644 --- a/src/app/submission/sections/form/section-form.component.spec.ts +++ b/src/app/submission/sections/form/section-form.component.spec.ts @@ -266,6 +266,7 @@ describe('FormSectionComponent test suite', () => { it('should set a section Error when init form model fails', () => { formBuilderService.modelFromConfiguration.and.throwError('test'); + translateService.instant.and.returnValue('test'); const sectionData = {}; const sectionError: SubmissionSectionError = { message: 'test' + 'Error: test', diff --git a/src/app/submission/sections/form/section-form.component.ts b/src/app/submission/sections/form/section-form.component.ts index a62c52f634..6ed91782d0 100644 --- a/src/app/submission/sections/form/section-form.component.ts +++ b/src/app/submission/sections/form/section-form.component.ts @@ -201,9 +201,9 @@ export class FormSectionComponent extends SectionModelComponent { sectionData, this.submissionService.getSubmissionScope()); } catch (e) { - const msg = this.translate.instant('error.submission.sections.init-form-error'); + const msg: string = this.translate.instant('error.submission.sections.init-form-error') + e.toString(); const sectionError: SubmissionSectionError = { - message: msg + e.toString(), + message: msg, path: '/sections/' + this.sectionData.id }; this.sectionService.setSectionError(this.submissionId, this.sectionData.id, sectionError); From 6335d61dda2f7083829487efcedbf6ab5c258e67 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 20 Mar 2019 19:57:07 +0100 Subject: [PATCH 12/27] Added more comments --- resources/i18n/en.json | 2 +- .../edit/submission-edit.component.ts | 44 ++++- .../submission-form-collection.component.ts | 113 +++++++++++- .../submission-form-footer.component.ts | 50 +++++- .../submission-form-section-add.component.ts | 37 +++- .../form/submission-form.component.ts | 93 +++++++++- .../submission-upload-files.component.ts | 83 ++++++++- .../objects/submission-objects.effects.ts | 2 +- .../objects/submission-objects.reducer.ts | 2 +- .../container/section-container.component.ts | 52 +++++- .../section-form-operations.service.spec.ts | 4 +- .../form/section-form-operations.service.ts | 131 +++++++++++++- .../sections/form/section-form.component.ts | 24 ++- .../license/section-license.component.ts | 72 +++++++- .../sections/models/section-data.model.ts | 34 ++++ .../sections/models/section.model.ts | 63 +++++++ .../submission/sections/sections.directive.ts | 170 ++++++++++++++++-- ...tion-upload-access-conditions.component.ts | 19 ++ .../section-upload-file-edit.component.ts | 140 ++++++++++++--- .../file/section-upload-file.component.ts | 165 +++++++++++++++-- .../section-upload-file-view.component.ts | 35 +++- .../upload/section-upload.component.html | 3 - .../upload/section-upload.component.ts | 127 ++++++++++--- .../sections/upload/section-upload.service.ts | 96 ++++++++-- .../submission/server-submission.service.ts | 24 +++ .../submit/submission-submit.component.ts | 43 ++++- 26 files changed, 1505 insertions(+), 123 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 42847f8ec8..f52ca12730 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -743,7 +743,7 @@ "upload-successful": "Upload successful", "upload-failed": "Upload failed", "header.policy.default.nolist": "Uploaded files in the {{collectionName}} collection will be accessible according to the following group(s):", - "header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicity decided for the single file, with the following group(s):", + "header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "form": { "access-condition-label": "Access condition type", "from-label": "Access grant from", diff --git a/src/app/submission/edit/submission-edit.component.ts b/src/app/submission/edit/submission-edit.component.ts index d128191d79..60c8b9a7a3 100644 --- a/src/app/submission/edit/submission-edit.component.ts +++ b/src/app/submission/edit/submission-edit.component.ts @@ -14,17 +14,44 @@ import { SubmissionObject } from '../../core/submission/models/submission-object import { Collection } from '../../core/shared/collection.model'; import { RemoteData } from '../../core/data/remote-data'; +/** + * This component allows to edit an existing workspaceitem/workflowitem. + */ @Component({ selector: 'ds-submission-edit', styleUrls: ['./submission-edit.component.scss'], templateUrl: './submission-edit.component.html' }) - export class SubmissionEditComponent implements OnDestroy, OnInit { + + /** + * The collection id this submission belonging to + * @type {string} + */ public collectionId: string; + + /** + * The list of submission's sections + * @type {WorkspaceitemSectionsObject} + */ public sections: WorkspaceitemSectionsObject; + + /** + * The submission self url + * @type {string} + */ public selfUrl: string; + + /** + * The configuration object that define this submission + * @type {SubmissionDefinitionsModel} + */ public submissionDefinition: SubmissionDefinitionsModel; + + /** + * The submission id + * @type {string} + */ public submissionId: string; /** @@ -33,6 +60,16 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { */ private subs: Subscription[] = []; + /** + * Initialize instance variables + * + * @param {ChangeDetectorRef} changeDetectorRef + * @param {NotificationsService} notificationsService + * @param {ActivatedRoute} route + * @param {Router} router + * @param {SubmissionService} submissionService + * @param {TranslateService} translate + */ constructor(private changeDetectorRef: ChangeDetectorRef, private notificationsService: NotificationsService, private route: ActivatedRoute, @@ -41,6 +78,9 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { private translate: TranslateService) { } + /** + * Retrieve workspaceitem/workflowitem from server and initialize all instance variables + */ ngOnInit() { this.subs.push(this.route.paramMap.pipe( switchMap((params: ParamMap) => this.submissionService.retrieveSubmission(params.get('id'))), @@ -70,7 +110,7 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { } /** - * Method provided by Angular. Invoked when the instance is destroyed. + * Unsubscribe from all subscriptions */ ngOnDestroy() { this.subs diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index fa54280aae..2fe424bd3f 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -36,24 +36,48 @@ import { SubmissionService } from '../../submission.service'; import { SubmissionObject } from '../../../core/submission/models/submission-object.model'; import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service'; +/** + * An interface to represent a collection entry + */ interface CollectionListEntryItem { id: string; name: string; } +/** + * An interface to represent an entry in the collection list + */ interface CollectionListEntry { communities: CollectionListEntryItem[], collection: CollectionListEntryItem } +/** + * This component allows to show the current collection the submission belonging to and to change it. + */ @Component({ selector: 'ds-submission-form-collection', styleUrls: ['./submission-form-collection.component.scss'], templateUrl: './submission-form-collection.component.html' }) export class SubmissionFormCollectionComponent implements OnChanges, OnInit { + + /** + * The current collection id this submission belonging to + * @type {string} + */ @Input() currentCollectionId: string; + + /** + * The current configuration object that define this submission + * @type {SubmissionDefinitionsModel} + */ @Input() currentDefinition: string; + + /** + * The submission id + * @type {string} + */ @Input() submissionId; /** @@ -62,18 +86,69 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { */ @Output() collectionChange: EventEmitter = new EventEmitter(); + /** + * A boolean representing if this dropdown button is disabled + * @type {BehaviorSubject} + */ public disabled$ = new BehaviorSubject(true); - public model: any; + + /** + * The search form control + * @type {FormControl} + */ public searchField: FormControl = new FormControl(); + + /** + * The collection list obtained from a search + * @type {Observable} + */ public searchListCollection$: Observable; + + /** + * The selected collection id + * @type {string} + */ public selectedCollectionId: string; + + /** + * The selected collection name + * @type {Observable} + */ public selectedCollectionName$: Observable; + /** + * The JsonPatchOperationPathCombiner object + * @type {JsonPatchOperationPathCombiner} + */ protected pathCombiner: JsonPatchOperationPathCombiner; + + /** + * A boolean representing if dropdown list is scrollable to the bottom + * @type {boolean} + */ private scrollableBottom = false; + + /** + * A boolean representing if dropdown list is scrollable to the top + * @type {boolean} + */ private scrollableTop = false; + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ private subs: Subscription[] = []; + /** + * Initialize instance variables + * + * @param {ChangeDetectorRef} cdr + * @param {CommunityDataService} communityDataService + * @param {JsonPatchOperationsBuilder} operationsBuilder + * @param {SubmissionJsonPatchOperationsService} operationsService + * @param {SubmissionService} submissionService + */ constructor(protected cdr: ChangeDetectorRef, private communityDataService: CommunityDataService, private operationsBuilder: JsonPatchOperationsBuilder, @@ -81,6 +156,13 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { private submissionService: SubmissionService) { } + /** + * Method called on mousewheel event, it prevent the page scroll + * when arriving at the top/bottom of dropdown menu + * + * @param event + * mousewheel event + */ @HostListener('mousewheel', ['$event']) onMousewheel(event) { if (event.wheelDelta > 0 && this.scrollableTop) { event.preventDefault(); @@ -90,11 +172,19 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { } } + /** + * Check if dropdown scrollbar is at the top or bottom of the dropdown list + * + * @param event + */ onScroll(event) { this.scrollableBottom = (event.target.scrollTop + event.target.clientHeight === event.target.scrollHeight); this.scrollableTop = (event.target.scrollTop === 0); } + /** + * Initialize collection list + */ ngOnChanges(changes: SimpleChanges) { if (hasValue(changes.currentCollectionId) && hasValue(changes.currentCollectionId.currentValue)) { @@ -153,14 +243,26 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { } } + /** + * Initialize all instance variables + */ ngOnInit() { this.pathCombiner = new JsonPatchOperationPathCombiner('sections', 'collection'); } + /** + * Unsubscribe from all subscriptions + */ ngOnDestroy(): void { this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } + /** + * Emit a [collectionChange] event when a new collection is selected from list + * + * @param event + * the selected [CollectionListEntryItem] + */ onSelect(event) { this.searchField.reset(); this.disabled$.next(true); @@ -181,10 +283,19 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { ); } + /** + * Reset search form control on dropdown menu close + */ onClose() { this.searchField.reset(); } + /** + * Reset search form control when dropdown menu is closed + * + * @param isOpen + * Representing if the dropdown menu is open or not. + */ toggled(isOpen: boolean) { if (!isOpen) { this.searchField.reset(); diff --git a/src/app/submission/form/footer/submission-form-footer.component.ts b/src/app/submission/form/footer/submission-form-footer.component.ts index 7245234fb4..4f4e355397 100644 --- a/src/app/submission/form/footer/submission-form-footer.component.ts +++ b/src/app/submission/form/footer/submission-form-footer.component.ts @@ -9,6 +9,9 @@ import { SubmissionService } from '../../submission.service'; import { SubmissionScopeType } from '../../../core/submission/submission-scope-type'; import { isNotEmpty } from '../../../shared/empty.util'; +/** + * This component represents submission form footer bar. + */ @Component({ selector: 'ds-submission-form-footer', styleUrls: ['./submission-form-footer.component.scss'], @@ -16,18 +19,51 @@ import { isNotEmpty } from '../../../shared/empty.util'; }) export class SubmissionFormFooterComponent implements OnChanges { - @Input() submissionId; + /** + * The submission id + * @type {string} + */ + @Input() submissionId: string; + /** + * A boolean representing if a submission deposit operation is pending + * @type {Observable} + */ public processingDepositStatus: Observable; + + /** + * A boolean representing if a submission save operation is pending + * @type {Observable} + */ public processingSaveStatus: Observable; + + /** + * A boolean representing if showing deposit and discard buttons + * @type {Observable} + */ public showDepositAndDiscard: Observable; + + /** + * A boolean representing if submission form is valid or not + * @type {Observable} + */ private submissionIsInvalid: Observable = observableOf(true); + /** + * Initialize instance variables + * + * @param {NgbModal} modalService + * @param {SubmissionRestService} restService + * @param {SubmissionService} submissionService + */ constructor(private modalService: NgbModal, private restService: SubmissionRestService, private submissionService: SubmissionService) { } + /** + * Initialize all instance variables + */ ngOnChanges(changes: SimpleChanges) { if (isNotEmpty(this.submissionId)) { this.submissionIsInvalid = this.submissionService.getSubmissionStatus(this.submissionId).pipe( @@ -40,18 +76,30 @@ export class SubmissionFormFooterComponent implements OnChanges { } } + /** + * Dispatch a submission save action + */ save(event) { this.submissionService.dispatchSave(this.submissionId); } + /** + * Dispatch a submission save for later action + */ saveLater(event) { this.submissionService.dispatchSaveForLater(this.submissionId); } + /** + * Dispatch a submission deposit action + */ public deposit(event) { this.submissionService.dispatchDeposit(this.submissionId); } + /** + * Dispatch a submission discard action + */ public confirmDiscard(content) { this.modalService.open(content).result.then( (result) => { diff --git a/src/app/submission/form/section-add/submission-form-section-add.component.ts b/src/app/submission/form/section-add/submission-form-section-add.component.ts index 20db74feac..48ba07dad1 100644 --- a/src/app/submission/form/section-add/submission-form-section-add.component.ts +++ b/src/app/submission/form/section-add/submission-form-section-add.component.ts @@ -1,30 +1,62 @@ import { Component, Input, OnInit, } from '@angular/core'; import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; import { SectionsService } from '../../sections/sections.service'; import { HostWindowService } from '../../../shared/host-window.service'; import { SubmissionService } from '../../submission.service'; import { SectionDataObject } from '../../sections/models/section-data.model'; -import { map } from 'rxjs/operators'; +/** + * This component allow to add any new section to submission form + */ @Component({ selector: 'ds-submission-form-section-add', styleUrls: [ './submission-form-section-add.component.scss' ], templateUrl: './submission-form-section-add.component.html' }) export class SubmissionFormSectionAddComponent implements OnInit { + + /** + * The collection id this submission belonging to + * @type {string} + */ @Input() collectionId: string; + + /** + * The submission id + * @type {string} + */ @Input() submissionId: string; + /** + * The possible section list to add + * @type {Observable} + */ public sectionList$: Observable; + + /** + * A boolean representing if there are available sections to add + * @type {Observable} + */ public hasSections$: Observable; + /** + * Initialize instance variables + * + * @param {SectionsService} sectionService + * @param {SubmissionService} submissionService + * @param {HostWindowService} windowService + */ constructor(private sectionService: SectionsService, private submissionService: SubmissionService, public windowService: HostWindowService) { } + /** + * Initialize all instance variables + */ ngOnInit() { this.sectionList$ = this.submissionService.getDisabledSectionsList(this.submissionId); this.hasSections$ = this.sectionList$.pipe( @@ -32,6 +64,9 @@ export class SubmissionFormSectionAddComponent implements OnInit { ) } + /** + * Dispatch an action to add a new section + */ addSection(sectionId) { this.sectionService.addSection(this.submissionId, sectionId); } diff --git a/src/app/submission/form/submission-form.component.ts b/src/app/submission/form/submission-form.component.ts index deb39fbae2..58725d79ff 100644 --- a/src/app/submission/form/submission-form.component.ts +++ b/src/app/submission/form/submission-form.component.ts @@ -15,22 +15,68 @@ import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; import { Collection } from '../../core/shared/collection.model'; import { SubmissionObject } from '../../core/submission/models/submission-object.model'; +/** + * This component represents the submission form. + */ @Component({ selector: 'ds-submission-submit-form', styleUrls: ['./submission-form.component.scss'], templateUrl: './submission-form.component.html', }) export class SubmissionFormComponent implements OnChanges, OnDestroy { + + /** + * The collection id this submission belonging to + * @type {string} + */ @Input() collectionId: string; + + /** + * The list of submission's sections + * @type {WorkspaceitemSectionsObject} + */ @Input() sections: WorkspaceitemSectionsObject; + + /** + * The submission self url + * @type {string} + */ @Input() selfUrl: string; + + /** + * The configuration object that define this submission + * @type {SubmissionDefinitionsModel} + */ @Input() submissionDefinition: SubmissionDefinitionsModel; + + /** + * The submission id + * @type {string} + */ @Input() submissionId: string; + /** + * The configuration id that define this submission + * @type {string} + */ public definitionId: string; - public test = true; + + /** + * A boolean representing if a submission form is pending + * @type {Observable} + */ public loading: Observable = observableOf(true); - public submissionSections: Observable; + + /** + * Observable of the list of submission's sections + * @type {Observable} + */ + public submissionSections: Observable; + + /** + * The uploader configuration options + * @type {UploaderOptions} + */ public uploadFilesOptions: UploaderOptions = { url: '', authToken: null, @@ -38,9 +84,26 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { itemAlias: null }; + /** + * A boolean representing if component is active + * @type {boolean} + */ protected isActive: boolean; + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ protected subs: Subscription[] = []; + /** + * Initialize instance variables + * + * @param {AuthService} authService + * @param {ChangeDetectorRef} changeDetectorRef + * @param {HALEndpointService} halService + * @param {SubmissionService} submissionService + */ constructor( private authService: AuthService, private changeDetectorRef: ChangeDetectorRef, @@ -49,9 +112,14 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { this.isActive = true; } + /** + * Initialize all instance variables and retrieve form configuration + */ ngOnChanges(changes: SimpleChanges) { if (this.collectionId && this.submissionId) { this.isActive = true; + + // retrieve submission's section list this.submissionSections = this.submissionService.getSubmissionObject(this.submissionId).pipe( filter(() => this.isActive), map((submission: SubmissionObjectEntry) => submission.isLoading), @@ -65,12 +133,14 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { } })); + // check if is submission loading this.loading = this.submissionService.getSubmissionObject(this.submissionId).pipe( filter(() => this.isActive), map((submission: SubmissionObjectEntry) => submission.isLoading), map((isLoading: boolean) => isLoading), distinctUntilChanged()); + // init submission state this.subs.push( this.halService.getEndpoint('workspaceitems').pipe( filter((href: string) => isNotEmpty(href)), @@ -89,10 +159,16 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { this.changeDetectorRef.detectChanges(); }) ); + + // start auto save this.submissionService.startAutoSave(this.submissionId); } } + /** + * Unsubscribe from all subscriptions, destroy instance variables + * and reset submission state + */ ngOnDestroy() { this.isActive = false; this.submissionService.stopAutoSave(); @@ -102,6 +178,13 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { .forEach((subscription) => subscription.unsubscribe()); } + /** + * On collection change reset submission state in case of it has a different + * submission definition + * + * @param submissionObject + * new submission object + */ onCollectionChange(submissionObject: SubmissionObject) { this.collectionId = (submissionObject.collection as Collection).id; if (this.definitionId !== (submissionObject.submissionDefinition as SubmissionDefinitionsModel).name) { @@ -119,10 +202,16 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { } } + /** + * Check if submission form is loading + */ isLoading(): Observable { return this.loading; } + /** + * Check if submission form is loading + */ protected getSectionsList(): Observable { return this.submissionService.getSubmissionSections(this.submissionId).pipe( filter((sections: SectionDataObject[]) => isNotEmpty(sections)), diff --git a/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts b/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts index 0cc226772c..be3e6b5c8c 100644 --- a/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts +++ b/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts @@ -14,24 +14,72 @@ import { UploaderOptions } from '../../../shared/uploader/uploader-options.model import parseSectionErrors from '../../utils/parseSectionErrors'; import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service'; +/** + * This component represents the drop zone that provides to add files to the submission. + */ @Component({ selector: 'ds-submission-upload-files', templateUrl: './submission-upload-files.component.html', }) export class SubmissionUploadFilesComponent implements OnChanges { - @Input() collectionId; - @Input() submissionId; - @Input() sectionId; + /** + * The collection id this submission belonging to + * @type {string} + */ + @Input() collectionId: string; + + /** + * The submission id + * @type {string} + */ + @Input() submissionId: string; + + /** + * The upload section id + * @type {string} + */ + @Input() sectionId: string; + + /** + * The uploader configuration options + * @type {UploaderOptions} + */ @Input() uploadFilesOptions: UploaderOptions; + /** + * A boolean representing if is possible to active drop zone over the document page + * @type {boolean} + */ public enableDragOverDocument = true; + + /** + * i18n message label + * @type {string} + */ public dropOverDocumentMsg = 'submission.sections.upload.drop-message'; + + /** + * i18n message label + * @type {string} + */ public dropMsg = 'submission.sections.upload.drop-message'; - private subs = []; + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ + private subs: Subscription[] = []; + + /** + * A boolean representing if upload functionality is enabled + * @type {boolean} + */ private uploadEnabled: Observable = observableOf(false); + /** + * Save submission before to upload a file + */ public onBeforeUpload = () => { const sub: Subscription = this.operationsService.jsonPatchByResourceType( this.submissionService.getSubmissionObjectLinkName(), @@ -42,6 +90,15 @@ export class SubmissionUploadFilesComponent implements OnChanges { return sub; }; + /** + * Initialize instance variables + * + * @param {NotificationsService} notificationsService + * @param {SubmissionJsonPatchOperationsService} operationsService + * @param {SectionsService} sectionService + * @param {SubmissionService} submissionService + * @param {TranslateService} translate + */ constructor(private notificationsService: NotificationsService, private operationsService: SubmissionJsonPatchOperationsService, private sectionService: SectionsService, @@ -49,10 +106,19 @@ export class SubmissionUploadFilesComponent implements OnChanges { private translate: TranslateService) { } + /** + * Check if upload functionality is enabled + */ ngOnChanges() { this.uploadEnabled = this.sectionService.isSectionAvailable(this.submissionId, this.sectionId); } + /** + * Parse the submission object retrieved from REST after upload + * + * @param workspaceitem + * The submission object retrieved from REST + */ public onCompleteItem(workspaceitem: Workspaceitem) { // Checks if upload section is enabled so do upload this.subs.push( @@ -61,8 +127,8 @@ export class SubmissionUploadFilesComponent implements OnChanges { .subscribe((isUploadEnabled) => { if (isUploadEnabled) { - const {sections} = workspaceitem; - const {errors} = workspaceitem; + const { sections } = workspaceitem; + const { errors } = workspaceitem; const errorsList = parseSectionErrors(errors); if (sections && isNotEmpty(sections)) { @@ -87,12 +153,15 @@ export class SubmissionUploadFilesComponent implements OnChanges { ); } + /** + * Show error notification on upload fails + */ public onUploadError() { this.notificationsService.error(null, this.translate.get('submission.sections.upload.upload-failed')); } /** - * Method provided by Angular. Invoked when the instance is destroyed. + * Unsubscribe from all subscriptions */ ngOnDestroy() { this.subs diff --git a/src/app/submission/objects/submission-objects.effects.ts b/src/app/submission/objects/submission-objects.effects.ts index f4b74807cf..f5c8887320 100644 --- a/src/app/submission/objects/submission-objects.effects.ts +++ b/src/app/submission/objects/submission-objects.effects.ts @@ -277,7 +277,7 @@ export class SubmissionObjectEffects { } /** - * Parse the submission object retrieved from REST haven't section errors and return actions to dispatch + * Parse the submission object retrieved from REST and return actions to dispatch * * @param currentState * The current SubmissionObjectEntry diff --git a/src/app/submission/objects/submission-objects.reducer.ts b/src/app/submission/objects/submission-objects.reducer.ts index 68ac7d56b9..1a65783945 100644 --- a/src/app/submission/objects/submission-objects.reducer.ts +++ b/src/app/submission/objects/submission-objects.reducer.ts @@ -138,7 +138,7 @@ export interface SubmissionObjectEntry { collection?: string, /** - * The configuration name tha define this submission + * The configuration name that define this submission */ definition?: string, diff --git a/src/app/submission/sections/container/section-container.component.ts b/src/app/submission/sections/container/section-container.component.ts index 7339df5892..b1453dffb3 100644 --- a/src/app/submission/sections/container/section-container.component.ts +++ b/src/app/submission/sections/container/section-container.component.ts @@ -3,29 +3,64 @@ import { Component, Injector, Input, OnInit, ViewChild } from '@angular/core'; import { SectionsDirective } from '../sections.directive'; import { SectionDataObject } from '../models/section-data.model'; import { rendersSectionType } from '../sections-decorator'; -import { SectionsType } from '../sections-type'; import { AlertType } from '../../../shared/alerts/aletrs-type'; +/** + * This component represents a section that contains the submission license form. + */ @Component({ selector: 'ds-submission-form-section-container', templateUrl: './section-container.component.html', styleUrls: ['./section-container.component.scss'] }) export class SectionContainerComponent implements OnInit { + + /** + * The collection id this submission belonging to + * @type {string} + */ @Input() collectionId: string; + + /** + * The section data + * @type {SectionDataObject} + */ @Input() sectionData: SectionDataObject; + + /** + * The submission id + * @type {string} + */ @Input() submissionId: string; + /** + * The AlertType enumeration + * @type {AlertType} + */ public AlertTypeEnum = AlertType; - public active = true; - public objectInjector: Injector; - public sectionComponentType: SectionsType; + /** + * Injector to inject a section component with the @Input parameters + * @type {Injector} + */ + public objectInjector: Injector; + + /** + * The SectionsDirective reference + */ @ViewChild('sectionRef') sectionRef: SectionsDirective; + /** + * Initialize instance variables + * + * @param {Injector} injector + */ constructor(private injector: Injector) { } + /** + * Initialize all instance variables + */ ngOnInit() { this.objectInjector = Injector.create({ providers: [ @@ -37,12 +72,21 @@ export class SectionContainerComponent implements OnInit { }); } + /** + * Remove section from submission form + * + * @param event + * the event emitted + */ public removeSection(event) { event.preventDefault(); event.stopPropagation(); this.sectionRef.removeSection(this.submissionId, this.sectionData.id); } + /** + * Find the correct component based on the section's type + */ getSectionContent(): string { return rendersSectionType(this.sectionData.sectionType); } diff --git a/src/app/submission/sections/form/section-form-operations.service.spec.ts b/src/app/submission/sections/form/section-form-operations.service.spec.ts index 1519da7557..c90fc62360 100644 --- a/src/app/submission/sections/form/section-form-operations.service.spec.ts +++ b/src/app/submission/sections/form/section-form-operations.service.spec.ts @@ -156,7 +156,7 @@ describe('SectionFormOperationsService test suite', () => { } }; - expect(service.isPartOfArrayOfGroup(model)).toBeTruthy(); + expect(service.isPartOfArrayOfGroup(model as any)).toBeTruthy(); }); it('should return false when parent element doesn\'t belong to an array group element', () => { @@ -164,7 +164,7 @@ describe('SectionFormOperationsService test suite', () => { parent: null }; - expect(service.isPartOfArrayOfGroup(model)).toBeFalsy(); + expect(service.isPartOfArrayOfGroup(model as any)).toBeFalsy(); }); }); diff --git a/src/app/submission/sections/form/section-form-operations.service.ts b/src/app/submission/sections/form/section-form-operations.service.ts index 9483c8d23e..2d6b1c5477 100644 --- a/src/app/submission/sections/form/section-form-operations.service.ts +++ b/src/app/submission/sections/form/section-form-operations.service.ts @@ -21,12 +21,35 @@ import { FormFieldMetadataValueObject } from '../../../shared/form/builder/model import { DynamicQualdropModel } from '../../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model'; import { DynamicRelationGroupModel } from '../../../shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model'; +/** + * The service handling all form section operations + */ @Injectable() export class SectionFormOperationsService { - constructor(private formBuilder: FormBuilderService, private operationsBuilder: JsonPatchOperationsBuilder) { + /** + * Initialize service variables + * + * @param {FormBuilderService} formBuilder + * @param {JsonPatchOperationsBuilder} operationsBuilder + */ + constructor( + private formBuilder: FormBuilderService, + private operationsBuilder: JsonPatchOperationsBuilder) { } + /** + * Dispatch properly method based on form operation type + * + * @param pathCombiner + * the [[JsonPatchOperationPathCombiner]] object for the specified operation + * @param event + * the [[DynamicFormControlEvent]] for the specified operation + * @param previousValue + * the [[FormFieldPreviousValueObject]] for the specified operation + * @param hasStoredValue + * representing if field value related to the specified operation has stored value + */ public dispatchOperationsFromEvent(pathCombiner: JsonPatchOperationPathCombiner, event: DynamicFormControlEvent, previousValue: FormFieldPreviousValueObject, @@ -43,6 +66,14 @@ export class SectionFormOperationsService { } } + /** + * Return index if specified field is part of fields array + * + * @param event + * the [[DynamicFormControlEvent]] for the specified operation + * @return number + * the array index is part of array, zero otherwise + */ public getArrayIndexFromEvent(event: DynamicFormControlEvent): number { let fieldIndex: number; if (isNotEmpty(event)) { @@ -60,7 +91,15 @@ export class SectionFormOperationsService { return isNotUndefined(fieldIndex) ? fieldIndex : 0; } - public isPartOfArrayOfGroup(model: any): boolean { + /** + * Check if specified model is part of array of group + * + * @param model + * the [[DynamicFormControlModel]] model + * @return boolean + * true if is part of array, false otherwise + */ + public isPartOfArrayOfGroup(model: DynamicFormControlModel): boolean { return (isNotNull(model.parent) && (model.parent as any).type === DYNAMIC_FORM_CONTROL_TYPE_GROUP && (model.parent as any).parent @@ -68,7 +107,15 @@ export class SectionFormOperationsService { && (model.parent as any).parent.context.type === DYNAMIC_FORM_CONTROL_TYPE_ARRAY); } - public getQualdropValueMap(event): Map { + /** + * Return a map for the values of a Qualdrop field + * + * @param event + * the [[DynamicFormControlEvent]] for the specified operation + * @return Map + * the map of values + */ + public getQualdropValueMap(event: DynamicFormControlEvent): Map { const metadataValueMap = new Map(); const context = this.formBuilder.isQualdropGroup(event.model) @@ -87,12 +134,28 @@ export class SectionFormOperationsService { return metadataValueMap; } + /** + * Return the absolute path for the field interesting in the specified operation + * + * @param event + * the [[DynamicFormControlEvent]] for the specified operation + * @return string + * the field path + */ public getFieldPathFromEvent(event: DynamicFormControlEvent): string { const fieldIndex = this.getArrayIndexFromEvent(event); const fieldId = this.getFieldPathSegmentedFromChangeEvent(event); return (isNotUndefined(fieldIndex)) ? fieldId + '/' + fieldIndex : fieldId; } + /** + * Return the absolute path for the Qualdrop field interesting in the specified operation + * + * @param event + * the [[DynamicFormControlEvent]] for the specified operation + * @return string + * the field path + */ public getQualdropItemPathFromEvent(event: DynamicFormControlEvent): string { const fieldIndex = this.getArrayIndexFromEvent(event); const metadataValueMap = new Map(); @@ -117,6 +180,14 @@ export class SectionFormOperationsService { return path; } + /** + * Return the segmented path for the field interesting in the specified change operation + * + * @param event + * the [[DynamicFormControlEvent]] for the specified operation + * @return string + * the field path + */ public getFieldPathSegmentedFromChangeEvent(event: DynamicFormControlEvent): string { let fieldId; if (this.formBuilder.isQualdropGroup(event.model as DynamicFormControlModel)) { @@ -129,6 +200,14 @@ export class SectionFormOperationsService { return fieldId; } + /** + * Return the value of the field interesting in the specified change operation + * + * @param event + * the [[DynamicFormControlEvent]] for the specified operation + * @return any + * the field value + */ public getFieldValueFromChangeEvent(event: DynamicFormControlEvent): any { let fieldValue; const value = (event.model as any).value; @@ -142,12 +221,12 @@ export class SectionFormOperationsService { if ((event.model as DsDynamicInputModel).hasAuthority) { if (Array.isArray(value)) { value.forEach((authority, index) => { - authority = Object.assign(new AuthorityValue(), authority, {language}); + authority = Object.assign(new AuthorityValue(), authority, { language }); value[index] = authority; }); fieldValue = value; } else { - fieldValue = Object.assign(new AuthorityValue(), value, {language}); + fieldValue = Object.assign(new AuthorityValue(), value, { language }); } } else { // Language without Authority (input, textArea) @@ -162,6 +241,14 @@ export class SectionFormOperationsService { return fieldValue; } + /** + * Return a map for the values of an array of field + * + * @param items + * the list of items + * @return Map + * the map of values + */ public getValueMap(items: any[]): Map { const metadataValueMap = new Map(); @@ -177,6 +264,16 @@ export class SectionFormOperationsService { return metadataValueMap; } + /** + * Handle form remove operations + * + * @param pathCombiner + * the [[JsonPatchOperationPathCombiner]] object for the specified operation + * @param event + * the [[DynamicFormControlEvent]] for the specified operation + * @param previousValue + * the [[FormFieldPreviousValueObject]] for the specified operation + */ protected dispatchOperationsFromRemoveEvent(pathCombiner: JsonPatchOperationPathCombiner, event: DynamicFormControlEvent, previousValue: FormFieldPreviousValueObject): void { @@ -189,6 +286,18 @@ export class SectionFormOperationsService { } } + /** + * Handle form change operations + * + * @param pathCombiner + * the [[JsonPatchOperationPathCombiner]] object for the specified operation + * @param event + * the [[DynamicFormControlEvent]] for the specified operation + * @param previousValue + * the [[FormFieldPreviousValueObject]] for the specified operation + * @param hasStoredValue + * representing if field value related to the specified operation has stored value + */ protected dispatchOperationsFromChangeEvent(pathCombiner: JsonPatchOperationPathCombiner, event: DynamicFormControlEvent, previousValue: FormFieldPreviousValueObject, @@ -243,6 +352,18 @@ export class SectionFormOperationsService { } } + /** + * Handle form operations interesting a field with a map as value + * + * @param valueMap + * map of values + * @param pathCombiner + * the [[JsonPatchOperationPathCombiner]] object for the specified operation + * @param event + * the [[DynamicFormControlEvent]] for the specified operation + * @param previousValue + * the [[FormFieldPreviousValueObject]] for the specified operation + */ protected dispatchOperationsFromMap(valueMap: Map, pathCombiner: JsonPatchOperationPathCombiner, event: DynamicFormControlEvent, diff --git a/src/app/submission/sections/form/section-form.component.ts b/src/app/submission/sections/form/section-form.component.ts index 6ed91782d0..048f109f54 100644 --- a/src/app/submission/sections/form/section-form.component.ts +++ b/src/app/submission/sections/form/section-form.component.ts @@ -42,46 +42,55 @@ export class FormSectionComponent extends SectionModelComponent { /** * The form id + * @type {string} */ - public formId; + public formId: string; /** * The form model + * @type {DynamicFormControlModel[]} */ public formModel: DynamicFormControlModel[]; /** * A boolean representing if this section is updating + * @type {boolean} */ public isUpdating = false; /** * A boolean representing if this section is loading + * @type {boolean} */ public isLoading = true; /** * The form config + * @type {SubmissionFormsModel} */ protected formConfig: SubmissionFormsModel; /** * The form data + * @type {any} */ protected formData: any = Object.create({}); /** * The [JsonPatchOperationPathCombiner] object + * @type {JsonPatchOperationPathCombiner} */ protected pathCombiner: JsonPatchOperationPathCombiner; /** * The [FormFieldPreviousValueObject] object + * @type {FormFieldPreviousValueObject} */ protected previousValue: FormFieldPreviousValueObject = new FormFieldPreviousValueObject(); /** * The list of Subscription + * @type {Array} */ protected subs: Subscription[] = []; @@ -92,6 +101,7 @@ export class FormSectionComponent extends SectionModelComponent { /** * Initialize instance variables + * * @param {ChangeDetectorRef} cdr * @param {FormBuilderService} formBuilderService * @param {SectionFormOperationsService} formOperationsService @@ -158,6 +168,9 @@ export class FormSectionComponent extends SectionModelComponent { /** * Get section status + * + * @return Observable + * the section status */ protected getSectionStatus(): Observable { return this.formService.isValid(this.formId); @@ -290,6 +303,9 @@ export class FormSectionComponent extends SectionModelComponent { /** * Method called when a form dfChange event is fired. * Dispatch form operations based on changes. + * + * @param event + * the [[DynamicFormControlEvent]] emitted */ onChange(event: DynamicFormControlEvent): void { this.formOperationsService.dispatchOperationsFromEvent( @@ -308,6 +324,9 @@ export class FormSectionComponent extends SectionModelComponent { /** * Method called when a form dfFocus event is fired. * Initialize [FormFieldPreviousValueObject] instance. + * + * @param event + * the [[DynamicFormControlEvent]] emitted */ onFocus(event: DynamicFormControlEvent): void { const value = this.formOperationsService.getFieldValueFromChangeEvent(event); @@ -324,6 +343,9 @@ export class FormSectionComponent extends SectionModelComponent { /** * Method called when a form remove event is fired. * Dispatch form operations based on changes. + * + * @param event + * the [[DynamicFormControlEvent]] emitted */ onRemove(event: DynamicFormControlEvent): void { this.formOperationsService.dispatchOperationsFromEvent( diff --git a/src/app/submission/sections/license/section-license.component.ts b/src/app/submission/sections/license/section-license.component.ts index 86051ae15b..f4dae0565b 100644 --- a/src/app/submission/sections/license/section-license.component.ts +++ b/src/app/submission/sections/license/section-license.component.ts @@ -29,6 +29,9 @@ import { SectionsService } from '../sections.service'; import { SectionFormOperationsService } from '../form/section-form-operations.service'; import { FormComponent } from '../../../shared/form/form.component'; +/** + * This component represents a section that contains the submission license form. + */ @Component({ selector: 'ds-submission-section-license', styleUrls: ['./section-license.component.scss'], @@ -37,17 +40,68 @@ import { FormComponent } from '../../../shared/form/form.component'; @renderSectionFor(SectionsType.License) export class LicenseSectionComponent extends SectionModelComponent { - public formId; + /** + * The form id + * @type {string} + */ + public formId: string; + + /** + * The form model + * @type {DynamicFormControlModel[]} + */ public formModel: DynamicFormControlModel[]; + + /** + * The [[DynamicFormLayout]] object + * @type {DynamicFormLayout} + */ public formLayout: DynamicFormLayout = SECTION_LICENSE_FORM_LAYOUT; + + /** + * A boolean representing if to show form submit and cancel buttons + * @type {boolean} + */ public displaySubmit = false; + + /** + * The submission license text + * @type {Array} + */ public licenseText$: Observable; + /** + * The [[JsonPatchOperationPathCombiner]] object + * @type {JsonPatchOperationPathCombiner} + */ protected pathCombiner: JsonPatchOperationPathCombiner; + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ protected subs: Subscription[] = []; + /** + * The FormComponent reference + */ @ViewChild('formRef') private formRef: FormComponent; + /** + * Initialize instance variables + * + * @param {ChangeDetectorRef} changeDetectorRef + * @param {CollectionDataService} collectionDataService + * @param {FormBuilderService} formBuilderService + * @param {SectionFormOperationsService} formOperationsService + * @param {FormService} formService + * @param {JsonPatchOperationsBuilder} operationsBuilder + * @param {SectionsService} sectionService + * @param {SubmissionService} submissionService + * @param {string} injectedCollectionId + * @param {SectionDataObject} injectedSectionData + * @param {string} injectedSubmissionId + */ constructor(protected changeDetectorRef: ChangeDetectorRef, protected collectionDataService: CollectionDataService, protected formBuilderService: FormBuilderService, @@ -62,6 +116,9 @@ export class LicenseSectionComponent extends SectionModelComponent { super(injectedCollectionId, injectedSectionData, injectedSubmissionId); } + /** + * Initialize all instance variables and retrieve submission license + */ onSectionInit() { this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionData.id); this.formId = this.formService.getUniqueId(this.sectionData.id); @@ -126,6 +183,12 @@ export class LicenseSectionComponent extends SectionModelComponent { ); } + /** + * Get section status + * + * @return Observable + * the section status + */ protected getSectionStatus(): Observable { const model = this.formBuilderService.findById('granted', this.formModel); return (model as DynamicCheckboxModel).valueUpdates.pipe( @@ -133,6 +196,10 @@ export class LicenseSectionComponent extends SectionModelComponent { startWith((model as DynamicCheckboxModel).value)); } + /** + * Method called when a form dfChange event is fired. + * Dispatch form operations based on changes. + */ onChange(event: DynamicFormControlEvent) { const path = this.formOperationsService.getFieldPathSegmentedFromChangeEvent(event); const value = this.formOperationsService.getFieldValueFromChangeEvent(event); @@ -145,6 +212,9 @@ export class LicenseSectionComponent extends SectionModelComponent { } } + /** + * Unsubscribe from all subscriptions + */ onSectionDestroy() { this.subs .filter((subscription) => hasValue(subscription)) diff --git a/src/app/submission/sections/models/section-data.model.ts b/src/app/submission/sections/models/section-data.model.ts index 230b36eb94..8feb78fa69 100644 --- a/src/app/submission/sections/models/section-data.model.ts +++ b/src/app/submission/sections/models/section-data.model.ts @@ -2,14 +2,48 @@ import { SubmissionSectionError } from '../../objects/submission-objects.reducer import { WorkspaceitemSectionDataType } from '../../../core/submission/models/workspaceitem-sections.model'; import { SectionsType } from '../sections-type'; +/** + * An interface to represent section model + */ export interface SectionDataObject { + + /** + * The section configuration url + */ config: string; + + /** + * The section data object + */ data: WorkspaceitemSectionDataType; + + /** + * The list of the section errors + */ errors: SubmissionSectionError[]; + + /** + * The section header + */ header: string; + + /** + * The section id + */ id: string; + + /** + * A boolean representing if this section is mandatory + */ mandatory: boolean; + + /** + * The section type + */ sectionType: SectionsType; + /** + * Eventually additional fields + */ [propName: string]: any; } diff --git a/src/app/submission/sections/models/section.model.ts b/src/app/submission/sections/models/section.model.ts index 016ce62067..4e9821dcd1 100644 --- a/src/app/submission/sections/models/section.model.ts +++ b/src/app/submission/sections/models/section.model.ts @@ -16,12 +16,44 @@ export interface SectionDataModel { */ export abstract class SectionModelComponent implements OnDestroy, OnInit, SectionDataModel { protected abstract sectionService: SectionsService; + + /** + * The collection id this submission belonging to + * @type {string} + */ collectionId: string; + + /** + * The section data + * @type {SectionDataObject} + */ sectionData: SectionDataObject; + + /** + * The submission id + * @type {string} + */ submissionId: string; + + /** + * A boolean representing if this section is valid + * @type {boolean} + */ protected valid: boolean; + + /** + * The Subscription to section status observable + * @type {Subscription} + */ private sectionStatusSub: Subscription; + /** + * Initialize instance variables + * + * @param {string} injectedCollectionId + * @param {SectionDataObject} injectedSectionData + * @param {string} injectedSubmissionId + */ public constructor(@Inject('collectionIdProvider') public injectedCollectionId: string, @Inject('sectionDataProvider') public injectedSectionData: SectionDataObject, @Inject('submissionIdProvider') public injectedSubmissionId: string) { @@ -30,15 +62,43 @@ export abstract class SectionModelComponent implements OnDestroy, OnInit, Sectio this.submissionId = injectedSubmissionId; } + /** + * Call abstract methods on component init + */ ngOnInit(): void { this.onSectionInit(); this.updateSectionStatus(); } + /** + * Abstract method to implement to get section status + * + * @return Observable + * the section status + */ protected abstract getSectionStatus(): Observable; + + /** + * Abstract method called on component init. + * It must be used instead of ngOnInit on the component that extend this abstract class + * + * @return Observable + * the section status + */ protected abstract onSectionInit(): void; + + /** + * Abstract method called on component destroy. + * It must be used instead of ngOnDestroy on the component that extend this abstract class + * + * @return Observable + * the section status + */ protected abstract onSectionDestroy(): void; + /** + * Subscribe to section status + */ protected updateSectionStatus(): void { this.sectionStatusSub = this.getSectionStatus().pipe( filter((sectionStatus: boolean) => isNotUndefined(sectionStatus)), @@ -48,6 +108,9 @@ export abstract class SectionModelComponent implements OnDestroy, OnInit, Sectio }); } + /** + * Unsubscribe from all subscriptions and Call abstract methods on component destroy + */ ngOnDestroy(): void { if (hasValue(this.sectionStatusSub)) { this.sectionStatusSub.unsubscribe(); diff --git a/src/app/submission/sections/sections.directive.ts b/src/app/submission/sections/sections.directive.ts index 54d28b4d5f..0efb7225aa 100644 --- a/src/app/submission/sections/sections.directive.ts +++ b/src/app/submission/sections/sections.directive.ts @@ -10,28 +10,90 @@ import { SubmissionSectionError, SubmissionSectionObject } from '../objects/subm import parseSectionErrorPaths, { SectionErrorPath } from '../utils/parseSectionErrorPaths'; import { SubmissionService } from '../submission.service'; +/** + * Directive for handling generic section functionality + */ @Directive({ selector: '[dsSection]', exportAs: 'sectionRef' }) export class SectionsDirective implements OnDestroy, OnInit { + + /** + * A boolean representing if section is mandatory + * @type {boolean} + */ @Input() mandatory = true; - @Input() sectionId; - @Input() submissionId; + + /** + * The section id + * @type {string} + */ + @Input() sectionId: string; + + /** + * The submission id + * @type {string} + */ + @Input() submissionId: string; + + /** + * The list of generic errors related to the section + * @type {Array} + */ public genericSectionErrors: string[] = []; + + /** + * The list of all errors related to the element belonging to this section + * @type {Array} + */ public allSectionErrors: string[] = []; + + /** + * A boolean representing if section is active + * @type {boolean} + */ private active = true; - private animation = !this.mandatory; + + /** + * A boolean representing if section is enabled + * @type {boolean} + */ private enabled: Observable; + + /** + * A boolean representing the panel collapsible state: opened (true) or closed (false) + * @type {boolean} + */ private sectionState = this.mandatory; + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ private subs: Subscription[] = []; + + /** + * A boolean representing if section is valid + * @type {boolean} + */ private valid: Observable; + /** + * Initialize instance variables + * + * @param {ChangeDetectorRef} changeDetectorRef + * @param {SubmissionService} submissionService + * @param {SectionsService} sectionService + */ constructor(private changeDetectorRef: ChangeDetectorRef, private submissionService: SubmissionService, private sectionService: SectionsService) { } + /** + * Initialize instance variables + */ ngOnInit() { this.valid = this.sectionService.isSectionValid(this.submissionId, this.sectionId).pipe( map((valid: boolean) => { @@ -78,67 +140,145 @@ export class SectionsDirective implements OnDestroy, OnInit { this.enabled = this.sectionService.isSectionEnabled(this.submissionId, this.sectionId); } + /** + * Unsubscribe from all subscriptions + */ ngOnDestroy() { this.subs .filter((subscription) => hasValue(subscription)) .forEach((subscription) => subscription.unsubscribe()); } + /** + * Change section state + * + * @param event + * the event emitted + */ public sectionChange(event) { this.sectionState = event.nextState; } - public isOpen() { + /** + * Check if section panel is open + * + * @returns {boolean} + * Returns true when section panel is open + */ + public isOpen(): boolean { return this.sectionState; } - public isMandatory() { + /** + * Check if section is mandatory + * + * @returns {boolean} + * Returns true when section is mandatory + */ + public isMandatory(): boolean { return this.mandatory; } - public isAnimationsActive() { - return this.animation; - } - + /** + * Check if section panel is active + * + * @returns {boolean} + * Returns true when section panel is active + */ public isSectionActive(): boolean { return this.active; } + /** + * Check if section is enabled + * + * @returns {Observable} + * Emits true whenever section is enabled + */ public isEnabled(): Observable { return this.enabled; } + /** + * Check if section is valid + * + * @returns {Observable} + * Emits true whenever section is valid + */ public isValid(): Observable { return this.valid; } - public removeSection(submissionId, sectionId) { + /** + * Remove section panel from submission form + * + * @param submissionId + * the submission id + * @param sectionId + * the section id + * @returns {Observable} + * Emits true whenever section is valid + */ + public removeSection(submissionId: string, sectionId: string) { this.sectionService.removeSection(submissionId, sectionId) } - public hasGenericErrors() { + /** + * Check if section has only generic errors + * + * @returns {boolean} + * Returns true when section has only generic errors + */ + public hasGenericErrors(): boolean { return this.genericSectionErrors && this.genericSectionErrors.length > 0 } - public hasErrors() { + /** + * Check if section has errors + * + * @returns {boolean} + * Returns true when section has errors + */ + public hasErrors(): boolean { return (this.genericSectionErrors && this.genericSectionErrors.length > 0) || (this.allSectionErrors && this.allSectionErrors.length > 0) } - public getErrors() { + /** + * Return section errors + * + * @returns {Array} + * Returns section errors list + */ + public getErrors(): string[] { return this.genericSectionErrors; } - public setFocus(event) { + /** + * Set form focus to this section panel + * + * @param event + * The event emitted + */ + public setFocus(event): void { if (!this.active) { this.submissionService.setActiveSection(this.submissionId, this.sectionId); } } - public removeError(index) { + /** + * Remove error from list + * + * @param index + * The error array key + */ + public removeError(index): void { this.genericSectionErrors.splice(index); } + /** + * Remove all errors from list + */ public resetErrors() { if (isNotEmpty(this.genericSectionErrors)) { this.sectionService.dispatchRemoveSectionErrors(this.submissionId, this.sectionId); diff --git a/src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.ts b/src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.ts index a5252a46f3..f13f730452 100644 --- a/src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.ts +++ b/src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.ts @@ -8,18 +8,37 @@ import { isEmpty } from '../../../../shared/empty.util'; import { Group } from '../../../../core/eperson/models/group.model'; import { RemoteData } from '../../../../core/data/remote-data'; +/** + * This component represents a badge that describe an access condition + */ @Component({ selector: 'ds-section-upload-access-conditions', templateUrl: './section-upload-access-conditions.component.html', }) export class SectionUploadAccessConditionsComponent implements OnInit { + /** + * The list of resource policy + * @type {Array} + */ @Input() accessConditions: ResourcePolicy[]; + /** + * The list of access conditions + * @type {Array} + */ public accessConditionsList = []; + /** + * Initialize instance variables + * + * @param {GroupEpersonService} groupService + */ constructor(private groupService: GroupEpersonService) {} + /** + * Retrieve access conditions list + */ ngOnInit() { this.accessConditions.forEach((accessCondition: ResourcePolicy) => { if (isEmpty(accessCondition.name)) { diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts index ff441d6102..9f1b49c221 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectorRef, Component, Input, OnChanges, ViewChild } from '@angular/core'; +import { FormControl } from '@angular/forms'; -import { WorkspaceitemSectionUploadFileObject } from '../../../../../core/submission/models/workspaceitem-section-upload-file.model'; import { DYNAMIC_FORM_CONTROL_TYPE_DATEPICKER, DynamicDateControlModel, @@ -12,6 +12,8 @@ import { DynamicFormGroupModel, DynamicSelectModel } from '@ng-dynamic-forms/core'; + +import { WorkspaceitemSectionUploadFileObject } from '../../../../../core/submission/models/workspaceitem-section-upload-file.model'; import { FormBuilderService } from '../../../../../shared/form/builder/form-builder.service'; import { BITSTREAM_ACCESS_CONDITIONS_FORM_ARRAY_CONFIG, @@ -35,37 +37,113 @@ import { AccessConditionOption } from '../../../../../core/config/models/config- import { SubmissionService } from '../../../../submission.service'; import { FormService } from '../../../../../shared/form/form.service'; import { FormComponent } from '../../../../../shared/form/form.component'; -import { FormControl } from '@angular/forms'; import { Group } from '../../../../../core/eperson/models/group.model'; +/** + * This component represents the edit form for bitstream + */ @Component({ selector: 'ds-submission-upload-section-file-edit', templateUrl: './section-upload-file-edit.component.html', }) export class UploadSectionFileEditComponent implements OnChanges { + /** + * The list of available access condition + * @type {Array} + */ @Input() availableAccessConditionOptions: any[]; - @Input() availableAccessConditionGroups: Map; - @Input() collectionId; - @Input() collectionPolicyType; - @Input() configMetadataForm: SubmissionFormsModel; - @Input() fileData: WorkspaceitemSectionUploadFileObject; - @Input() fileId; - @Input() fileIndex; - @Input() formId; - @Input() sectionId; - @Input() submissionId; + /** + * The list of available groups for an access condition + * @type {Array} + */ + @Input() availableAccessConditionGroups: Map; + + /** + * The submission id + * @type {string} + */ + @Input() collectionId: string; + + /** + * Define if collection access conditions policy type : + * POLICY_DEFAULT_NO_LIST : is not possible to define additional access group/s for the single file + * POLICY_DEFAULT_WITH_LIST : is possible to define additional access group/s for the single file + * @type {number} + */ + @Input() collectionPolicyType: number; + + /** + * The configuration for the bitstream's metadata form + * @type {SubmissionFormsModel} + */ + @Input() configMetadataForm: SubmissionFormsModel; + + /** + * The bitstream's metadata data + * @type {WorkspaceitemSectionUploadFileObject} + */ + @Input() fileData: WorkspaceitemSectionUploadFileObject; + + /** + * The bitstream id + * @type {string} + */ + @Input() fileId: string; + + /** + * The bitstream array key + * @type {string} + */ + @Input() fileIndex: string; + + /** + * The form id + * @type {string} + */ + @Input() formId: string; + + /** + * The section id + * @type {string} + */ + @Input() sectionId: string; + + /** + * The submission id + * @type {string} + */ + @Input() submissionId: string; + + /** + * The form model + * @type {DynamicFormControlModel[]} + */ public formModel: DynamicFormControlModel[]; + /** + * The FormComponent reference + */ @ViewChild('formRef') public formRef: FormComponent; + /** + * Initialize instance variables + * + * @param {ChangeDetectorRef} cdr + * @param {FormBuilderService} formBuilderService + * @param {FormService} formService + * @param {SubmissionService} submissionService + */ constructor(private cdr: ChangeDetectorRef, private formBuilderService: FormBuilderService, private formService: FormService, private submissionService: SubmissionService) { } + /** + * Dispatch form model init + */ ngOnChanges() { if (this.fileData && this.formId) { this.formModel = this.buildFileEditForm(); @@ -73,8 +151,10 @@ export class UploadSectionFileEditComponent implements OnChanges { } } + /** + * Initialize form model + */ protected buildFileEditForm() { - // TODO check in the rest server configuration whether dc.description may be repeatable const configDescr: FormFieldModel = Object.assign({}, this.configMetadataForm.rows[0].fields[0]); configDescr.repeatable = false; const configForm = Object.assign({}, this.configMetadataForm, { @@ -107,7 +187,7 @@ export class UploadSectionFileEditComponent implements OnChanges { } accessConditionTypeModelConfig.options = accessConditionTypeOptions; - // Dynamic assign of relation in config. For startdate, endDate, groups. + // Dynamically assign of relation in config. For startdate, endDate, groups. const hasStart = []; const hasEnd = []; const hasGroups = []; @@ -153,6 +233,12 @@ export class UploadSectionFileEditComponent implements OnChanges { return formModel; } + /** + * Initialize form model values + * + * @param formModel + * The form model + */ public initModelData(formModel: DynamicFormControlModel[]) { this.fileData.accessConditions.forEach((accessCondition, index) => { Array.of('name', 'groupUUID', 'startDate', 'endDate') @@ -183,22 +269,36 @@ export class UploadSectionFileEditComponent implements OnChanges { }); } + /** + * Dispatch form model update when changing an access condition + * + * @param formModel + * The form model + */ public onChange(event: DynamicFormControlEvent) { if (event.model.id === 'name') { this.setOptions(event.model, event.control); } } - public setOptions(model, control) { + /** + * Update `startDate`, 'groupUUID' and 'endDate' model + * + * @param model + * The [[DynamicFormControlModel]] object + * @param control + * The [[FormControl]] object + */ + public setOptions(model: DynamicFormControlModel, control: FormControl) { let accessCondition: AccessConditionOption = null; this.availableAccessConditionOptions.filter((element) => element.name === control.value) .forEach((element) => accessCondition = element); if (isNotEmpty(accessCondition)) { const showGroups: boolean = accessCondition.hasStartDate === true || accessCondition.hasEndDate === true; - const groupControl: FormControl = control.parent.get('groupUUID'); - const startDateControl: FormControl = control.parent.get('startDate'); - const endDateControl: FormControl = control.parent.get('endDate'); + const groupControl: FormControl = control.parent.get('groupUUID') as FormControl; + const startDateControl: FormControl = control.parent.get('startDate') as FormControl; + const endDateControl: FormControl = control.parent.get('endDate') as FormControl; // Clear previous state groupControl.markAsUntouched(); @@ -236,8 +336,8 @@ export class UploadSectionFileEditComponent implements OnChanges { const confGroup = { relation: groupModel.relation }; const groupsConfig = Object.assign({}, BITSTREAM_FORM_ACCESS_CONDITION_GROUPS_CONFIG, confGroup); groupsConfig.options = groupOptions; - model.parent.group.pop(); - model.parent.group.push(new DynamicSelectModel(groupsConfig, BITSTREAM_FORM_ACCESS_CONDITION_GROUPS_LAYOUT)); + (model.parent as DynamicFormGroupModel).group.pop(); + (model.parent as DynamicFormGroupModel).group.push(new DynamicSelectModel(groupsConfig, BITSTREAM_FORM_ACCESS_CONDITION_GROUPS_LAYOUT)); } } diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.ts b/src/app/submission/sections/upload/file/section-upload-file.component.ts index 45d91428fe..4d8f8119d7 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.ts +++ b/src/app/submission/sections/upload/file/section-upload-file.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectorRef, Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, Subscription } from 'rxjs'; import { filter, first, flatMap, take } from 'rxjs/operators'; import { DynamicFormControlModel, } from '@ng-dynamic-forms/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; @@ -23,6 +23,9 @@ import { WorkspaceitemSectionUploadObject } from '../../../../core/submission/mo import { UploadSectionFileEditComponent } from './edit/section-upload-file-edit.component'; import { Group } from '../../../../core/eperson/models/group.model'; +/** + * This component represents a single bitstream contained in the submission + */ @Component({ selector: 'ds-submission-upload-section-file', styleUrls: ['./section-upload-file.component.scss'], @@ -30,28 +33,129 @@ import { Group } from '../../../../core/eperson/models/group.model'; }) export class UploadSectionFileComponent implements OnChanges, OnInit { + /** + * The list of available access condition + * @type {Array} + */ @Input() availableAccessConditionOptions: any[]; - @Input() availableAccessConditionGroups: Map; - @Input() collectionId; - @Input() collectionPolicyType; - @Input() configMetadataForm: SubmissionFormsModel; - @Input() fileId; - @Input() fileIndex; - @Input() fileName; - @Input() sectionId; - @Input() submissionId; + /** + * The list of available groups for an access condition + * @type {Array} + */ + @Input() availableAccessConditionGroups: Map; + + /** + * The submission id + * @type {string} + */ + @Input() collectionId: string; + + /** + * Define if collection access conditions policy type : + * POLICY_DEFAULT_NO_LIST : is not possible to define additional access group/s for the single file + * POLICY_DEFAULT_WITH_LIST : is possible to define additional access group/s for the single file + * @type {number} + */ + @Input() collectionPolicyType: number; + + /** + * The configuration for the bitstream's metadata form + * @type {SubmissionFormsModel} + */ + @Input() configMetadataForm: SubmissionFormsModel; + + /** + * The bitstream id + * @type {string} + */ + @Input() fileId: string; + + /** + * The bitstream array key + * @type {string} + */ + @Input() fileIndex: string; + + /** + * The bitstream id + * @type {string} + */ + @Input() fileName: string; + + /** + * The section id + * @type {string} + */ + @Input() sectionId: string; + + /** + * The submission id + * @type {string} + */ + @Input() submissionId: string; + + /** + * The bitstream's metadata data + * @type {WorkspaceitemSectionUploadFileObject} + */ public fileData: WorkspaceitemSectionUploadFileObject; - public formId; - public readMode; + + /** + * The form id + * @type {string} + */ + public formId: string; + + /** + * A boolean representing if to show bitstream edit form + * @type {boolean} + */ + public readMode: boolean; + + /** + * The form model + * @type {DynamicFormControlModel[]} + */ public formModel: DynamicFormControlModel[]; + + /** + * A boolean representing if a submission delete operation is pending + * @type {BehaviorSubject} + */ public processingDelete$ = new BehaviorSubject(false); + /** + * The [JsonPatchOperationPathCombiner] object + * @type {JsonPatchOperationPathCombiner} + */ protected pathCombiner: JsonPatchOperationPathCombiner; - protected subscriptions = []; + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ + protected subscriptions: Subscription[] = []; + + /** + * The [[UploadSectionFileEditComponent]] reference + * @type {UploadSectionFileEditComponent} + */ @ViewChild(UploadSectionFileEditComponent) fileEditComp: UploadSectionFileEditComponent; + /** + * Initialize instance variables + * + * @param {ChangeDetectorRef} cdr + * @param {FileService} fileService + * @param {FormService} formService + * @param {HALEndpointService} halService + * @param {NgbModal} modalService + * @param {JsonPatchOperationsBuilder} operationsBuilder + * @param {SubmissionJsonPatchOperationsService} operationsService + * @param {SubmissionService} submissionService + * @param {SectionUploadService} uploadService + */ constructor(private cdr: ChangeDetectorRef, private fileService: FileService, private formService: FormService, @@ -64,6 +168,9 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { this.readMode = true; } + /** + * Retrieve bitstream's metadata + */ ngOnChanges() { if (this.availableAccessConditionOptions && this.availableAccessConditionGroups) { // Retrieve file state @@ -79,11 +186,17 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { } } + /** + * Initialize instance variables + */ ngOnInit() { this.formId = this.formService.getUniqueId(this.fileId); this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId, 'files', this.fileIndex); } + /** + * Delete bitstream from submission + */ protected deleteFile() { this.operationsBuilder.remove(this.pathCombiner.getPath()); this.subscriptions.push(this.operationsService.jsonPatchByResourceID( @@ -97,6 +210,9 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { })); } + /** + * Show confirmation dialog for delete + */ public confirmDelete(content) { this.modalService.open(content).result.then( (result) => { @@ -108,6 +224,9 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { ); } + /** + * Perform bitstream download + */ public downloadBitstreamFile() { this.halService.getEndpoint('bitstreams').pipe( first()) @@ -117,9 +236,16 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { }); } + /** + * Save bitstream metadata + * + * @param event + * the click event emitted + */ public saveBitstreamData(event) { event.preventDefault(); + // validate form this.formService.validateAllFormFields(this.fileEditComp.formRef.formGroup); this.subscriptions.push(this.formService.isValid(this.formId).pipe( take(1), @@ -127,6 +253,7 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { flatMap(() => this.formService.getFormData(this.formId)), take(1), flatMap((formData: any) => { + // collect bitstream metadata Object.keys((formData.metadata)) .filter((key) => isNotEmpty(formData.metadata[key])) .forEach((key) => { @@ -174,6 +301,7 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { this.operationsBuilder.add(this.pathCombiner.getPath('accessConditions'), accessConditionsToSave, true); } + // dispatch a PATCH request to save metadata return this.operationsService.jsonPatchByResourceID( this.submissionService.getSubmissionObjectLinkName(), this.submissionId, @@ -194,11 +322,20 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { })); } - private retrieveValueFromField(field) { + /** + * Retrieve field value + * + * @param field + * the specified field object + */ + private retrieveValueFromField(field: any) { const temp = Array.isArray(field) ? field[0] : field; return (temp) ? temp.value : undefined; } + /** + * Switch from edit form to metadata view + */ public switchMode() { this.readMode = !this.readMode; this.cdr.detectChanges(); diff --git a/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts index a61979e74f..cafd3c7947 100644 --- a/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts +++ b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts @@ -5,17 +5,42 @@ import { isNotEmpty } from '../../../../../shared/empty.util'; import { Metadata } from '../../../../../core/shared/metadata.utils'; import { MetadataMap, MetadataValue } from '../../../../../core/shared/metadata.models'; +/** + * This component allow to show bitstream's metadata + */ @Component({ selector: 'ds-submission-upload-section-file-view', templateUrl: './section-upload-file-view.component.html', }) export class UploadSectionFileViewComponent implements OnInit { + + /** + * The bitstream's metadata data + * @type {WorkspaceitemSectionUploadFileObject} + */ @Input() fileData: WorkspaceitemSectionUploadFileObject; + /** + * The [[MetadataMap]] object + * @type {MetadataMap} + */ public metadata: MetadataMap = Object.create({}); + + /** + * The bitstream's title key + * @type {string} + */ public fileTitleKey = 'Title'; + + /** + * The bitstream's description key + * @type {string} + */ public fileDescrKey = 'Description'; + /** + * Initialize instance variables + */ ngOnInit() { if (isNotEmpty(this.fileData.metadata)) { this.metadata[this.fileTitleKey] = Metadata.all(this.fileData.metadata, 'dc.title'); @@ -23,7 +48,15 @@ export class UploadSectionFileViewComponent implements OnInit { } } - getAllMetadataValue(metadataKey): MetadataValue[] { + /** + * Gets all matching metadata in the map(s) + * + * @param metadataKey + * The metadata key(s) in scope + * @returns {MetadataValue[]} + * The matching values + */ + getAllMetadataValue(metadataKey: string): MetadataValue[] { return Metadata.all(this.metadata, metadataKey); } } diff --git a/src/app/submission/sections/upload/section-upload.component.html b/src/app/submission/sections/upload/section-upload.component.html index 080f7a1f04..afe8f3314a 100644 --- a/src/app/submission/sections/upload/section-upload.component.html +++ b/src/app/submission/sections/upload/section-upload.component.html @@ -15,13 +15,10 @@
- - {{ 'submission.sections.upload.header.policy.default.nolist' | translate:{ "collectionName": collectionName } }} - {{ 'submission.sections.upload.header.policy.default.withlist' | translate:{ "collectionName": collectionName } }} diff --git a/src/app/submission/sections/upload/section-upload.component.ts b/src/app/submission/sections/upload/section-upload.component.ts index 0c2eda5d77..3145cb8301 100644 --- a/src/app/submission/sections/upload/section-upload.component.ts +++ b/src/app/submission/sections/upload/section-upload.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectorRef, Component, Inject } from '@angular/core'; -import { combineLatest, Observable } from 'rxjs'; +import { combineLatest, Observable, Subscription } from 'rxjs'; import { distinctUntilChanged, filter, find, flatMap, map, reduce, take, tap } from 'rxjs/operators'; import { SectionModelComponent } from '../models/section.model'; @@ -33,6 +33,9 @@ export interface AccessConditionGroupsMapEntry { groups: Group[] } +/** + * This component represents a section that contains submission's bitstreams + */ @Component({ selector: 'ds-submission-section-upload', styleUrls: ['./section-upload.component.scss'], @@ -41,37 +44,84 @@ export interface AccessConditionGroupsMapEntry { @renderSectionFor(SectionsType.Upload) export class UploadSectionComponent extends SectionModelComponent { + /** + * The AlertType enumeration + * @type {AlertType} + */ public AlertTypeEnum = AlertType; - public fileIndexes = []; - public fileList = []; - public fileNames = []; + /** + * The array containing the keys of file list array + * @type {Array} + */ + public fileIndexes: string[] = []; + + /** + * The file list + * @type {Array} + */ + public fileList: any[] = []; + + /** + * The array containing the name of the files + * @type {Array} + */ + public fileNames: string[] = []; + + /** + * The collection name this submission belonging to + * @type {string} + */ public collectionName: string; - /* + /** * Default access conditions of this collection + * @type {Array} */ public collectionDefaultAccessConditions: any[] = []; - /* - * The collection access conditions policy + /** + * Define if collection access conditions policy type : + * POLICY_DEFAULT_NO_LIST : is not possible to define additional access group/s for the single file + * POLICY_DEFAULT_WITH_LIST : is possible to define additional access group/s for the single file + * @type {number} */ - public collectionPolicyType; + public collectionPolicyType: number; + /** + * The configuration for the bitstream's metadata form + */ public configMetadataForm$: Observable; - /* + /** * List of available access conditions that could be setted to files */ public availableAccessConditionOptions: AccessConditionOption[]; // List of accessConditions that an user can select - /* + /** * List of Groups available for every access condition */ protected availableGroups: Map; // Groups for any policy - protected subs = []; + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ + protected subs: Subscription[] = []; + /** + * Initialize instance variables + * + * @param {SectionUploadService} bitstreamService + * @param {ChangeDetectorRef} changeDetectorRef + * @param {CollectionDataService} collectionDataService + * @param {GroupEpersonService} groupService + * @param {SectionsService} sectionService + * @param {SubmissionService} submissionService + * @param {SubmissionUploadsConfigService} uploadsConfigService + * @param {SectionDataObject} injectedSectionData + * @param {string} injectedSubmissionId + */ constructor(private bitstreamService: SectionUploadService, private changeDetectorRef: ChangeDetectorRef, private collectionDataService: CollectionDataService, @@ -84,10 +134,14 @@ export class UploadSectionComponent extends SectionModelComponent { super(undefined, injectedSectionData, injectedSubmissionId); } + /** + * Initialize all instance variables and retrieve collection default access conditions + */ onSectionInit() { const config$ = this.uploadsConfigService.getConfigByHref(this.sectionData.config).pipe( map((config) => config.payload)); + // retrieve configuration for the bitstream's metadata form this.configMetadataForm$ = config$.pipe( take(1), map((config: SubmissionUploadsModel) => config.metadata)); @@ -124,7 +178,7 @@ export class UploadSectionComponent extends SectionModelComponent { this.availableGroups = new Map(); const mapGroups$: Array> = []; - // Retrieve Groups for accessConditionPolicies + // Retrieve Groups for accessCondition Policies this.availableAccessConditionOptions.forEach((accessCondition: AccessConditionOption) => { if (accessCondition.hasEndDate === true || accessCondition.hasStartDate === true) { if (accessCondition.groupUUID) { @@ -148,7 +202,7 @@ export class UploadSectionComponent extends SectionModelComponent { accessCondition: accessCondition.name, groups: rd.payload.page } as AccessConditionGroupsMapEntry)) - )); + )); } } }); @@ -164,8 +218,9 @@ export class UploadSectionComponent extends SectionModelComponent { this.availableGroups.set(entry.accessCondition, entry.groups); }); this.changeDetectorRef.detectChanges(); - }) - , + }), + + // retrieve submission's bitstreams from state combineLatest(this.configMetadataForm$, this.bitstreamService.getUploadedFileList(this.submissionId, this.sectionData.id)).pipe( filter(([configMetadataForm, fileList]: [SubmissionFormsModel, any[]]) => { @@ -173,24 +228,32 @@ export class UploadSectionComponent extends SectionModelComponent { }), 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.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(); - } - ) + this.changeDetectorRef.detectChanges(); + } + ) ); } + /** + * Return file name from metadata + * + * @param configMetadataForm + * the bitstream's form configuration + * @param fileData + * the file metadata + */ private getFileName(configMetadataForm: SubmissionFormsModel, fileData: any): string { const metadataName: string = configMetadataForm.rows[0].fields[0].selectableMetadata[0].metadata; let title: string; @@ -203,6 +266,12 @@ export class UploadSectionComponent extends SectionModelComponent { return title; } + /** + * Get section status + * + * @return Observable + * the section status + */ protected getSectionStatus(): Observable { return this.bitstreamService.getUploadedFileList(this.submissionId, this.sectionData.id).pipe( map((fileList: any[]) => (isNotUndefined(fileList) && fileList.length > 0))); diff --git a/src/app/submission/sections/upload/section-upload.service.ts b/src/app/submission/sections/upload/section-upload.service.ts index ab18807fa5..a851fa9daf 100644 --- a/src/app/submission/sections/upload/section-upload.service.ts +++ b/src/app/submission/sections/upload/section-upload.service.ts @@ -14,51 +14,127 @@ import { submissionUploadedFileFromUuidSelector, submissionUploadedFilesFromIdSe import { isUndefined } from '../../../shared/empty.util'; import { WorkspaceitemSectionUploadFileObject } from '../../../core/submission/models/workspaceitem-section-upload-file.model'; +/** + * A service that provides methods to handle submission's bitstream state. + */ @Injectable() export class SectionUploadService { + /** + * Initialize service variables + * + * @param {Store} store + */ constructor(private store: Store) {} + /** + * Return submission's bitstream list from state + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @returns {Array} + * Returns submission's bitstream list + */ public getUploadedFileList(submissionId: string, sectionId: string): Observable { return this.store.select(submissionUploadedFilesFromIdSelector(submissionId, sectionId)).pipe( map((state) => state), distinctUntilChanged()); } - public getFileData(submissionId: string, sectionId: string, fileUuid: string): Observable { + /** + * Return bitstream's metadata + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @param fileUUID + * The bitstream UUID + * @returns {Observable} + * Emits bitstream's metadata + */ + public getFileData(submissionId: string, sectionId: string, fileUUID: string): Observable { return this.store.select(submissionUploadedFilesFromIdSelector(submissionId, sectionId)).pipe( filter((state) => !isUndefined(state)), map((state) => { let fileState; Object.keys(state) - .filter((key) => state[key].uuid === fileUuid) + .filter((key) => state[key].uuid === fileUUID) .forEach((key) => fileState = state[key]); return fileState; }), distinctUntilChanged()); } - public getDefaultPolicies(submissionId: string, sectionId: string, fileId: string): Observable { - return this.store.select(submissionUploadedFileFromUuidSelector(submissionId, sectionId, fileId)).pipe( + /** + * Return bitstream's default policies + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @param fileUUID + * The bitstream UUID + * @returns {Observable} + * Emits bitstream's default policies + */ + public getDefaultPolicies(submissionId: string, sectionId: string, fileUUID: string): Observable { + return this.store.select(submissionUploadedFileFromUuidSelector(submissionId, sectionId, fileUUID)).pipe( map((state) => state), distinctUntilChanged()); } - public addUploadedFile(submissionId: string, sectionId: string, fileId: string, data: WorkspaceitemSectionUploadFileObject) { + /** + * Add a new bitstream to the state + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @param fileUUID + * The bitstream UUID + * @param data + * The [[WorkspaceitemSectionUploadFileObject]] object + */ + public addUploadedFile(submissionId: string, sectionId: string, fileUUID: string, data: WorkspaceitemSectionUploadFileObject) { this.store.dispatch( - new NewUploadedFileAction(submissionId, sectionId, fileId, data) + new NewUploadedFileAction(submissionId, sectionId, fileUUID, data) ); } - public updateFileData(submissionId: string, sectionId: string, fileId: string, data: WorkspaceitemSectionUploadFileObject) { + /** + * Update bitstream metadata into the state + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @param fileUUID + * The bitstream UUID + * @param data + * The [[WorkspaceitemSectionUploadFileObject]] object + */ + public updateFileData(submissionId: string, sectionId: string, fileUUID: string, data: WorkspaceitemSectionUploadFileObject) { this.store.dispatch( - new EditFileDataAction(submissionId, sectionId, fileId, data) + new EditFileDataAction(submissionId, sectionId, fileUUID, data) ); } - public removeUploadedFile(submissionId: string, sectionId: string, fileId: string) { + /** + * Remove bitstream from the state + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @param fileUUID + * The bitstream UUID + */ + public removeUploadedFile(submissionId: string, sectionId: string, fileUUID: string) { this.store.dispatch( - new DeleteUploadedFileAction(submissionId, sectionId, fileId) + new DeleteUploadedFileAction(submissionId, sectionId, fileUUID) ); } } diff --git a/src/app/submission/server-submission.service.ts b/src/app/submission/server-submission.service.ts index f9382af8d0..3aa55a9d58 100644 --- a/src/app/submission/server-submission.service.ts +++ b/src/app/submission/server-submission.service.ts @@ -6,21 +6,45 @@ import { SubmissionService } from './submission.service'; import { SubmissionObject } from '../core/submission/models/submission-object.model'; import { RemoteData } from '../core/data/remote-data'; +/** + * Instance of SubmissionService used on SSR. + */ @Injectable() export class ServerSubmissionService extends SubmissionService { + /** + * Override createSubmission parent method to return an empty observable + * + * @return Observable + * observable of SubmissionObject + */ createSubmission(): Observable { return observableOf(null); } + /** + * Override retrieveSubmission parent method to return an empty observable + * + * @return Observable + * observable of SubmissionObject + */ retrieveSubmission(submissionId): Observable> { return observableOf(null); } + /** + * Override startAutoSave parent method and return without doing anything + * + * @param submissionId + * The submission id + */ startAutoSave(submissionId) { return; } + /** + * Override startAutoSave parent method and return without doing anything + */ stopAutoSave() { return; } diff --git a/src/app/submission/submit/submission-submit.component.ts b/src/app/submission/submit/submission-submit.component.ts index 2354c2b8ab..c773093336 100644 --- a/src/app/submission/submit/submission-submit.component.ts +++ b/src/app/submission/submit/submission-submit.component.ts @@ -11,6 +11,9 @@ import { SubmissionService } from '../submission.service'; import { SubmissionObject } from '../../core/submission/models/submission-object.model'; import { Collection } from '../../core/shared/collection.model'; +/** + * This component allows to submit a new workspaceitem. + */ @Component({ selector: 'ds-submit-page', styleUrls: ['./submission-submit.component.scss'], @@ -18,14 +21,46 @@ import { Collection } from '../../core/shared/collection.model'; }) export class SubmissionSubmitComponent implements OnDestroy, OnInit { + /** + * The collection id this submission belonging to + * @type {string} + */ public collectionId: string; - public model: any; + + /** + * The submission self url + * @type {string} + */ public selfUrl: string; + + /** + * The configuration object that define this submission + * @type {SubmissionDefinitionsModel} + */ public submissionDefinition: SubmissionDefinitionsModel; + + /** + * The submission id + * @type {string} + */ public submissionId: string; + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ protected subs: Subscription[] = []; + /** + * Initialize instance variables + * + * @param {ChangeDetectorRef} changeDetectorRef + * @param {NotificationsService} notificationsService + * @param {SubmissionService} submissioService + * @param {Router} router + * @param {TranslateService} translate + * @param {ViewContainerRef} viewContainerRef + */ constructor(private changeDetectorRef: ChangeDetectorRef, private notificationsService: NotificationsService, private router: Router, @@ -34,6 +69,9 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit { private viewContainerRef: ViewContainerRef) { } + /** + * Create workspaceitem on the server and initialize all instance variables + */ ngOnInit() { // NOTE execute the code on the browser side only, otherwise it is executed twice this.subs.push( @@ -56,6 +94,9 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit { ) } + /** + * Unsubscribe from all subscriptions + */ ngOnDestroy() { this.subs .filter((subscription) => hasValue(subscription)) From 1804a467f32b3d60755b0104cacff0cfe6c64770 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 21 Mar 2019 10:00:21 +0100 Subject: [PATCH 13/27] Removed WorkspaceitemSectionDetectDuplicateObject --- ...rkspaceitem-section-deduplication.model.ts | 21 ------------------- .../models/workspaceitem-sections.model.ts | 2 -- 2 files changed, 23 deletions(-) delete mode 100644 src/app/core/submission/models/workspaceitem-section-deduplication.model.ts diff --git a/src/app/core/submission/models/workspaceitem-section-deduplication.model.ts b/src/app/core/submission/models/workspaceitem-section-deduplication.model.ts deleted file mode 100644 index 9233780be8..0000000000 --- a/src/app/core/submission/models/workspaceitem-section-deduplication.model.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Item } from '../../shared/item.model'; - -export interface WorkspaceitemSectionDetectDuplicateObject { - matches: { - [itemId: string]: DetectDuplicateMatch; - }; -} - -export interface DetectDuplicateMatch { - submitterDecision?: string; // [reject|verify] - submitterNote?: string; - submitterTime?: string; // (readonly) - - workflowDecision?: string; // [reject|verify] - workflowNote?: string; - workflowTime?: string; // (readonly) - - adminDecision?: string; - - matchObject?: Item; -} diff --git a/src/app/core/submission/models/workspaceitem-sections.model.ts b/src/app/core/submission/models/workspaceitem-sections.model.ts index e954c880c4..eb0839adf9 100644 --- a/src/app/core/submission/models/workspaceitem-sections.model.ts +++ b/src/app/core/submission/models/workspaceitem-sections.model.ts @@ -2,7 +2,6 @@ import { WorkspaceitemSectionFormObject } from './workspaceitem-section-form.mod import { WorkspaceitemSectionLicenseObject } from './workspaceitem-section-license.model'; import { WorkspaceitemSectionUploadObject } from './workspaceitem-section-upload.model'; import { WorkspaceitemSectionRecycleObject } from './workspaceitem-section-recycle.model'; -import { WorkspaceitemSectionDetectDuplicateObject } from './workspaceitem-section-deduplication.model'; export class WorkspaceitemSectionsObject { [name: string]: WorkspaceitemSectionDataType; @@ -13,5 +12,4 @@ export type WorkspaceitemSectionDataType | WorkspaceitemSectionFormObject | WorkspaceitemSectionLicenseObject | WorkspaceitemSectionRecycleObject - | WorkspaceitemSectionDetectDuplicateObject | string; From 3615075090d365861c1736865f8d2f361d056e6d Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 21 Mar 2019 10:08:57 +0100 Subject: [PATCH 14/27] Removed WorkspaceitemSectionRecycleObject --- .../models/workspaceitem-section-recycle.model.ts | 8 -------- .../submission/models/workspaceitem-sections.model.ts | 2 -- 2 files changed, 10 deletions(-) delete mode 100644 src/app/core/submission/models/workspaceitem-section-recycle.model.ts diff --git a/src/app/core/submission/models/workspaceitem-section-recycle.model.ts b/src/app/core/submission/models/workspaceitem-section-recycle.model.ts deleted file mode 100644 index 760114e73a..0000000000 --- a/src/app/core/submission/models/workspaceitem-section-recycle.model.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { FormFieldMetadataValueObject } from '../../../shared/form/builder/models/form-field-metadata-value.model'; -import { WorkspaceitemSectionUploadFileObject } from './workspaceitem-section-upload-file.model'; - -export interface WorkspaceitemSectionRecycleObject { - unexpected: any; - metadata: FormFieldMetadataValueObject[]; - files: WorkspaceitemSectionUploadFileObject[]; -} diff --git a/src/app/core/submission/models/workspaceitem-sections.model.ts b/src/app/core/submission/models/workspaceitem-sections.model.ts index eb0839adf9..f0d010cd27 100644 --- a/src/app/core/submission/models/workspaceitem-sections.model.ts +++ b/src/app/core/submission/models/workspaceitem-sections.model.ts @@ -1,7 +1,6 @@ import { WorkspaceitemSectionFormObject } from './workspaceitem-section-form.model'; import { WorkspaceitemSectionLicenseObject } from './workspaceitem-section-license.model'; import { WorkspaceitemSectionUploadObject } from './workspaceitem-section-upload.model'; -import { WorkspaceitemSectionRecycleObject } from './workspaceitem-section-recycle.model'; export class WorkspaceitemSectionsObject { [name: string]: WorkspaceitemSectionDataType; @@ -11,5 +10,4 @@ export type WorkspaceitemSectionDataType = WorkspaceitemSectionUploadObject | WorkspaceitemSectionFormObject | WorkspaceitemSectionLicenseObject - | WorkspaceitemSectionRecycleObject | string; From 4539ee704afd372cb5a6e50c1874cc0f5c495b28 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 21 Mar 2019 11:22:17 +0100 Subject: [PATCH 15/27] Added more comments --- src/app/+login-page/login-page.component.ts | 22 ++++++++- src/app/core/data/data.service.ts | 33 +++++++++++++ .../models/normalized-workflowitem.model.ts | 15 ++++++ .../models/normalized-workspaceitem.model.ts | 15 ++++++ .../models/submission-object.model.ts | 2 +- .../workspaceitem-section-form.model.ts | 4 ++ .../workspaceitem-section-license.model.ts | 15 ++++++ ...workspaceitem-section-upload-file.model.ts | 31 ++++++++++++ .../workspaceitem-section-upload.model.ts | 7 +++ .../models/workspaceitem-sections.model.ts | 7 +++ ...ubmission-json-patch-operations.service.ts | 3 ++ .../submission-response-parsing.service.ts | 24 ++++++++++ .../submission/submission-rest.service.ts | 2 +- .../submission/workflowitem-data.service.ts | 3 ++ .../submission/workspaceitem-data.service.ts | 3 ++ src/app/shared/alerts/alerts.component.ts | 34 ++++++++++++- .../authority-confidence-state.directive.ts | 48 +++++++++++++++++++ src/app/shared/utils/object-ngfor.pipe.ts | 8 ++++ src/app/submission/selectors.ts | 7 ++- src/app/submission/submission.reducers.ts | 3 ++ .../submit/submission-submit.component.ts | 2 +- 21 files changed, 282 insertions(+), 6 deletions(-) diff --git a/src/app/+login-page/login-page.component.ts b/src/app/+login-page/login-page.component.ts index 1f1cf7cf04..6a8508eb45 100644 --- a/src/app/+login-page/login-page.component.ts +++ b/src/app/+login-page/login-page.component.ts @@ -1,7 +1,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs'; +import { combineLatest as observableCombineLatest, Subscription } from 'rxjs'; import { filter, take } from 'rxjs/operators'; import { Store } from '@ngrx/store'; @@ -16,17 +16,34 @@ import { hasValue, isNotEmpty } from '../shared/empty.util'; import { AuthTokenInfo } from '../core/auth/models/auth-token-info.model'; import { isAuthenticated } from '../core/auth/selectors'; +/** + * This component represents the login page + */ @Component({ selector: 'ds-login-page', styleUrls: ['./login-page.component.scss'], templateUrl: './login-page.component.html' }) export class LoginPageComponent implements OnDestroy, OnInit { + + /** + * Subscription to unsubscribe onDestroy + * @type {Subscription} + */ sub: Subscription; + /** + * Initialize instance variables + * + * @param {ActivatedRoute} route + * @param {Store} store + */ constructor(private route: ActivatedRoute, private store: Store) {} + /** + * Initialize instance variables + */ ngOnInit() { const queryParamsObs = this.route.queryParams; const authenticated = this.store.select(isAuthenticated); @@ -52,6 +69,9 @@ export class LoginPageComponent implements OnDestroy, OnInit { }) } + /** + * Unsubscribe from subscription + */ ngOnDestroy() { if (hasValue(this.sub)) { this.sub.unsubscribe(); diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 984495078b..4e66c7673e 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -53,6 +53,14 @@ export abstract class DataService { public abstract getBrowseEndpoint(options: FindAllOptions, linkPath?: string): Observable + /** + * Create the HREF with given options object + * + * @param options The [[FindAllOptions]] object + * @param linkPath The link path for the object + * @return {Observable} + * Return an observable that emits created HREF + */ protected getFindAllHref(options: FindAllOptions = {}, linkPath?: string): Observable { let result: Observable; const args = []; @@ -62,6 +70,14 @@ export abstract class DataService { return this.buildHrefFromFindOptions(result, args, options); } + /** + * Create the HREF for a specific object's search method with given options object + * + * @param searchMethod The search method for the object + * @param options The [[FindAllOptions]] object + * @return {Observable} + * Return an observable that emits created HREF + */ protected getSearchByHref(searchMethod: string, options: FindAllOptions = {}): Observable { let result: Observable; const args = []; @@ -77,6 +93,15 @@ export abstract class DataService { return this.buildHrefFromFindOptions(result, args, options); } + /** + * Turn an options object into a query string and combine it with the given HREF + * + * @param href$ The HREF to which the query string should be appended + * @param args Array with additional params to combine with query string + * @param options The [[FindAllOptions]] object + * @return {Observable} + * Return an observable that emits created HREF + */ protected buildHrefFromFindOptions(href$: Observable, args: string[], options: FindAllOptions): Observable { if (hasValue(options.currentPage) && typeof options.currentPage === 'number') { @@ -146,6 +171,14 @@ export abstract class DataService { map((href: string) => `${href}/${searchMethod}`)); } + /** + * Make a new FindAllRequest with given search method + * + * @param searchMethod The search method for the object + * @param options The [[FindAllOptions]] object + * @return {Observable>} + * Return an observable that emits response from the server + */ protected searchBy(searchMethod: string, options: FindAllOptions = {}): Observable>> { const hrefObs = this.getSearchByHref(searchMethod, options); diff --git a/src/app/core/submission/models/normalized-workflowitem.model.ts b/src/app/core/submission/models/normalized-workflowitem.model.ts index 0ea4ff6150..a3fa8992a2 100644 --- a/src/app/core/submission/models/normalized-workflowitem.model.ts +++ b/src/app/core/submission/models/normalized-workflowitem.model.ts @@ -5,22 +5,37 @@ import { Workflowitem } from './workflowitem.model'; import { NormalizedSubmissionObject } from './normalized-submission-object.model'; import { ResourceType } from '../../shared/resource-type'; +/** + * An model class for a NormalizedWorkflowItem. + */ @mapsTo(Workflowitem) @inheritSerialization(NormalizedSubmissionObject) export class NormalizedWorkflowItem extends NormalizedSubmissionObject { + /** + * The collection this workflowitem belonging to + */ @autoserialize @relationship(ResourceType.Collection, false) collection: string; + /** + * The item created with this workflowitem + */ @autoserialize @relationship(ResourceType.Item, false) item: string; + /** + * The configuration object that define this workflowitem + */ @autoserialize @relationship(ResourceType.SubmissionDefinition, false) submissionDefinition: string; + /** + * The EPerson who submit this workflowitem + */ @autoserialize @relationship(ResourceType.EPerson, false) submitter: string; diff --git a/src/app/core/submission/models/normalized-workspaceitem.model.ts b/src/app/core/submission/models/normalized-workspaceitem.model.ts index 7ec40d6524..7c15925c98 100644 --- a/src/app/core/submission/models/normalized-workspaceitem.model.ts +++ b/src/app/core/submission/models/normalized-workspaceitem.model.ts @@ -7,23 +7,38 @@ import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-obj import { ResourceType } from '../../shared/resource-type'; import { Workflowitem } from './workflowitem.model'; +/** + * An model class for a NormalizedWorkspaceItem. + */ @mapsTo(Workspaceitem) @inheritSerialization(NormalizedDSpaceObject) @inheritSerialization(NormalizedSubmissionObject) export class NormalizedWorkspaceItem extends NormalizedSubmissionObject { + /** + * The collection this workspaceitem belonging to + */ @autoserialize @relationship(ResourceType.Collection, false) collection: string; + /** + * The item created with this workspaceitem + */ @autoserialize @relationship(ResourceType.Item, false) item: string; + /** + * The configuration object that define this workspaceitem + */ @autoserialize @relationship(ResourceType.SubmissionDefinition, false) submissionDefinition: string; + /** + * The EPerson who submit this workspaceitem + */ @autoserialize @relationship(ResourceType.EPerson, false) submitter: string; diff --git a/src/app/core/submission/models/submission-object.model.ts b/src/app/core/submission/models/submission-object.model.ts index 7e3b74a6a9..6b2d9a03b9 100644 --- a/src/app/core/submission/models/submission-object.model.ts +++ b/src/app/core/submission/models/submission-object.model.ts @@ -46,7 +46,7 @@ export abstract class SubmissionObject extends DSpaceObject implements Cacheable sections: WorkspaceitemSectionsObject; /** - * The submission config definition + * The configuration object that define this submission */ submissionDefinition: Observable> | SubmissionDefinitionsModel; diff --git a/src/app/core/submission/models/workspaceitem-section-form.model.ts b/src/app/core/submission/models/workspaceitem-section-form.model.ts index cfae3f5b0f..1462a96d81 100644 --- a/src/app/core/submission/models/workspaceitem-section-form.model.ts +++ b/src/app/core/submission/models/workspaceitem-section-form.model.ts @@ -1,6 +1,10 @@ import { FormFieldMetadataValueObject } from '../../../shared/form/builder/models/form-field-metadata-value.model'; import { MetadataMapInterface } from '../../shared/metadata.models'; +/** + * An interface to represent submission's form section data. + * A map of metadata keys to an ordered list of FormFieldMetadataValueObject objects. + */ export interface WorkspaceitemSectionFormObject extends MetadataMapInterface { [metadata: string]: FormFieldMetadataValueObject[]; } diff --git a/src/app/core/submission/models/workspaceitem-section-license.model.ts b/src/app/core/submission/models/workspaceitem-section-license.model.ts index 4a86503a04..26f625871e 100644 --- a/src/app/core/submission/models/workspaceitem-section-license.model.ts +++ b/src/app/core/submission/models/workspaceitem-section-license.model.ts @@ -1,5 +1,20 @@ + +/** + * An interface to represent submission's license section data. + */ export interface WorkspaceitemSectionLicenseObject { + /** + * The license url + */ url: string; + + /** + * The acceptance date of the license + */ acceptanceDate: string; + + /** + * A boolean representing if license has been granted + */ granted: boolean; } diff --git a/src/app/core/submission/models/workspaceitem-section-upload-file.model.ts b/src/app/core/submission/models/workspaceitem-section-upload-file.model.ts index a42a334b86..177473b7d5 100644 --- a/src/app/core/submission/models/workspaceitem-section-upload-file.model.ts +++ b/src/app/core/submission/models/workspaceitem-section-upload-file.model.ts @@ -1,15 +1,46 @@ import { SubmissionUploadFileAccessConditionObject } from './submission-upload-file-access-condition.model'; import { WorkspaceitemSectionFormObject } from './workspaceitem-section-form.model'; +/** + * An interface to represent submission's upload section file entry. + */ export class WorkspaceitemSectionUploadFileObject { + + /** + * The file UUID + */ uuid: string; + + /** + * The file metadata + */ metadata: WorkspaceitemSectionFormObject; + + /** + * The file size + */ sizeBytes: number; + + /** + * The file check sum + */ checkSum: { checkSumAlgorithm: string; value: string; }; + + /** + * The file url + */ url: string; + + /** + * The file thumbnail url + */ thumbnail: string; + + /** + * The list of file access conditions + */ accessConditions: SubmissionUploadFileAccessConditionObject[]; } diff --git a/src/app/core/submission/models/workspaceitem-section-upload.model.ts b/src/app/core/submission/models/workspaceitem-section-upload.model.ts index b936b5d4d8..f98e0584eb 100644 --- a/src/app/core/submission/models/workspaceitem-section-upload.model.ts +++ b/src/app/core/submission/models/workspaceitem-section-upload.model.ts @@ -1,5 +1,12 @@ import { WorkspaceitemSectionUploadFileObject } from './workspaceitem-section-upload-file.model'; +/** + * An interface to represent submission's upload section data. + */ export interface WorkspaceitemSectionUploadObject { + + /** + * A list of [[WorkspaceitemSectionUploadFileObject]] + */ files: WorkspaceitemSectionUploadFileObject[]; } diff --git a/src/app/core/submission/models/workspaceitem-sections.model.ts b/src/app/core/submission/models/workspaceitem-sections.model.ts index f0d010cd27..165e69869c 100644 --- a/src/app/core/submission/models/workspaceitem-sections.model.ts +++ b/src/app/core/submission/models/workspaceitem-sections.model.ts @@ -2,10 +2,17 @@ import { WorkspaceitemSectionFormObject } from './workspaceitem-section-form.mod import { WorkspaceitemSectionLicenseObject } from './workspaceitem-section-license.model'; import { WorkspaceitemSectionUploadObject } from './workspaceitem-section-upload.model'; +/** + * An interface to represent submission's section object. + * A map of section keys to an ordered list of WorkspaceitemSectionDataType objects. + */ export class WorkspaceitemSectionsObject { [name: string]: WorkspaceitemSectionDataType; } +/** + * Export a type alias of all sections + */ export type WorkspaceitemSectionDataType = WorkspaceitemSectionUploadObject | WorkspaceitemSectionFormObject diff --git a/src/app/core/submission/submission-json-patch-operations.service.ts b/src/app/core/submission/submission-json-patch-operations.service.ts index f9371100d6..d469f2098f 100644 --- a/src/app/core/submission/submission-json-patch-operations.service.ts +++ b/src/app/core/submission/submission-json-patch-operations.service.ts @@ -9,6 +9,9 @@ import { SubmitDataResponseDefinitionObject } from '../shared/submit-data-respon import { SubmissionPatchRequest } from '../data/request.models'; import { CoreState } from '../core.reducers'; +/** + * A service that provides methods to make JSON Patch requests. + */ @Injectable() export class SubmissionJsonPatchOperationsService extends JsonPatchOperationsService { protected linkPath = ''; diff --git a/src/app/core/submission/submission-response-parsing.service.ts b/src/app/core/submission/submission-response-parsing.service.ts index abce34808e..d793b90e56 100644 --- a/src/app/core/submission/submission-response-parsing.service.ts +++ b/src/app/core/submission/submission-response-parsing.service.ts @@ -18,6 +18,11 @@ import { FormFieldMetadataValueObject } from '../../shared/form/builder/models/f import { SubmissionObject } from './models/submission-object.model'; import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory'; +/** + * Export a function to check if object has same properties of FormFieldMetadataValueObject + * + * @param obj + */ export function isServerFormValue(obj: any): boolean { return (typeof obj === 'object' && obj.hasOwnProperty('value') @@ -27,6 +32,11 @@ export function isServerFormValue(obj: any): boolean { && obj.hasOwnProperty('place')) } +/** + * Export a function to normalize sections object of the server response + * + * @param obj + */ export function normalizeSectionData(obj: any) { let result: any = obj; if (isNotNull(obj)) { @@ -74,6 +84,13 @@ export class SubmissionResponseParsingService extends BaseResponseParsingService super(); } + /** + * Parses data from the workspaceitems/workflowitems endpoints + * + * @param {RestRequest} request + * @param {DSpaceRESTV2Response} data + * @returns {RestResponse} + */ parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links) @@ -93,6 +110,13 @@ export class SubmissionResponseParsingService extends BaseResponseParsingService } } + /** + * Parses response and normalize it + * + * @param {DSpaceRESTV2Response} data + * @param {string} requestHref + * @returns {any[]} + */ protected processResponse(data: any, requestHref: string): any[] { const dataDefinition = this.process(data, requestHref); const normalizedDefinition = Array.of(); diff --git a/src/app/core/submission/submission-rest.service.ts b/src/app/core/submission/submission-rest.service.ts index a249f10c6f..e2b8bb01c8 100644 --- a/src/app/core/submission/submission-rest.service.ts +++ b/src/app/core/submission/submission-rest.service.ts @@ -22,7 +22,7 @@ import { ErrorResponse, RestResponse, SubmissionSuccessResponse } from '../cache import { getResponseFromEntry } from '../shared/operators'; /** - * The service handling all submission requests + * The service handling all submission REST requests */ @Injectable() export class SubmissionRestService { diff --git a/src/app/core/submission/workflowitem-data.service.ts b/src/app/core/submission/workflowitem-data.service.ts index 266d2b5411..e739a62e81 100644 --- a/src/app/core/submission/workflowitem-data.service.ts +++ b/src/app/core/submission/workflowitem-data.service.ts @@ -14,6 +14,9 @@ import { NotificationsService } from '../../shared/notifications/notifications.s import { ObjectCacheService } from '../cache/object-cache.service'; import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service'; +/** + * A service that provides methods to make REST requests with workflowitems endpoint. + */ @Injectable() export class WorkflowitemDataService extends DataService { protected linkPath = 'workflowitems'; diff --git a/src/app/core/submission/workspaceitem-data.service.ts b/src/app/core/submission/workspaceitem-data.service.ts index 119bfb66cc..3bb3eb1ee8 100644 --- a/src/app/core/submission/workspaceitem-data.service.ts +++ b/src/app/core/submission/workspaceitem-data.service.ts @@ -14,6 +14,9 @@ import { NotificationsService } from '../../shared/notifications/notifications.s import { ObjectCacheService } from '../cache/object-cache.service'; import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service'; +/** + * A service that provides methods to make REST requests with workspaceitems endpoint. + */ @Injectable() export class WorkspaceitemDataService extends DataService { protected linkPath = 'workspaceitems'; diff --git a/src/app/shared/alerts/alerts.component.ts b/src/app/shared/alerts/alerts.component.ts index c9fc0ec9cc..d5fc2b48c7 100644 --- a/src/app/shared/alerts/alerts.component.ts +++ b/src/app/shared/alerts/alerts.component.ts @@ -4,6 +4,9 @@ import { trigger } from '@angular/animations'; import { AlertType } from './aletrs-type'; import { fadeOutLeave, fadeOutState } from '../animations/fade'; +/** + * This component allow to create div that uses the Bootstrap's Alerts component. + */ @Component({ selector: 'ds-alert', encapsulation: ViewEncapsulation.None, @@ -15,20 +18,49 @@ import { fadeOutLeave, fadeOutState } from '../animations/fade'; templateUrl: './alerts.component.html', styleUrls: ['./alerts.component.scss'] }) - export class AlertsComponent { + /** + * The alert content + */ @Input() content: string; + + /** + * A boolean representing if alert is dismissible + */ @Input() dismissible = false; + + /** + * The alert type + */ @Input() type: AlertType; + + /** + * An event fired when alert is dismissed. + */ @Output() close: EventEmitter = new EventEmitter(); + /** + * The initial animation name + */ public animate = 'fadeIn'; + + /** + * A boolean representing if alert is dismissed or not + */ public dismissed = false; + /** + * Initialize instance variables + * + * @param {ChangeDetectorRef} cdr + */ constructor(private cdr: ChangeDetectorRef) { } + /** + * Dismiss div with animation + */ dismiss() { if (this.dismissible) { this.animate = 'fadeOut'; diff --git a/src/app/shared/authority-confidence/authority-confidence-state.directive.ts b/src/app/shared/authority-confidence/authority-confidence-state.directive.ts index da62204d10..6362daf3c7 100644 --- a/src/app/shared/authority-confidence/authority-confidence-state.directive.ts +++ b/src/app/shared/authority-confidence/authority-confidence-state.directive.ts @@ -19,25 +19,55 @@ import { isNotEmpty, isNull } from '../empty.util'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; import { ConfidenceIconConfig } from '../../../config/submission-config.interface'; +/** + * Directive to add to the element a bootstrap utility class based on metadata confidence value + */ @Directive({ selector: '[dsAuthorityConfidenceState]' }) export class AuthorityConfidenceStateDirective implements OnChanges { + /** + * The metadata value + */ @Input() authorityValue: AuthorityValue | FormFieldMetadataValueObject | string; + + /** + * A boolean representing if to show html icon if authority value is empty + */ @Input() visibleWhenAuthorityEmpty = true; + /** + * The css class applied before directive changes + */ private previousClass: string = null; + + /** + * The css class applied after directive changes + */ private newClass: string; + /** + * An event fired when click on element that has a confidence value empty or different from CF_ACCEPTED + */ @Output() whenClickOnConfidenceNotAccepted: EventEmitter = new EventEmitter(); + /** + * Listener to click event + */ @HostListener('click') onClick() { if (isNotEmpty(this.authorityValue) && this.getConfidenceByValue(this.authorityValue) !== ConfidenceType.CF_ACCEPTED) { this.whenClickOnConfidenceNotAccepted.emit(this.getConfidenceByValue(this.authorityValue)); } } + /** + * Initialize instance variables + * + * @param {GlobalConfig} EnvConfig + * @param {ElementRef} elem + * @param {Renderer2} renderer + */ constructor( @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, private elem: ElementRef, @@ -45,6 +75,11 @@ export class AuthorityConfidenceStateDirective implements OnChanges { ) { } + /** + * Apply css class to element whenever authority value change + * + * @param {SimpleChanges} changes + */ ngOnChanges(changes: SimpleChanges): void { if (!changes.authorityValue.firstChange) { this.previousClass = this.getClassByConfidence(this.getConfidenceByValue(changes.authorityValue.previousValue)) @@ -59,6 +94,9 @@ export class AuthorityConfidenceStateDirective implements OnChanges { } } + /** + * Apply css class to element after view init + */ ngAfterViewInit() { if (isNull(this.previousClass)) { this.renderer.addClass(this.elem.nativeElement, this.newClass); @@ -68,6 +106,11 @@ export class AuthorityConfidenceStateDirective implements OnChanges { } } + /** + * Return confidence value as ConfidenceType + * + * @param value + */ private getConfidenceByValue(value: any): ConfidenceType { let confidence: ConfidenceType = ConfidenceType.CF_UNSET; @@ -82,6 +125,11 @@ export class AuthorityConfidenceStateDirective implements OnChanges { return confidence; } + /** + * Return the properly css class based on confidence value + * + * @param confidence + */ private getClassByConfidence(confidence: any): string { if (!this.visibleWhenAuthorityEmpty && confidence === ConfidenceType.CF_UNSET) { return 'd-none'; diff --git a/src/app/shared/utils/object-ngfor.pipe.ts b/src/app/shared/utils/object-ngfor.pipe.ts index 4715d5c151..982e3342e0 100644 --- a/src/app/shared/utils/object-ngfor.pipe.ts +++ b/src/app/shared/utils/object-ngfor.pipe.ts @@ -1,5 +1,13 @@ import { Pipe, PipeTransform } from '@angular/core'; +/** + * Pipe that allows to iterate over an object and to access to entry key and value : + * + *
+ * {{obj.key}} - {{obj.value}} + *
+ * + */ @Pipe({ name: 'dsObjNgFor' }) diff --git a/src/app/submission/selectors.ts b/src/app/submission/selectors.ts index b52c44b7b1..51c960b537 100644 --- a/src/app/submission/selectors.ts +++ b/src/app/submission/selectors.ts @@ -4,7 +4,9 @@ import { hasValue } from '../shared/empty.util'; import { submissionSelector, SubmissionState } from './submission.reducers'; import { SubmissionObjectEntry, SubmissionSectionObject } from './objects/submission-objects.reducer'; -// @TODO: Merge with keySelector function present in 'src/app/core/shared/selectors.ts' +/** + * Export a function to return a subset of the state by key + */ export function keySelector(parentSelector: Selector, subState: string, key: string): MemoizedSelector { return createSelector(parentSelector, (state: T) => { if (hasValue(state) && hasValue(state[subState])) { @@ -15,6 +17,9 @@ export function keySelector(parentSelector: Selector, subState: }); } +/** + * Export a function to return a subset of the state + */ export function subStateSelector(parentSelector: Selector, subState: string): MemoizedSelector { return createSelector(parentSelector, (state: T) => { if (hasValue(state) && hasValue(state[subState])) { diff --git a/src/app/submission/submission.reducers.ts b/src/app/submission/submission.reducers.ts index 39069b2917..939c4654ad 100644 --- a/src/app/submission/submission.reducers.ts +++ b/src/app/submission/submission.reducers.ts @@ -5,6 +5,9 @@ import { SubmissionObjectState } from './objects/submission-objects.reducer'; +/** + * The Submission State + */ export interface SubmissionState { 'objects': SubmissionObjectState } diff --git a/src/app/submission/submit/submission-submit.component.ts b/src/app/submission/submit/submission-submit.component.ts index c773093336..dbfd2f5a40 100644 --- a/src/app/submission/submit/submission-submit.component.ts +++ b/src/app/submission/submit/submission-submit.component.ts @@ -15,7 +15,7 @@ import { Collection } from '../../core/shared/collection.model'; * This component allows to submit a new workspaceitem. */ @Component({ - selector: 'ds-submit-page', + selector: 'ds-submission-submit', styleUrls: ['./submission-submit.component.scss'], templateUrl: './submission-submit.component.html' }) From 7ac39f05b993ff04ff05e9c357a79507792ed959 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 21 Mar 2019 11:41:10 +0100 Subject: [PATCH 16/27] Removed edit item from submission --- src/app/core/submission/models/edititem.model.ts | 4 ---- .../submission/models/normalized-edititem.model.ts | 10 ---------- src/app/core/submission/submission-resource-type.ts | 1 - .../submission-response-parsing.service.ts | 4 +--- src/app/core/submission/submission-scope-type.ts | 3 +-- src/app/submission/submission.service.spec.ts | 3 --- src/app/submission/submission.service.ts | 13 +++++++------ 7 files changed, 9 insertions(+), 29 deletions(-) delete mode 100644 src/app/core/submission/models/edititem.model.ts delete mode 100644 src/app/core/submission/models/normalized-edititem.model.ts diff --git a/src/app/core/submission/models/edititem.model.ts b/src/app/core/submission/models/edititem.model.ts deleted file mode 100644 index 9c8da2ab5a..0000000000 --- a/src/app/core/submission/models/edititem.model.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Workspaceitem } from './workspaceitem.model'; - -export class EditItem extends Workspaceitem { -} diff --git a/src/app/core/submission/models/normalized-edititem.model.ts b/src/app/core/submission/models/normalized-edititem.model.ts deleted file mode 100644 index 5615512399..0000000000 --- a/src/app/core/submission/models/normalized-edititem.model.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { inheritSerialization } from 'cerialize'; -import { mapsTo } from '../../cache/builders/build-decorators'; -import { NormalizedSubmissionObject } from './normalized-submission-object.model'; -import { EditItem } from './edititem.model'; - -@mapsTo(EditItem) -@inheritSerialization(NormalizedSubmissionObject) -export class NormalizedEditItem extends NormalizedSubmissionObject { - -} diff --git a/src/app/core/submission/submission-resource-type.ts b/src/app/core/submission/submission-resource-type.ts index 8718fc55d3..f5b8e2c423 100644 --- a/src/app/core/submission/submission-resource-type.ts +++ b/src/app/core/submission/submission-resource-type.ts @@ -11,7 +11,6 @@ export enum SubmissionResourceType { Group = 'group', WorkspaceItem = 'workspaceitem', WorkflowItem = 'workflowitem', - EditItem = 'edititem', SubmissionDefinitions = 'submissiondefinitions', SubmissionDefinition = 'submissiondefinition', SubmissionForm = 'submissionform', diff --git a/src/app/core/submission/submission-response-parsing.service.ts b/src/app/core/submission/submission-response-parsing.service.ts index d793b90e56..20dfb43cbd 100644 --- a/src/app/core/submission/submission-response-parsing.service.ts +++ b/src/app/core/submission/submission-response-parsing.service.ts @@ -13,7 +13,6 @@ import { ObjectCacheService } from '../cache/object-cache.service'; import { SubmissionResourceType } from './submission-resource-type'; import { NormalizedWorkspaceItem } from './models/normalized-workspaceitem.model'; import { NormalizedWorkflowItem } from './models/normalized-workflowitem.model'; -import { NormalizedEditItem } from './models/normalized-edititem.model'; import { FormFieldMetadataValueObject } from '../../shared/form/builder/models/form-field-metadata-value.model'; import { SubmissionObject } from './models/submission-object.model'; import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory'; @@ -127,8 +126,7 @@ export class SubmissionResponseParsingService extends BaseResponseParsingService let normalizedItem = Object.assign({}, item); // In case data is an Instance of NormalizedWorkspaceItem normalize field value of all the section of type form if (item instanceof NormalizedWorkspaceItem - || item instanceof NormalizedWorkflowItem - || item instanceof NormalizedEditItem) { + || item instanceof NormalizedWorkflowItem) { if (item.sections) { const precessedSection = Object.create({}); // Iterate over all workspaceitem's sections diff --git a/src/app/core/submission/submission-scope-type.ts b/src/app/core/submission/submission-scope-type.ts index 80d57c853f..6ed32d3b4e 100644 --- a/src/app/core/submission/submission-scope-type.ts +++ b/src/app/core/submission/submission-scope-type.ts @@ -1,5 +1,4 @@ export enum SubmissionScopeType { WorkspaceItem = 'WORKSPACE', - WorkflowItem = 'WORKFLOW', - EditItem = 'ITEM', + WorkflowItem = 'WORKFLOW' } diff --git a/src/app/submission/submission.service.spec.ts b/src/app/submission/submission.service.spec.ts index a35cf8329c..d764f09538 100644 --- a/src/app/submission/submission.service.spec.ts +++ b/src/app/submission/submission.service.spec.ts @@ -660,9 +660,6 @@ describe('SubmissionService test suite', () => { router.setRoute('/workflowitems/826/edit'); expect(service.getSubmissionScope()).toBe(expected); - expected = SubmissionScopeType.EditItem; - router.setRoute('/items/9e79b1f2-ae0f-4737-9a4b-990952a8857c/edit'); - expect(service.getSubmissionScope()).toBe(expected); }); }); diff --git a/src/app/submission/submission.service.ts b/src/app/submission/submission.service.ts index 11b26e2b5e..82185a8eae 100644 --- a/src/app/submission/submission.service.ts +++ b/src/app/submission/submission.service.ts @@ -65,7 +65,7 @@ export class SubmissionService { * @param {GlobalConfig} EnvConfig * @param {NotificationsService} notificationsService * @param {SubmissionRestService} restService - * @param {Router} restSerroutervice + * @param {Router} router * @param {RouteService} routeService * @param {Store} store * @param {TranslateService} translate @@ -202,6 +202,8 @@ export class SubmissionService { * * @param submissionId * The submission id + * @param sectionId + * The section id */ dispatchSaveSection(submissionId, sectionId) { this.store.dispatch(new SaveSubmissionSectionFormAction(submissionId, sectionId)); @@ -327,9 +329,6 @@ export class SubmissionService { case 'workflowitems': scope = SubmissionScopeType.WorkflowItem; break; - case 'edititems': - scope = SubmissionScopeType.EditItem; - break; } return scope; } @@ -400,8 +399,8 @@ export class SubmissionService { /** * Return the visibility status of the specified section * - * @param submissionId - * The submission id + * @param sectionData + * The section data * @return boolean * true if section is hidden, false otherwise */ @@ -432,6 +431,8 @@ export class SubmissionService { * The submission id * @param sectionId * The section id + * @param sectionType + * The section type */ notifyNewSection(submissionId: string, sectionId: string, sectionType?: SectionsType) { const m = this.translate.instant('submission.sections.general.metadata-extracted-new-section', { sectionId }); From e3e9be03b2e44fa69fd33731234cea4c1d40e99d Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 21 Mar 2019 11:59:22 +0100 Subject: [PATCH 17/27] Renamed properly submission's section components --- .../form/section-form.component.spec.ts | 16 ++++----- .../sections/form/section-form.component.ts | 2 +- .../license/section-license.component.spec.ts | 16 ++++----- .../license/section-license.component.ts | 4 +-- ...n-upload-access-conditions.component.html} | 0 ...ion-upload-access-conditions.component.ts} | 6 ++-- ...section-upload-file-edit.component.spec.ts | 20 +++++------ .../section-upload-file-edit.component.ts | 8 ++--- .../file/section-upload-file.component.html | 8 ++--- .../section-upload-file.component.spec.ts | 24 ++++++------- .../file/section-upload-file.component.ts | 10 +++--- .../section-upload-file-view.component.html | 2 +- ...section-upload-file-view.component.spec.ts | 18 +++++----- .../section-upload-file-view.component.ts | 4 +-- .../upload/section-upload.component.html | 2 +- .../upload/section-upload.component.spec.ts | 16 ++++----- .../upload/section-upload.component.ts | 2 +- src/app/submission/submission.module.ts | 34 +++++++++---------- 18 files changed, 96 insertions(+), 96 deletions(-) rename src/app/submission/sections/upload/accessConditions/{section-upload-access-conditions.component.html => submission-section-upload-access-conditions.component.html} (100%) rename src/app/submission/sections/upload/accessConditions/{section-upload-access-conditions.component.ts => submission-section-upload-access-conditions.component.ts} (88%) diff --git a/src/app/submission/sections/form/section-form.component.spec.ts b/src/app/submission/sections/form/section-form.component.spec.ts index cf16cc9317..477d42a0a1 100644 --- a/src/app/submission/sections/form/section-form.component.spec.ts +++ b/src/app/submission/sections/form/section-form.component.spec.ts @@ -12,7 +12,7 @@ import { SubmissionServiceStub } from '../../../shared/testing/submission-servic import { getMockTranslateService } from '../../../shared/mocks/mock-translate.service'; import { SectionsService } from '../sections.service'; import { SectionsServiceStub } from '../../../shared/testing/sections-service-stub'; -import { FormSectionComponent } from './section-form.component'; +import { SubmissionSectionformComponent } from './section-form.component'; import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; import { getMockFormBuilderService } from '../../../shared/mocks/mock-form-builder-service'; import { getMockFormOperationsService } from '../../../shared/mocks/mock-form-operations-service'; @@ -132,11 +132,11 @@ const dynamicFormControlEvent: DynamicFormControlEvent = { type: DynamicFormControlEventType.Change }; -describe('FormSectionComponent test suite', () => { +describe('SubmissionSectionformComponent test suite', () => { - let comp: FormSectionComponent; + let comp: SubmissionSectionformComponent; let compAsAny: any; - let fixture: ComponentFixture; + let fixture: ComponentFixture; let submissionServiceStub: SubmissionServiceStub; let sectionsServiceStub: SectionsServiceStub; let notificationsServiceStub: NotificationsServiceStub; @@ -163,7 +163,7 @@ describe('FormSectionComponent test suite', () => { ], declarations: [ FormComponent, - FormSectionComponent, + SubmissionSectionformComponent, TestComponent ], providers: [ @@ -180,7 +180,7 @@ describe('FormSectionComponent test suite', () => { { provide: 'sectionDataProvider', useValue: sectionObject }, { provide: 'submissionIdProvider', useValue: submissionId }, ChangeDetectorRef, - FormSectionComponent + SubmissionSectionformComponent ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents().then(); @@ -203,7 +203,7 @@ describe('FormSectionComponent test suite', () => { testFixture.destroy(); }); - it('should create FormSectionComponent', inject([FormSectionComponent], (app: FormSectionComponent) => { + it('should create SubmissionSectionformComponent', inject([SubmissionSectionformComponent], (app: SubmissionSectionformComponent) => { expect(app).toBeDefined(); @@ -212,7 +212,7 @@ describe('FormSectionComponent test suite', () => { describe('', () => { beforeEach(() => { - fixture = TestBed.createComponent(FormSectionComponent); + fixture = TestBed.createComponent(SubmissionSectionformComponent); comp = fixture.componentInstance; compAsAny = comp; submissionServiceStub = TestBed.get(SubmissionService); diff --git a/src/app/submission/sections/form/section-form.component.ts b/src/app/submission/sections/form/section-form.component.ts index 048f109f54..ef817a7568 100644 --- a/src/app/submission/sections/form/section-form.component.ts +++ b/src/app/submission/sections/form/section-form.component.ts @@ -38,7 +38,7 @@ import { WorkspaceitemSectionFormObject } from '../../../core/submission/models/ templateUrl: './section-form.component.html', }) @renderSectionFor(SectionsType.SubmissionForm) -export class FormSectionComponent extends SectionModelComponent { +export class SubmissionSectionformComponent extends SectionModelComponent { /** * The form id diff --git a/src/app/submission/sections/license/section-license.component.spec.ts b/src/app/submission/sections/license/section-license.component.spec.ts index a84e8a891d..6d8f82c0f8 100644 --- a/src/app/submission/sections/license/section-license.component.spec.ts +++ b/src/app/submission/sections/license/section-license.component.spec.ts @@ -29,7 +29,7 @@ import { } from '../../../shared/mocks/mock-submission'; import { FormComponent } from '../../../shared/form/form.component'; import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner'; -import { LicenseSectionComponent } from './section-license.component'; +import { SubmissionSectionLicenseComponent } from './section-license.component'; import { CollectionDataService } from '../../../core/data/collection-data.service'; import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder'; import { SectionFormOperationsService } from '../form/section-form-operations.service'; @@ -78,11 +78,11 @@ const dynamicFormControlEvent: DynamicFormControlEvent = { type: DynamicFormControlEventType.Change }; -describe('LicenseSectionComponent test suite', () => { +describe('SubmissionSectionLicenseComponent test suite', () => { - let comp: LicenseSectionComponent; + let comp: SubmissionSectionLicenseComponent; let compAsAny: any; - let fixture: ComponentFixture; + let fixture: ComponentFixture; let submissionServiceStub: SubmissionServiceStub; let sectionsServiceStub: SectionsServiceStub; let formService: any; @@ -124,7 +124,7 @@ describe('LicenseSectionComponent test suite', () => { ], declarations: [ FormComponent, - LicenseSectionComponent, + SubmissionSectionLicenseComponent, TestComponent ], providers: [ @@ -141,7 +141,7 @@ describe('LicenseSectionComponent test suite', () => { { provide: 'submissionIdProvider', useValue: submissionId }, ChangeDetectorRef, FormBuilderService, - LicenseSectionComponent + SubmissionSectionLicenseComponent ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents().then(); @@ -164,7 +164,7 @@ describe('LicenseSectionComponent test suite', () => { testFixture.destroy(); }); - it('should create LicenseSectionComponent', inject([LicenseSectionComponent], (app: LicenseSectionComponent) => { + it('should create SubmissionSectionLicenseComponent', inject([SubmissionSectionLicenseComponent], (app: SubmissionSectionLicenseComponent) => { expect(app).toBeDefined(); @@ -173,7 +173,7 @@ describe('LicenseSectionComponent test suite', () => { describe('', () => { beforeEach(() => { - fixture = TestBed.createComponent(LicenseSectionComponent); + fixture = TestBed.createComponent(SubmissionSectionLicenseComponent); comp = fixture.componentInstance; compAsAny = comp; submissionServiceStub = TestBed.get(SubmissionService); diff --git a/src/app/submission/sections/license/section-license.component.ts b/src/app/submission/sections/license/section-license.component.ts index f4dae0565b..940460c83d 100644 --- a/src/app/submission/sections/license/section-license.component.ts +++ b/src/app/submission/sections/license/section-license.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectorRef, Component, Inject, ViewChild } from '@angular/core'; import { Observable, Subscription } from 'rxjs'; -import { distinctUntilChanged, filter, find, flatMap, map, startWith, take, tap } from 'rxjs/operators'; +import { distinctUntilChanged, filter, find, flatMap, map, startWith, take } from 'rxjs/operators'; import { DynamicCheckboxModel, DynamicFormControlEvent, @@ -38,7 +38,7 @@ import { FormComponent } from '../../../shared/form/form.component'; templateUrl: './section-license.component.html', }) @renderSectionFor(SectionsType.License) -export class LicenseSectionComponent extends SectionModelComponent { +export class SubmissionSectionLicenseComponent extends SectionModelComponent { /** * The form id diff --git a/src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.html b/src/app/submission/sections/upload/accessConditions/submission-section-upload-access-conditions.component.html similarity index 100% rename from src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.html rename to src/app/submission/sections/upload/accessConditions/submission-section-upload-access-conditions.component.html diff --git a/src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.ts b/src/app/submission/sections/upload/accessConditions/submission-section-upload-access-conditions.component.ts similarity index 88% rename from src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.ts rename to src/app/submission/sections/upload/accessConditions/submission-section-upload-access-conditions.component.ts index f13f730452..43b0a7da3f 100644 --- a/src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.ts +++ b/src/app/submission/sections/upload/accessConditions/submission-section-upload-access-conditions.component.ts @@ -12,10 +12,10 @@ import { RemoteData } from '../../../../core/data/remote-data'; * This component represents a badge that describe an access condition */ @Component({ - selector: 'ds-section-upload-access-conditions', - templateUrl: './section-upload-access-conditions.component.html', + selector: 'ds-submission-section-upload-access-conditions', + templateUrl: './submission-section-upload-access-conditions.component.html', }) -export class SectionUploadAccessConditionsComponent implements OnInit { +export class SubmissionSectionUploadAccessConditionsComponent implements OnInit { /** * The list of resource policy diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts index acc4e5edcd..4774b22924 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts @@ -15,7 +15,7 @@ import { createTestComponent } from '../../../../../shared/testing/utils'; import { FormBuilderService } from '../../../../../shared/form/builder/form-builder.service'; import { SubmissionServiceStub } from '../../../../../shared/testing/submission-service-stub'; import { SubmissionService } from '../../../../submission.service'; -import { UploadSectionFileEditComponent } from './section-upload-file-edit.component'; +import { SubmissionSectionUploadFileEditComponent } from './section-upload-file-edit.component'; import { POLICY_DEFAULT_WITH_LIST } from '../../section-upload.component'; import { mockGroup, @@ -32,11 +32,11 @@ import { MOCK_SUBMISSION_CONFIG } from '../../../../../shared/testing/mock-submi import { getMockFormService } from '../../../../../shared/mocks/mock-form-service'; import { Group } from '../../../../../core/eperson/models/group.model'; -describe('UploadSectionFileEditComponent test suite', () => { +describe('SubmissionSectionUploadFileEditComponent test suite', () => { - let comp: UploadSectionFileEditComponent; + let comp: SubmissionSectionUploadFileEditComponent; let compAsAny: any; - let fixture: ComponentFixture; + let fixture: ComponentFixture; let submissionServiceStub: SubmissionServiceStub; let formbuilderService: any; @@ -66,7 +66,7 @@ describe('UploadSectionFileEditComponent test suite', () => { ], declarations: [ FormComponent, - UploadSectionFileEditComponent, + SubmissionSectionUploadFileEditComponent, TestComponent ], providers: [ @@ -75,7 +75,7 @@ describe('UploadSectionFileEditComponent test suite', () => { { provide: SubmissionService, useClass: SubmissionServiceStub }, FormBuilderService, ChangeDetectorRef, - UploadSectionFileEditComponent + SubmissionSectionUploadFileEditComponent ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents().then(); @@ -88,7 +88,7 @@ describe('UploadSectionFileEditComponent test suite', () => { // synchronous beforeEach beforeEach(() => { const html = ` - { [fileId]="fileId" [fileIndex]="fileIndex" [formId]="formId" - [sectionId]="sectionId">`; + [sectionId]="sectionId">`; testFixture = createTestComponent(html, TestComponent) as ComponentFixture; testComp = testFixture.componentInstance; @@ -107,7 +107,7 @@ describe('UploadSectionFileEditComponent test suite', () => { testFixture.destroy(); }); - it('should create UploadSectionFileEditComponent', inject([UploadSectionFileEditComponent], (app: UploadSectionFileEditComponent) => { + it('should create SubmissionSectionUploadFileEditComponent', inject([SubmissionSectionUploadFileEditComponent], (app: SubmissionSectionUploadFileEditComponent) => { expect(app).toBeDefined(); @@ -116,7 +116,7 @@ describe('UploadSectionFileEditComponent test suite', () => { describe('', () => { beforeEach(() => { - fixture = TestBed.createComponent(UploadSectionFileEditComponent); + fixture = TestBed.createComponent(SubmissionSectionUploadFileEditComponent); comp = fixture.componentInstance; compAsAny = comp; submissionServiceStub = TestBed.get(SubmissionService); diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts index 9f1b49c221..b2575d1d58 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts @@ -43,10 +43,10 @@ import { Group } from '../../../../../core/eperson/models/group.model'; * This component represents the edit form for bitstream */ @Component({ - selector: 'ds-submission-upload-section-file-edit', + selector: 'ds-submission-section-upload-file-edit', templateUrl: './section-upload-file-edit.component.html', }) -export class UploadSectionFileEditComponent implements OnChanges { +export class SubmissionSectionUploadFileEditComponent implements OnChanges { /** * The list of available access condition @@ -272,8 +272,8 @@ export class UploadSectionFileEditComponent implements OnChanges { /** * Dispatch form model update when changing an access condition * - * @param formModel - * The form model + * @param event + * The event emitted */ public onChange(event: DynamicFormControlEvent) { if (event.model.id === 'name') { diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.html b/src/app/submission/sections/upload/file/section-upload-file.component.html index 2e12cc4f5e..ea23340e9a 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.html +++ b/src/app/submission/sections/upload/file/section-upload-file.component.html @@ -34,9 +34,9 @@
- - + + [sectionId]="sectionId">
diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts index ff2b3071f3..f87aa7d703 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts +++ b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts @@ -15,7 +15,7 @@ import { NgbModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { JsonPatchOperationsBuilder } from '../../../../core/json-patch/builder/json-patch-operations-builder'; import { SubmissionJsonPatchOperationsServiceStub } from '../../../../shared/testing/submission-json-patch-operations-service-stub'; import { SubmissionJsonPatchOperationsService } from '../../../../core/submission/submission-json-patch-operations.service'; -import { UploadSectionFileComponent } from './section-upload-file.component'; +import { SubmissionSectionUploadFileComponent } from './section-upload-file.component'; import { SubmissionServiceStub } from '../../../../shared/testing/submission-service-stub'; import { mockFileFormData, @@ -36,7 +36,7 @@ import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/buil import { getMockSectionUploadService } from '../../../../shared/mocks/mock-section-upload.service'; import { FormFieldMetadataValueObject } from '../../../../shared/form/builder/models/form-field-metadata-value.model'; import { Group } from '../../../../core/eperson/models/group.model'; -import { UploadSectionFileEditComponent } from './edit/section-upload-file-edit.component'; +import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component'; function getMockFileService(): FileService { return jasmine.createSpyObj('FileService', { @@ -45,11 +45,11 @@ function getMockFileService(): FileService { }); } -describe('UploadSectionFileComponent test suite', () => { +describe('SubmissionSectionUploadFileComponent test suite', () => { - let comp: UploadSectionFileComponent; + let comp: SubmissionSectionUploadFileComponent; let compAsAny: any; - let fixture: ComponentFixture; + let fixture: ComponentFixture; let submissionServiceStub: SubmissionServiceStub; let uploadService: any; let fileService: any; @@ -90,7 +90,7 @@ describe('UploadSectionFileComponent test suite', () => { ], declarations: [ FileSizePipe, - UploadSectionFileComponent, + SubmissionSectionUploadFileComponent, TestComponent ], providers: [ @@ -103,8 +103,8 @@ describe('UploadSectionFileComponent test suite', () => { { provide: SectionUploadService, useValue: getMockSectionUploadService() }, ChangeDetectorRef, NgbModal, - UploadSectionFileComponent, - UploadSectionFileEditComponent + SubmissionSectionUploadFileComponent, + SubmissionSectionUploadFileEditComponent ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents().then(); @@ -136,7 +136,7 @@ describe('UploadSectionFileComponent test suite', () => { testFixture.destroy(); }); - it('should create UploadSectionFileComponent', inject([UploadSectionFileComponent], (app: UploadSectionFileComponent) => { + it('should create SubmissionSectionUploadFileComponent', inject([SubmissionSectionUploadFileComponent], (app: SubmissionSectionUploadFileComponent) => { expect(app).toBeDefined(); @@ -145,7 +145,7 @@ describe('UploadSectionFileComponent test suite', () => { describe('', () => { beforeEach(() => { - fixture = TestBed.createComponent(UploadSectionFileComponent); + fixture = TestBed.createComponent(SubmissionSectionUploadFileComponent); comp = fixture.componentInstance; compAsAny = comp; submissionServiceStub = TestBed.get(SubmissionService); @@ -235,7 +235,7 @@ describe('UploadSectionFileComponent test suite', () => { })); it('should save Bitstream File data properly when form is valid', fakeAsync(() => { - compAsAny.fileEditComp = TestBed.get(UploadSectionFileEditComponent); + compAsAny.fileEditComp = TestBed.get(SubmissionSectionUploadFileEditComponent); compAsAny.fileEditComp.formRef = {formGroup: null}; compAsAny.pathCombiner = pathCombiner; const event = new Event('click', null); @@ -290,7 +290,7 @@ describe('UploadSectionFileComponent test suite', () => { })); it('should not save Bitstream File data properly when form is not valid', fakeAsync(() => { - compAsAny.fileEditComp = TestBed.get(UploadSectionFileEditComponent); + compAsAny.fileEditComp = TestBed.get(SubmissionSectionUploadFileEditComponent); compAsAny.fileEditComp.formRef = {formGroup: null}; compAsAny.pathCombiner = pathCombiner; const event = new Event('click', null); diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.ts b/src/app/submission/sections/upload/file/section-upload-file.component.ts index 4d8f8119d7..9923c358e7 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.ts +++ b/src/app/submission/sections/upload/file/section-upload-file.component.ts @@ -20,7 +20,7 @@ import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service import { SubmissionJsonPatchOperationsService } from '../../../../core/submission/submission-json-patch-operations.service'; import { SubmissionObject } from '../../../../core/submission/models/submission-object.model'; import { WorkspaceitemSectionUploadObject } from '../../../../core/submission/models/workspaceitem-section-upload.model'; -import { UploadSectionFileEditComponent } from './edit/section-upload-file-edit.component'; +import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component'; import { Group } from '../../../../core/eperson/models/group.model'; /** @@ -31,7 +31,7 @@ import { Group } from '../../../../core/eperson/models/group.model'; styleUrls: ['./section-upload-file.component.scss'], templateUrl: './section-upload-file.component.html', }) -export class UploadSectionFileComponent implements OnChanges, OnInit { +export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { /** * The list of available access condition @@ -138,10 +138,10 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { protected subscriptions: Subscription[] = []; /** - * The [[UploadSectionFileEditComponent]] reference - * @type {UploadSectionFileEditComponent} + * The [[SubmissionSectionUploadFileEditComponent]] reference + * @type {SubmissionSectionUploadFileEditComponent} */ - @ViewChild(UploadSectionFileEditComponent) fileEditComp: UploadSectionFileEditComponent; + @ViewChild(SubmissionSectionUploadFileEditComponent) fileEditComp: SubmissionSectionUploadFileEditComponent; /** * Initialize instance variables diff --git a/src/app/submission/sections/upload/file/view/section-upload-file-view.component.html b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.html index 7f5250778c..65b4b6379b 100644 --- a/src/app/submission/sections/upload/file/view/section-upload-file-view.component.html +++ b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.html @@ -25,5 +25,5 @@ - + diff --git a/src/app/submission/sections/upload/file/view/section-upload-file-view.component.spec.ts b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.spec.ts index af2bb587ac..87b025e6a9 100644 --- a/src/app/submission/sections/upload/file/view/section-upload-file-view.component.spec.ts +++ b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.spec.ts @@ -6,15 +6,15 @@ import { TranslateModule } from '@ngx-translate/core'; import { createTestComponent } from '../../../../../shared/testing/utils'; import { mockUploadFiles } from '../../../../../shared/mocks/mock-submission'; import { FormComponent } from '../../../../../shared/form/form.component'; -import { UploadSectionFileViewComponent } from './section-upload-file-view.component'; +import { SubmissionSectionUploadFileViewComponent } from './section-upload-file-view.component'; import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe'; import { Metadata } from '../../../../../core/shared/metadata.utils'; -describe('UploadSectionFileViewComponent test suite', () => { +describe('SubmissionSectionUploadFileViewComponent test suite', () => { - let comp: UploadSectionFileViewComponent; + let comp: SubmissionSectionUploadFileViewComponent; let compAsAny: any; - let fixture: ComponentFixture; + let fixture: ComponentFixture; const fileData: any = mockUploadFiles[0]; @@ -26,11 +26,11 @@ describe('UploadSectionFileViewComponent test suite', () => { declarations: [ TruncatePipe, FormComponent, - UploadSectionFileViewComponent, + SubmissionSectionUploadFileViewComponent, TestComponent ], providers: [ - UploadSectionFileViewComponent + SubmissionSectionUploadFileViewComponent ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents().then(); @@ -43,7 +43,7 @@ describe('UploadSectionFileViewComponent test suite', () => { // synchronous beforeEach beforeEach(() => { const html = ` - `; + `; testFixture = createTestComponent(html, TestComponent) as ComponentFixture; testComp = testFixture.componentInstance; @@ -53,7 +53,7 @@ describe('UploadSectionFileViewComponent test suite', () => { testFixture.destroy(); }); - it('should create UploadSectionFileViewComponent', inject([UploadSectionFileViewComponent], (app: UploadSectionFileViewComponent) => { + it('should create SubmissionSectionUploadFileViewComponent', inject([SubmissionSectionUploadFileViewComponent], (app: SubmissionSectionUploadFileViewComponent) => { expect(app).toBeDefined(); @@ -62,7 +62,7 @@ describe('UploadSectionFileViewComponent test suite', () => { describe('', () => { beforeEach(() => { - fixture = TestBed.createComponent(UploadSectionFileViewComponent); + fixture = TestBed.createComponent(SubmissionSectionUploadFileViewComponent); comp = fixture.componentInstance; compAsAny = comp; }); diff --git a/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts index cafd3c7947..bb2fea20f8 100644 --- a/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts +++ b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts @@ -9,10 +9,10 @@ import { MetadataMap, MetadataValue } from '../../../../../core/shared/metadata. * This component allow to show bitstream's metadata */ @Component({ - selector: 'ds-submission-upload-section-file-view', + selector: 'ds-submission-section-upload-file-view', templateUrl: './section-upload-file-view.component.html', }) -export class UploadSectionFileViewComponent implements OnInit { +export class SubmissionSectionUploadFileViewComponent implements OnInit { /** * The bitstream's metadata data diff --git a/src/app/submission/sections/upload/section-upload.component.html b/src/app/submission/sections/upload/section-upload.component.html index afe8f3314a..d63bc1b7d6 100644 --- a/src/app/submission/sections/upload/section-upload.component.html +++ b/src/app/submission/sections/upload/section-upload.component.html @@ -22,7 +22,7 @@ {{ 'submission.sections.upload.header.policy.default.withlist' | translate:{ "collectionName": collectionName } }} - + diff --git a/src/app/submission/sections/upload/section-upload.component.spec.ts b/src/app/submission/sections/upload/section-upload.component.spec.ts index 345b78d358..be8f096964 100644 --- a/src/app/submission/sections/upload/section-upload.component.spec.ts +++ b/src/app/submission/sections/upload/section-upload.component.spec.ts @@ -24,7 +24,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { CommonModule } from '@angular/common'; import { SubmissionUploadsConfigService } from '../../../core/config/submission-uploads-config.service'; import { SectionUploadService } from './section-upload.service'; -import { UploadSectionComponent } from './section-upload.component'; +import { SubmissionSectionUploadComponent } from './section-upload.component'; import { CollectionDataService } from '../../../core/data/collection-data.service'; import { GroupEpersonService } from '../../../core/eperson/group-eperson.service'; import { cold, hot } from 'jasmine-marbles'; @@ -71,11 +71,11 @@ const sectionObject: SectionDataObject = { sectionType: SectionsType.Upload }; -describe('UploadSectionComponent test suite', () => { +describe('SubmissionSectionUploadComponent test suite', () => { - let comp: UploadSectionComponent; + let comp: SubmissionSectionUploadComponent; let compAsAny: any; - let fixture: ComponentFixture; + let fixture: ComponentFixture; let submissionServiceStub: SubmissionServiceStub; let sectionsServiceStub: SectionsServiceStub; let collectionDataService: any; @@ -114,7 +114,7 @@ describe('UploadSectionComponent test suite', () => { TranslateModule.forRoot() ], declarations: [ - UploadSectionComponent, + SubmissionSectionUploadComponent, TestComponent ], providers: [ @@ -127,7 +127,7 @@ describe('UploadSectionComponent test suite', () => { { provide: 'sectionDataProvider', useValue: sectionObject }, { provide: 'submissionIdProvider', useValue: submissionId }, ChangeDetectorRef, - UploadSectionComponent + SubmissionSectionUploadComponent ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents().then(); @@ -150,7 +150,7 @@ describe('UploadSectionComponent test suite', () => { testFixture.destroy(); }); - it('should create UploadSectionComponent', inject([UploadSectionComponent], (app: UploadSectionComponent) => { + it('should create SubmissionSectionUploadComponent', inject([SubmissionSectionUploadComponent], (app: SubmissionSectionUploadComponent) => { expect(app).toBeDefined(); @@ -159,7 +159,7 @@ describe('UploadSectionComponent test suite', () => { describe('', () => { beforeEach(() => { - fixture = TestBed.createComponent(UploadSectionComponent); + fixture = TestBed.createComponent(SubmissionSectionUploadComponent); comp = fixture.componentInstance; compAsAny = comp; submissionServiceStub = TestBed.get(SubmissionService); diff --git a/src/app/submission/sections/upload/section-upload.component.ts b/src/app/submission/sections/upload/section-upload.component.ts index 3145cb8301..61a44fe46a 100644 --- a/src/app/submission/sections/upload/section-upload.component.ts +++ b/src/app/submission/sections/upload/section-upload.component.ts @@ -42,7 +42,7 @@ export interface AccessConditionGroupsMapEntry { templateUrl: './section-upload.component.html', }) @renderSectionFor(SectionsType.Upload) -export class UploadSectionComponent extends SectionModelComponent { +export class SubmissionSectionUploadComponent extends SectionModelComponent { /** * The AlertType enumeration diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index 1b29f43a51..f3893344fe 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -3,7 +3,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CoreModule } from '../core/core.module'; import { SharedModule } from '../shared/shared.module'; -import { FormSectionComponent } from './sections/form/section-form.component'; +import { SubmissionSectionformComponent } from './sections/form/section-form.component'; import { SectionsDirective } from './sections/sections.directive'; import { SectionsService } from './sections/sections.service'; import { SubmissionFormCollectionComponent } from './form/collection/submission-form-collection.component'; @@ -16,16 +16,16 @@ import { StoreModule } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; import { submissionReducers } from './submission.reducers'; import { submissionEffects } from './submission.effects'; -import { UploadSectionComponent } from './sections/upload/section-upload.component'; +import { SubmissionSectionUploadComponent } from './sections/upload/section-upload.component'; import { SectionUploadService } from './sections/upload/section-upload.service'; import { SubmissionUploadFilesComponent } from './form/submission-upload-files/submission-upload-files.component'; -import { LicenseSectionComponent } from './sections/license/section-license.component'; +import { SubmissionSectionLicenseComponent } from './sections/license/section-license.component'; import { SubmissionUploadsConfigService } from '../core/config/submission-uploads-config.service'; import { SubmissionEditComponent } from './edit/submission-edit.component'; -import { UploadSectionFileComponent } from './sections/upload/file/section-upload-file.component'; -import { UploadSectionFileEditComponent } from './sections/upload/file/edit/section-upload-file-edit.component'; -import { UploadSectionFileViewComponent } from './sections/upload/file/view/section-upload-file-view.component'; -import { SectionUploadAccessConditionsComponent } from './sections/upload/accessConditions/section-upload-access-conditions.component'; +import { SubmissionSectionUploadFileComponent } from './sections/upload/file/section-upload-file.component'; +import { SubmissionSectionUploadFileEditComponent } from './sections/upload/file/edit/section-upload-file-edit.component'; +import { SubmissionSectionUploadFileViewComponent } from './sections/upload/file/view/section-upload-file-view.component'; +import { SubmissionSectionUploadAccessConditionsComponent } from './sections/upload/accessConditions/submission-section-upload-access-conditions.component'; import { SubmissionSubmitComponent } from './submit/submission-submit.component'; @NgModule({ @@ -38,10 +38,10 @@ import { SubmissionSubmitComponent } from './submit/submission-submit.component' TranslateModule ], declarations: [ - SectionUploadAccessConditionsComponent, - UploadSectionComponent, - FormSectionComponent, - LicenseSectionComponent, + SubmissionSectionUploadAccessConditionsComponent, + SubmissionSectionUploadComponent, + SubmissionSectionformComponent, + SubmissionSectionLicenseComponent, SectionsDirective, SectionContainerComponent, SubmissionEditComponent, @@ -51,14 +51,14 @@ import { SubmissionSubmitComponent } from './submit/submission-submit.component' SubmissionFormFooterComponent, SubmissionSubmitComponent, SubmissionUploadFilesComponent, - UploadSectionFileComponent, - UploadSectionFileEditComponent, - UploadSectionFileViewComponent + SubmissionSectionUploadFileComponent, + SubmissionSectionUploadFileEditComponent, + SubmissionSectionUploadFileViewComponent ], entryComponents: [ - UploadSectionComponent, - FormSectionComponent, - LicenseSectionComponent, + SubmissionSectionUploadComponent, + SubmissionSectionformComponent, + SubmissionSectionLicenseComponent, SectionContainerComponent], exports: [ SubmissionEditComponent, From b0e76d227f3ee8b1239da483f9232df4dca46d6c Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 21 Mar 2019 12:15:01 +0100 Subject: [PATCH 18/27] Added more comments --- src/app/core/data/data.service.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 4e66c7673e..2c21e94c66 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -165,6 +165,11 @@ export abstract class DataService { return this.rdbService.buildSingle(href); } + /** + * Return object search endpoint by given search method + * + * @param searchMethod The search method for the object + */ protected getSearchEndpoint(searchMethod: string): Observable { return this.halService.getEndpoint(`${this.linkPath}/search`).pipe( filter((href: string) => isNotEmpty(href)), From a017b178f789df323f0d52afad20b60676901f5c Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 21 Mar 2019 12:30:40 +0100 Subject: [PATCH 19/27] Added more comments --- ...sion-upload-file-access-condition.model.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/app/core/submission/models/submission-upload-file-access-condition.model.ts b/src/app/core/submission/models/submission-upload-file-access-condition.model.ts index ca2f21de47..8b89397f24 100644 --- a/src/app/core/submission/models/submission-upload-file-access-condition.model.ts +++ b/src/app/core/submission/models/submission-upload-file-access-condition.model.ts @@ -1,7 +1,30 @@ +/** + * An interface to represent bitstream's access condition. + */ export class SubmissionUploadFileAccessConditionObject { + + /** + * The access condition id + */ id: string; + + /** + * The access condition name + */ name: string; + + /** + * The access group UUID defined in this access condition + */ groupUUID: string; + + /** + * Possible start date of the access condition + */ startDate: string; + + /** + * Possible end date of the access condition + */ endDate: string; } From 49c060d1ae8c6f295b14bd6d46414d559a67aef2 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 21 Mar 2019 12:37:17 +0100 Subject: [PATCH 20/27] Added more comments --- src/app/core/submission/models/workflowitem.model.ts | 3 +++ src/app/core/submission/models/workspaceitem.model.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/app/core/submission/models/workflowitem.model.ts b/src/app/core/submission/models/workflowitem.model.ts index 3df49c91f7..f1a0467f43 100644 --- a/src/app/core/submission/models/workflowitem.model.ts +++ b/src/app/core/submission/models/workflowitem.model.ts @@ -1,4 +1,7 @@ import { Workspaceitem } from './workspaceitem.model'; +/** + * A model class for a Workflowitem. + */ export class Workflowitem extends Workspaceitem { } diff --git a/src/app/core/submission/models/workspaceitem.model.ts b/src/app/core/submission/models/workspaceitem.model.ts index e927431d71..6548191ba2 100644 --- a/src/app/core/submission/models/workspaceitem.model.ts +++ b/src/app/core/submission/models/workspaceitem.model.ts @@ -1,5 +1,8 @@ import { SubmissionObject } from './submission-object.model'; +/** + * A model class for a Workspaceitem. + */ export class Workspaceitem extends SubmissionObject { } From a87900c13256c5fb574452d1b9e6cbf3d8dffa35 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 21 Mar 2019 13:16:38 +0100 Subject: [PATCH 21/27] Renamed AlertsComponent to AlertComponent and added tests --- .../alert.component.html} | 0 .../alert.component.scss} | 0 src/app/shared/alert/alert.component.spec.ts | 114 ++++++++++++++++++ .../alert.component.ts} | 8 +- .../aletrs-type.ts => alert/aletr-type.ts} | 0 src/app/shared/shared.module.ts | 4 +- .../container/section-container.component.ts | 2 +- .../upload/section-upload.component.ts | 2 +- 8 files changed, 122 insertions(+), 8 deletions(-) rename src/app/shared/{alerts/alerts.component.html => alert/alert.component.html} (100%) rename src/app/shared/{alerts/alerts.component.scss => alert/alert.component.scss} (100%) create mode 100644 src/app/shared/alert/alert.component.spec.ts rename src/app/shared/{alerts/alerts.component.ts => alert/alert.component.ts} (90%) rename src/app/shared/{alerts/aletrs-type.ts => alert/aletr-type.ts} (100%) diff --git a/src/app/shared/alerts/alerts.component.html b/src/app/shared/alert/alert.component.html similarity index 100% rename from src/app/shared/alerts/alerts.component.html rename to src/app/shared/alert/alert.component.html diff --git a/src/app/shared/alerts/alerts.component.scss b/src/app/shared/alert/alert.component.scss similarity index 100% rename from src/app/shared/alerts/alerts.component.scss rename to src/app/shared/alert/alert.component.scss diff --git a/src/app/shared/alert/alert.component.spec.ts b/src/app/shared/alert/alert.component.spec.ts new file mode 100644 index 0000000000..e235e27b28 --- /dev/null +++ b/src/app/shared/alert/alert.component.spec.ts @@ -0,0 +1,114 @@ +import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core'; +import { BrowserModule, By } from '@angular/platform-browser'; +import { CommonModule } from '@angular/common'; +import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +import { TranslateModule } from '@ngx-translate/core'; + +import { AlertComponent } from './alert.component'; +import { createTestComponent } from '../testing/utils'; +import { AlertType } from './aletr-type'; + +describe('AlertComponent test suite', () => { + + let comp: AlertComponent; + let compAsAny: any; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + BrowserModule, + CommonModule, + NoopAnimationsModule, + TranslateModule.forRoot() + ], + declarations: [ + AlertComponent, + TestComponent + ], + providers: [ + ChangeDetectorRef, + AlertComponent + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents().then(); + })); + + describe('', () => { + let testComp: TestComponent; + let testFixture: ComponentFixture; + + // synchronous beforeEach + beforeEach(() => { + const html = ` + `; + + testFixture = createTestComponent(html, TestComponent) as ComponentFixture; + testComp = testFixture.componentInstance; + }); + + afterEach(() => { + testFixture.destroy(); + }); + + it('should create AlertComponent', inject([AlertComponent], (app: AlertComponent) => { + + expect(app).toBeDefined(); + })); + }); + + describe('', () => { + beforeEach(() => { + fixture = TestBed.createComponent(AlertComponent); + comp = fixture.componentInstance; + compAsAny = comp; + comp.content = 'test alert'; + comp.dismissible = true; + comp.type = AlertType.Info; + fixture.detectChanges(); + }); + + it('should display close icon when dismissible is true', () => { + + const btn = fixture.debugElement.query(By.css('.close')); + expect(btn).toBeDefined(); + }); + + it('should not display close icon when dismissible is false', () => { + comp.dismissible = false; + fixture.detectChanges(); + + const btn = fixture.debugElement.query(By.css('.close')); + expect(btn).toBeDefined(); + }); + + it('should dismiss alert when click on close icon', () => { + spyOn(comp, 'dismiss'); + const btn = fixture.debugElement.query(By.css('.close')); + + btn.nativeElement.click(); + + expect(comp.dismiss).toHaveBeenCalled(); + }); + + afterEach(() => { + fixture.destroy(); + comp = null; + compAsAny = null; + }); + }); +}); + +// declare a test component +@Component({ + selector: 'ds-test-cmp', + template: `` +}) +class TestComponent { + + content = 'test alert'; + dismissible = true; + type = AlertType.Info; +} diff --git a/src/app/shared/alerts/alerts.component.ts b/src/app/shared/alert/alert.component.ts similarity index 90% rename from src/app/shared/alerts/alerts.component.ts rename to src/app/shared/alert/alert.component.ts index d5fc2b48c7..93535d2057 100644 --- a/src/app/shared/alerts/alerts.component.ts +++ b/src/app/shared/alert/alert.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectorRef, Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; import { trigger } from '@angular/animations'; -import { AlertType } from './aletrs-type'; +import { AlertType } from './aletr-type'; import { fadeOutLeave, fadeOutState } from '../animations/fade'; /** @@ -15,10 +15,10 @@ import { fadeOutLeave, fadeOutState } from '../animations/fade'; fadeOutLeave, fadeOutState, ]) ], - templateUrl: './alerts.component.html', - styleUrls: ['./alerts.component.scss'] + templateUrl: './alert.component.html', + styleUrls: ['./alert.component.scss'] }) -export class AlertsComponent { +export class AlertComponent { /** * The alert content diff --git a/src/app/shared/alerts/aletrs-type.ts b/src/app/shared/alert/aletr-type.ts similarity index 100% rename from src/app/shared/alerts/aletrs-type.ts rename to src/app/shared/alert/aletr-type.ts diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 28ac5ef6b3..7ec41a301b 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -76,7 +76,7 @@ import { NumberPickerComponent } from './number-picker/number-picker.component'; import { DsDatePickerComponent } from './form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component'; import { DsDynamicLookupComponent } from './form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component'; import { MockAdminGuard } from './mocks/mock-admin-guard.service'; -import { AlertsComponent } from './alerts/alerts.component'; +import { AlertComponent } from './alert/alert.component'; import { ObjNgFor } from './utils/object-ngfor.pipe'; import { BrowseByComponent } from './browse-by/browse-by.component'; import { BrowseEntryListElementComponent } from './object-list/browse-entry-list-element/browse-entry-list-element.component'; @@ -140,7 +140,7 @@ const PIPES = [ const COMPONENTS = [ // put shared components here - AlertsComponent, + AlertComponent, AuthNavMenuComponent, ChipsComponent, ComcolPageContentComponent, diff --git a/src/app/submission/sections/container/section-container.component.ts b/src/app/submission/sections/container/section-container.component.ts index b1453dffb3..cc7ef0bf87 100644 --- a/src/app/submission/sections/container/section-container.component.ts +++ b/src/app/submission/sections/container/section-container.component.ts @@ -3,7 +3,7 @@ import { Component, Injector, Input, OnInit, ViewChild } from '@angular/core'; import { SectionsDirective } from '../sections.directive'; import { SectionDataObject } from '../models/section-data.model'; import { rendersSectionType } from '../sections-decorator'; -import { AlertType } from '../../../shared/alerts/aletrs-type'; +import { AlertType } from '../../../shared/alert/aletr-type'; /** * This component represents a section that contains the submission license form. diff --git a/src/app/submission/sections/upload/section-upload.component.ts b/src/app/submission/sections/upload/section-upload.component.ts index 61a44fe46a..a14e970723 100644 --- a/src/app/submission/sections/upload/section-upload.component.ts +++ b/src/app/submission/sections/upload/section-upload.component.ts @@ -15,7 +15,7 @@ import { SectionsType } from '../sections-type'; import { renderSectionFor } from '../sections-decorator'; import { SectionDataObject } from '../models/section-data.model'; import { SubmissionObjectEntry } from '../../objects/submission-objects.reducer'; -import { AlertType } from '../../../shared/alerts/aletrs-type'; +import { AlertType } from '../../../shared/alert/aletr-type'; import { RemoteData } from '../../../core/data/remote-data'; import { Group } from '../../../core/eperson/models/group.model'; import { SectionsService } from '../sections.service'; From 433faf932c8f87b3fea03a267057ec3a2d34d923 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 21 Mar 2019 14:01:25 +0100 Subject: [PATCH 22/27] Renamed SectionContainerComponent to SubmissionSectionContainerComponent --- .../form/submission-form.component.html | 6 +++--- .../section-container.component.spec.ts | 20 +++++++++---------- .../container/section-container.component.ts | 4 ++-- src/app/submission/submission.module.ts | 6 +++--- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/app/submission/form/submission-form.component.html b/src/app/submission/form/submission-form.component.html index 21f92c9cf1..31e46e2ccc 100644 --- a/src/app/submission/form/submission-form.component.html +++ b/src/app/submission/form/submission-form.component.html @@ -24,9 +24,9 @@
- +