diff --git a/src/app/curation-form/curation-form.component.spec.ts b/src/app/curation-form/curation-form.component.spec.ts index 4ff013f77c..dc70b925e8 100644 --- a/src/app/curation-form/curation-form.component.spec.ts +++ b/src/app/curation-form/curation-form.component.spec.ts @@ -15,6 +15,7 @@ import { By } from '@angular/platform-browser'; import { ConfigurationDataService } from '../core/data/configuration-data.service'; import { ConfigurationProperty } from '../core/shared/configuration-property.model'; import { getProcessDetailRoute } from '../process-page/process-page-routing.paths'; +import { HandleService } from '../shared/handle.service'; describe('CurationFormComponent', () => { let comp: CurationFormComponent; @@ -23,6 +24,7 @@ describe('CurationFormComponent', () => { let scriptDataService: ScriptDataService; let processDataService: ProcessDataService; let configurationDataService: ConfigurationDataService; + let handleService: HandleService; let notificationsService; let router; @@ -51,6 +53,10 @@ describe('CurationFormComponent', () => { })) }); + handleService = { + normalizeHandle: (a) => a + } as any; + notificationsService = new NotificationsServiceStub(); router = new RouterStub(); @@ -58,11 +64,12 @@ describe('CurationFormComponent', () => { imports: [TranslateModule.forRoot(), FormsModule, ReactiveFormsModule], declarations: [CurationFormComponent], providers: [ - {provide: ScriptDataService, useValue: scriptDataService}, - {provide: ProcessDataService, useValue: processDataService}, - {provide: NotificationsService, useValue: notificationsService}, - {provide: Router, useValue: router}, - {provide: ConfigurationDataService, useValue: configurationDataService}, + { provide: ScriptDataService, useValue: scriptDataService }, + { provide: ProcessDataService, useValue: processDataService }, + { provide: NotificationsService, useValue: notificationsService }, + { provide: HandleService, useValue: handleService }, + { provide: Router, useValue: router}, + { provide: ConfigurationDataService, useValue: configurationDataService }, ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }).compileComponents(); @@ -143,4 +150,13 @@ describe('CurationFormComponent', () => { {name: '-i', value: 'all'}, ], []); }); + + it(`should show an error notification and return when an invalid dsoHandle is provided`, () => { + comp.dsoHandle = 'test-handle'; + spyOn(handleService, 'normalizeHandle').and.returnValue(null); + comp.submit(); + + expect(notificationsService.error).toHaveBeenCalled(); + expect(scriptDataService.invoke).not.toHaveBeenCalled(); + }); }); diff --git a/src/app/curation-form/curation-form.component.ts b/src/app/curation-form/curation-form.component.ts index 31501e70d7..422c955037 100644 --- a/src/app/curation-form/curation-form.component.ts +++ b/src/app/curation-form/curation-form.component.ts @@ -5,7 +5,7 @@ import { getFirstCompletedRemoteData } from '../core/shared/operators'; import { find, map } from 'rxjs/operators'; import { NotificationsService } from '../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; -import { hasValue, isEmpty, isNotEmpty } from '../shared/empty.util'; +import { hasValue, isEmpty, isNotEmpty, hasNoValue } from '../shared/empty.util'; import { RemoteData } from '../core/data/remote-data'; import { Router } from '@angular/router'; import { ProcessDataService } from '../core/data/processes/process-data.service'; @@ -14,9 +14,9 @@ import { ConfigurationDataService } from '../core/data/configuration-data.servic import { ConfigurationProperty } from '../core/shared/configuration-property.model'; import { Observable } from 'rxjs'; import { getProcessDetailRoute } from '../process-page/process-page-routing.paths'; +import { HandleService } from '../shared/handle.service'; export const CURATION_CFG = 'plugin.named.org.dspace.curate.CurationTask'; - /** * Component responsible for rendering the Curation Task form */ @@ -39,6 +39,7 @@ export class CurationFormComponent implements OnInit { private processDataService: ProcessDataService, private notificationsService: NotificationsService, private translateService: TranslateService, + private handleService: HandleService, private router: Router ) { } @@ -76,13 +77,19 @@ export class CurationFormComponent implements OnInit { const taskName = this.form.get('task').value; let handle; if (this.hasHandleValue()) { - handle = this.dsoHandle; + handle = this.handleService.normalizeHandle(this.dsoHandle); + if (isEmpty(handle)) { + this.notificationsService.error(this.translateService.get('curation.form.submit.error.head'), + this.translateService.get('curation.form.submit.error.invalid-handle')); + return; + } } else { - handle = this.form.get('handle').value; + handle = this.handleService.normalizeHandle(this.form.get('handle').value); if (isEmpty(handle)) { handle = 'all'; } } + this.scriptDataService.invoke('curate', [ { name: '-t', value: taskName }, { name: '-i', value: handle }, diff --git a/src/app/shared/comcol/comcol-page-handle/comcol-page-handle.component.html b/src/app/shared/comcol/comcol-page-handle/comcol-page-handle.component.html index b3ca75bf94..552854a0c0 100644 --- a/src/app/shared/comcol/comcol-page-handle/comcol-page-handle.component.html +++ b/src/app/shared/comcol/comcol-page-handle/comcol-page-handle.component.html @@ -1,4 +1,4 @@

{{ title | translate }}

-
{{getHandle()}}
+
{{getHandle()}}
diff --git a/src/app/shared/handle.service.spec.ts b/src/app/shared/handle.service.spec.ts new file mode 100644 index 0000000000..b326eb0416 --- /dev/null +++ b/src/app/shared/handle.service.spec.ts @@ -0,0 +1,47 @@ +import { HandleService } from './handle.service'; + +describe('HandleService', () => { + let service: HandleService; + + beforeEach(() => { + service = new HandleService(); + }); + + describe(`normalizeHandle`, () => { + it(`should simply return an already normalized handle`, () => { + let input, output; + + input = '123456789/123456'; + output = service.normalizeHandle(input); + expect(output).toEqual(input); + + input = '12.3456.789/123456'; + output = service.normalizeHandle(input); + expect(output).toEqual(input); + }); + + it(`should normalize a handle url`, () => { + let input, output; + + input = 'https://hdl.handle.net/handle/123456789/123456'; + output = service.normalizeHandle(input); + expect(output).toEqual('123456789/123456'); + + input = 'https://rest.api/server/handle/123456789/123456'; + output = service.normalizeHandle(input); + expect(output).toEqual('123456789/123456'); + }); + + it(`should return null if the input doesn't contain a handle`, () => { + let input, output; + + input = 'https://hdl.handle.net/handle/123456789'; + output = service.normalizeHandle(input); + expect(output).toBeNull(); + + input = 'something completely different'; + output = service.normalizeHandle(input); + expect(output).toBeNull(); + }); + }); +}); diff --git a/src/app/shared/handle.service.ts b/src/app/shared/handle.service.ts new file mode 100644 index 0000000000..da0f17f7de --- /dev/null +++ b/src/app/shared/handle.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@angular/core'; +import { isNotEmpty, isEmpty } from './empty.util'; + +const PREFIX_REGEX = /handle\/([^\/]+\/[^\/]+)$/; +const NO_PREFIX_REGEX = /^([^\/]+\/[^\/]+)$/; + +@Injectable({ + providedIn: 'root' +}) +export class HandleService { + + + /** + * Turns a handle string into the default 123456789/12345 format + * + * @param handle the input handle + * + * normalizeHandle('123456789/123456') // '123456789/123456' + * normalizeHandle('12.3456.789/123456') // '12.3456.789/123456' + * normalizeHandle('https://hdl.handle.net/handle/123456789/123456') // '123456789/123456' + * normalizeHandle('https://rest.api/server/handle/123456789/123456') // '123456789/123456' + * normalizeHandle('https://rest.api/server/handle/123456789') // null + */ + normalizeHandle(handle: string): string { + let matches: string[]; + if (isNotEmpty(handle)) { + matches = handle.match(PREFIX_REGEX); + } + + if (isEmpty(matches) || matches.length < 2) { + matches = handle.match(NO_PREFIX_REGEX); + } + + if (isEmpty(matches) || matches.length < 2) { + return null; + } else { + return matches[1]; + } + } + +} diff --git a/src/app/submission/sections/accesses/section-accesses.component.spec.ts b/src/app/submission/sections/accesses/section-accesses.component.spec.ts index 5509cab4bb..65123db533 100644 --- a/src/app/submission/sections/accesses/section-accesses.component.spec.ts +++ b/src/app/submission/sections/accesses/section-accesses.component.spec.ts @@ -16,11 +16,15 @@ import { SectionAccessesService } from './section-accesses.service'; import { SectionFormOperationsService } from '../form/section-form-operations.service'; import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; -import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service'; +import { + SubmissionJsonPatchOperationsService +} from '../../../core/submission/submission-json-patch-operations.service'; import { getSectionAccessesService } from '../../../shared/mocks/section-accesses.service.mock'; import { getMockFormOperationsService } from '../../../shared/mocks/form-operations-service.mock'; import { getMockTranslateService } from '../../../shared/mocks/translate.service.mock'; -import { SubmissionJsonPatchOperationsServiceStub } from '../../../shared/testing/submission-json-patch-operations-service.stub'; +import { + SubmissionJsonPatchOperationsServiceStub +} from '../../../shared/testing/submission-json-patch-operations-service.stub'; import { BrowserModule } from '@angular/platform-browser'; import { of as observableOf } from 'rxjs'; @@ -42,8 +46,6 @@ describe('SubmissionSectionAccessesComponent', () => { let fixture: ComponentFixture; const sectionsServiceStub = new SectionsServiceStub(); - // const pathCombiner = new JsonPatchOperationPathCombiner('sections', sectionId, 'files', fileIndex); - const builderService: FormBuilderService = getMockFormBuilderService(); const submissionAccessesConfigService = getSubmissionAccessesConfigService(); const sectionAccessesService = getSectionAccessesService(); @@ -55,6 +57,7 @@ describe('SubmissionSectionAccessesComponent', () => { }); let formService: any; + let formbuilderService: any; const storeStub = jasmine.createSpyObj('store', ['dispatch']); @@ -86,7 +89,6 @@ describe('SubmissionSectionAccessesComponent', () => { declarations: [SubmissionSectionAccessesComponent, FormComponent], providers: [ { provide: SectionsService, useValue: sectionsServiceStub }, - { provide: FormBuilderService, useValue: builderService }, { provide: SubmissionAccessesConfigService, useValue: submissionAccessesConfigService }, { provide: SectionAccessesService, useValue: sectionAccessesService }, { provide: SectionFormOperationsService, useValue: sectionFormOperationsService }, @@ -97,6 +99,7 @@ describe('SubmissionSectionAccessesComponent', () => { { provide: SubmissionJsonPatchOperationsService, useValue: SubmissionJsonPatchOperationsServiceStub }, { provide: 'sectionDataProvider', useValue: sectionData }, { provide: 'submissionIdProvider', useValue: '1508' }, + FormBuilderService ] }) .compileComponents(); @@ -106,6 +109,7 @@ describe('SubmissionSectionAccessesComponent', () => { fixture = TestBed.createComponent(SubmissionSectionAccessesComponent); component = fixture.componentInstance; formService = TestBed.inject(FormService); + formbuilderService = TestBed.inject(FormBuilderService); formService.validateAllFormFields.and.callFake(() => null); formService.isValid.and.returnValue(observableOf(true)); formService.getFormData.and.returnValue(observableOf(mockAccessesFormData)); @@ -133,11 +137,22 @@ describe('SubmissionSectionAccessesComponent', () => { it('formModel type array should have formgroup with 1 input and 2 datepickers', () => { const formModel: any = component.formModel[1]; const formGroup = formModel.groupFactory()[0].group; + expect(formGroup[0] instanceof DynamicSelectModel).toBeTrue(); expect(formGroup[1] instanceof DynamicDatePickerModel).toBeTrue(); expect(formGroup[2] instanceof DynamicDatePickerModel).toBeTrue(); }); + it('should have set maxStartDate and maxEndDate properly', () => { + const maxStartDate = {year: 2024, month: 12, day: 20}; + const maxEndDate = {year: 2022, month: 6, day: 20}; + + const startDateModel = formbuilderService.findById('startDate', component.formModel); + expect(startDateModel.max).toEqual(maxStartDate); + const endDateModel = formbuilderService.findById('endDate', component.formModel); + expect(endDateModel.max).toEqual(maxEndDate); + }); + it('when checkbox changed it should call operationsBuilder replace function', () => { component.onChange(checkboxChangeEvent); fixture.detectChanges(); diff --git a/src/app/submission/sections/accesses/section-accesses.component.ts b/src/app/submission/sections/accesses/section-accesses.component.ts index 40863e7ef9..c59f9af2d0 100644 --- a/src/app/submission/sections/accesses/section-accesses.component.ts +++ b/src/app/submission/sections/accesses/section-accesses.component.ts @@ -41,7 +41,9 @@ import { FORM_ACCESS_CONDITION_TYPE_LAYOUT } from './section-accesses.model'; import { hasValue, isNotEmpty, isNotNull } from '../../../shared/empty.util'; -import { WorkspaceitemSectionAccessesObject } from '../../../core/submission/models/workspaceitem-section-accesses.model'; +import { + WorkspaceitemSectionAccessesObject +} from '../../../core/submission/models/workspaceitem-section-accesses.model'; import { SubmissionAccessesConfigService } from '../../../core/config/submission-accesses-config.service'; import { getFirstSucceededRemoteData } from '../../../core/shared/operators'; import { FormComponent } from '../../../shared/form/form.component'; @@ -50,8 +52,12 @@ import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder import { SectionFormOperationsService } from '../form/section-form-operations.service'; import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder'; import { AccessesConditionOption } from '../../../core/config/models/config-accesses-conditions-options.model'; -import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service'; +import { + SubmissionJsonPatchOperationsService +} from '../../../core/submission/submission-json-patch-operations.service'; import { dateToISOFormat } from '../../../shared/date.util'; +import { DynamicFormControlCondition } from '@ng-dynamic-forms/core/lib/model/misc/dynamic-form-control-relation.model'; +import { DynamicDateControlValue } from '@ng-dynamic-forms/core/lib/model/dynamic-date-control.model'; /** * This component represents a section for managing item's access conditions. @@ -322,40 +328,61 @@ export class SubmissionSectionAccessesComponent extends SectionModelComponent { } accessConditionTypeModelConfig.options = accessConditionTypeOptions; - // Dynamically assign of relation in config. For startdate, endDate, groups. - const hasStart = []; - const hasEnd = []; - const hasGroups = []; + // Dynamically assign of relation in config. For startDate and endDate. + const startDateCondition: DynamicFormControlCondition[] = []; + const endDateCondition: DynamicFormControlCondition[] = []; + let maxStartDate: DynamicDateControlValue; + let maxEndDate: DynamicDateControlValue; this.availableAccessConditionOptions.forEach((condition) => { - const showStart: boolean = condition.hasStartDate === true; - const showEnd: boolean = condition.hasEndDate === true; - const showGroups: boolean = showStart || showEnd; - if (showStart) { - hasStart.push({ id: 'name', value: condition.name }); + + if (condition.hasStartDate) { + startDateCondition.push({ id: 'name', value: condition.name }); + if (condition.maxStartDate) { + const min = new Date(condition.maxStartDate); + maxStartDate = { + year: min.getUTCFullYear(), + month: min.getUTCMonth() + 1, + day: min.getUTCDate() + }; + } } - if (showEnd) { - hasEnd.push({ id: 'name', value: condition.name }); - } - if (showGroups) { - hasGroups.push({ id: 'name', value: condition.name }); + if (condition.hasEndDate) { + endDateCondition.push({ id: 'name', value: condition.name }); + if (condition.maxEndDate) { + const max = new Date(condition.maxEndDate); + maxEndDate = { + year: max.getUTCFullYear(), + month: max.getUTCMonth() + 1, + day: max.getUTCDate() + }; + } } }); - const confStart = { relations: [{ match: MATCH_ENABLED, operator: OR_OPERATOR, when: hasStart }] }; - const confEnd = { relations: [{ match: MATCH_ENABLED, operator: OR_OPERATOR, when: hasEnd }] }; + const confStart = { relations: [{ match: MATCH_ENABLED, operator: OR_OPERATOR, when: startDateCondition }] }; + const confEnd = { relations: [{ match: MATCH_ENABLED, operator: OR_OPERATOR, when: endDateCondition }] }; + const hasStartDate = startDateCondition.length > 0; + const hasEndDate = endDateCondition.length > 0; accessConditionsArrayConfig.groupFactory = () => { const type = new DynamicSelectModel(accessConditionTypeModelConfig, FORM_ACCESS_CONDITION_TYPE_LAYOUT); const startDateConfig = Object.assign({}, FORM_ACCESS_CONDITION_START_DATE_CONFIG, confStart); + if (maxStartDate) { + startDateConfig.max = maxStartDate; + } + const endDateConfig = Object.assign({}, FORM_ACCESS_CONDITION_END_DATE_CONFIG, confEnd); + if (maxEndDate) { + endDateConfig.max = maxEndDate; + } const startDate = new DynamicDatePickerModel(startDateConfig, FORM_ACCESS_CONDITION_START_DATE_LAYOUT); const endDate = new DynamicDatePickerModel(endDateConfig, FORM_ACCESS_CONDITION_END_DATE_LAYOUT); const accessConditionGroupConfig = Object.assign({}, ACCESS_CONDITION_GROUP_CONFIG); accessConditionGroupConfig.group = [type]; - if (hasStart.length > 0) { + if (hasStartDate) { accessConditionGroupConfig.group.push(startDate); } - if (hasEnd.length > 0) { + if (hasEndDate) { accessConditionGroupConfig.group.push(endDate); } return [new DynamicFormGroupModel(accessConditionGroupConfig, ACCESS_CONDITION_GROUP_LAYOUT)]; 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 aa03d37eb2..d008bf61f1 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 @@ -1,10 +1,9 @@ import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core'; -import { waitForAsync, ComponentFixture, inject, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { BrowserModule } from '@angular/platform-browser'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; import { - DynamicFormArrayGroupModel, DynamicFormArrayModel, DynamicFormControlEvent, DynamicFormGroupModel, @@ -17,13 +16,13 @@ import { SubmissionService } from '../../../../submission.service'; import { SubmissionSectionUploadFileEditComponent } from './section-upload-file-edit.component'; import { POLICY_DEFAULT_WITH_LIST } from '../../section-upload.component'; import { + mockFileFormData, mockSubmissionCollectionId, mockSubmissionId, + mockSubmissionObject, mockUploadConfigResponse, mockUploadConfigResponseMetadata, mockUploadFiles, - mockFileFormData, - mockSubmissionObject, } from '../../../../../shared/mocks/submission.mock'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormComponent } from '../../../../../shared/form/form.component'; @@ -32,12 +31,20 @@ import { getMockFormService } from '../../../../../shared/mocks/form-service.moc import { createTestComponent } from '../../../../../shared/testing/utils.test'; import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { JsonPatchOperationsBuilder } from '../../../../../core/json-patch/builder/json-patch-operations-builder'; -import { SubmissionJsonPatchOperationsServiceStub } from '../../../../../shared/testing/submission-json-patch-operations-service.stub'; -import { SubmissionJsonPatchOperationsService } from '../../../../../core/submission/submission-json-patch-operations.service'; +import { + SubmissionJsonPatchOperationsServiceStub +} from '../../../../../shared/testing/submission-json-patch-operations-service.stub'; +import { + SubmissionJsonPatchOperationsService +} from '../../../../../core/submission/submission-json-patch-operations.service'; import { SectionUploadService } from '../../section-upload.service'; import { getMockSectionUploadService } from '../../../../../shared/mocks/section-upload.service.mock'; -import { FormFieldMetadataValueObject } from '../../../../../shared/form/builder/models/form-field-metadata-value.model'; -import { JsonPatchOperationPathCombiner } from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner'; +import { + FormFieldMetadataValueObject +} from '../../../../../shared/form/builder/models/form-field-metadata-value.model'; +import { + JsonPatchOperationPathCombiner +} from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner'; import { dateToISOFormat } from '../../../../../shared/date.util'; import { of } from 'rxjs'; @@ -171,6 +178,8 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => { it('should init form model properly', () => { comp.fileData = fileData; comp.formId = 'testFileForm'; + const maxStartDate = {year: 2022, month: 1, day: 12}; + const maxEndDate = {year: 2019, month: 7, day: 12}; comp.ngOnInit(); @@ -179,6 +188,10 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => { expect(comp.formModel[0] instanceof DynamicFormGroupModel).toBeTruthy(); expect(comp.formModel[1] instanceof DynamicFormArrayModel).toBeTruthy(); expect((comp.formModel[1] as DynamicFormArrayModel).groups.length).toBe(2); + const startDateModel = formbuilderService.findById('startDate', comp.formModel); + expect(startDateModel.max).toEqual(maxStartDate); + const endDateModel = formbuilderService.findById('endDate', comp.formModel); + expect(endDateModel.max).toEqual(maxEndDate); }); it('should call setOptions method onChange', () => { @@ -208,20 +221,19 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => { const formGroup = formbuilderService.createFormGroup(comp.formModel); const control = formbuilderService.getFormControlById('name', formGroup, comp.formModel, 0); - spyOn(formbuilderService, 'findById').and.callThrough(); + spyOn(control.parent, 'markAsDirty').and.callThrough(); control.value = 'openaccess'; comp.setOptions(model, control); - expect(formbuilderService.findById).not.toHaveBeenCalledWith('endDate', (model.parent as DynamicFormArrayGroupModel).group); - expect(formbuilderService.findById).not.toHaveBeenCalledWith('startDate', (model.parent as DynamicFormArrayGroupModel).group); + expect(control.parent.markAsDirty).toHaveBeenCalled(); control.value = 'lease'; comp.setOptions(model, control); - expect(formbuilderService.findById).toHaveBeenCalledWith('endDate', (model.parent as DynamicFormArrayGroupModel).group); + expect(control.parent.markAsDirty).toHaveBeenCalled(); control.value = 'embargo'; comp.setOptions(model, control); - expect(formbuilderService.findById).toHaveBeenCalledWith('startDate', (model.parent as DynamicFormArrayGroupModel).group); + expect(control.parent.markAsDirty).toHaveBeenCalled(); }); it('should retrieve Value From Field properly', () => { 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 3a43e718a0..195d291530 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 @@ -3,9 +3,7 @@ import { FormControl } from '@angular/forms'; import { DYNAMIC_FORM_CONTROL_TYPE_DATEPICKER, - DynamicDateControlModel, DynamicDatePickerModel, - DynamicFormArrayGroupModel, DynamicFormArrayModel, DynamicFormControlEvent, DynamicFormControlModel, @@ -15,7 +13,9 @@ import { OR_OPERATOR } from '@ng-dynamic-forms/core'; -import { WorkspaceitemSectionUploadFileObject } from '../../../../../core/submission/models/workspaceitem-section-upload-file.model'; +import { + WorkspaceitemSectionUploadFileObject +} from '../../../../../core/submission/models/workspaceitem-section-upload-file.model'; import { FormBuilderService } from '../../../../../shared/form/builder/form-builder.service'; import { BITSTREAM_ACCESS_CONDITION_GROUP_CONFIG, @@ -43,12 +43,20 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { filter, mergeMap, take } from 'rxjs/operators'; import { dateToISOFormat } from '../../../../../shared/date.util'; import { SubmissionObject } from '../../../../../core/submission/models/submission-object.model'; -import { WorkspaceitemSectionUploadObject } from '../../../../../core/submission/models/workspaceitem-section-upload.model'; +import { + WorkspaceitemSectionUploadObject +} from '../../../../../core/submission/models/workspaceitem-section-upload.model'; import { JsonPatchOperationsBuilder } from '../../../../../core/json-patch/builder/json-patch-operations-builder'; -import { SubmissionJsonPatchOperationsService } from '../../../../../core/submission/submission-json-patch-operations.service'; -import { JsonPatchOperationPathCombiner } from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner'; +import { + SubmissionJsonPatchOperationsService +} from '../../../../../core/submission/submission-json-patch-operations.service'; +import { + JsonPatchOperationPathCombiner +} from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner'; import { SectionUploadService } from '../../section-upload.service'; import { Subscription } from 'rxjs'; +import { DynamicFormControlCondition } from '@ng-dynamic-forms/core/lib/model/misc/dynamic-form-control-relation.model'; +import { DynamicDateControlValue } from '@ng-dynamic-forms/core/lib/model/dynamic-date-control.model'; /** * This component represents the edit form for bitstream @@ -237,8 +245,6 @@ export class SubmissionSectionUploadFileEditComponent implements OnInit { this.availableAccessConditionOptions.filter((element) => element.name === control.value) .forEach((element) => accessCondition = element ); if (isNotEmpty(accessCondition)) { - const showGroups: boolean = accessCondition.hasStartDate === true || accessCondition.hasEndDate === true; - const startDateControl: FormControl = control.parent.get('startDate') as FormControl; const endDateControl: FormControl = control.parent.get('endDate') as FormControl; @@ -249,33 +255,6 @@ export class SubmissionSectionUploadFileEditComponent implements OnInit { startDateControl?.setValue(null); control.parent.markAsDirty(); endDateControl?.setValue(null); - - if (showGroups) { - if (accessCondition.hasStartDate) { - const startDateModel = this.formBuilderService.findById( - 'startDate', - (model.parent as DynamicFormArrayGroupModel).group) as DynamicDateControlModel; - - const min = new Date(accessCondition.maxStartDate); - startDateModel.max = { - year: min.getUTCFullYear(), - month: min.getUTCMonth() + 1, - day: min.getUTCDate() - }; - } - if (accessCondition.hasEndDate) { - const endDateModel = this.formBuilderService.findById( - 'endDate', - (model.parent as DynamicFormArrayGroupModel).group) as DynamicDateControlModel; - - const max = new Date(accessCondition.maxEndDate); - endDateModel.max = { - year: max.getUTCFullYear(), - month: max.getUTCMonth() + 1, - day: max.getUTCDate() - }; - } - } } } @@ -335,38 +314,63 @@ export class SubmissionSectionUploadFileEditComponent implements OnInit { } accessConditionTypeModelConfig.options = accessConditionTypeOptions; - // Dynamically assign of relation in config. For startdate, endDate, groups. - const hasStart = []; - const hasEnd = []; - const hasGroups = []; + // Dynamically assign of relation in config. For startDate and endDate. + const startDateCondition: DynamicFormControlCondition[] = []; + const endDateCondition: DynamicFormControlCondition[] = []; + let maxStartDate: DynamicDateControlValue; + let maxEndDate: DynamicDateControlValue; this.availableAccessConditionOptions.forEach((condition) => { - const showStart: boolean = condition.hasStartDate === true; - const showEnd: boolean = condition.hasEndDate === true; - const showGroups: boolean = showStart || showEnd; - if (showStart) { - hasStart.push({id: 'name', value: condition.name}); + + if (condition.hasStartDate) { + startDateCondition.push({ id: 'name', value: condition.name }); + if (condition.maxStartDate) { + const min = new Date(condition.maxStartDate); + maxStartDate = { + year: min.getUTCFullYear(), + month: min.getUTCMonth() + 1, + day: min.getUTCDate() + }; + } } - if (showEnd) { - hasEnd.push({id: 'name', value: condition.name}); - } - if (showGroups) { - hasGroups.push({id: 'name', value: condition.name}); + if (condition.hasEndDate) { + endDateCondition.push({ id: 'name', value: condition.name }); + if (condition.maxEndDate) { + const max = new Date(condition.maxEndDate); + maxEndDate = { + year: max.getUTCFullYear(), + month: max.getUTCMonth() + 1, + day: max.getUTCDate() + }; + } } }); - const confStart = {relations: [{match: MATCH_ENABLED, operator: OR_OPERATOR, when: hasStart}]}; - const confEnd = {relations: [{match: MATCH_ENABLED, operator: OR_OPERATOR, when: hasEnd}]}; + const confStart = { relations: [{ match: MATCH_ENABLED, operator: OR_OPERATOR, when: startDateCondition }] }; + const confEnd = { relations: [{ match: MATCH_ENABLED, operator: OR_OPERATOR, when: endDateCondition }] }; + const hasStartDate = startDateCondition.length > 0; + const hasEndDate = endDateCondition.length > 0; accessConditionsArrayConfig.groupFactory = () => { const type = new DynamicSelectModel(accessConditionTypeModelConfig, BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT); const startDateConfig = Object.assign({}, BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_CONFIG, confStart); + if (maxStartDate) { + startDateConfig.max = maxStartDate; + } + const endDateConfig = Object.assign({}, BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_CONFIG, confEnd); + if (maxEndDate) { + endDateConfig.max = maxEndDate; + } const startDate = new DynamicDatePickerModel(startDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_LAYOUT); const endDate = new DynamicDatePickerModel(endDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_LAYOUT); const accessConditionGroupConfig = Object.assign({}, BITSTREAM_ACCESS_CONDITION_GROUP_CONFIG); accessConditionGroupConfig.group = [type]; - if (hasStart.length > 0) { accessConditionGroupConfig.group.push(startDate); } - if (hasEnd.length > 0) { accessConditionGroupConfig.group.push(endDate); } + if (hasStartDate) { + accessConditionGroupConfig.group.push(startDate); + } + if (hasEndDate) { + accessConditionGroupConfig.group.push(endDate); + } return [new DynamicFormGroupModel(accessConditionGroupConfig, BITSTREAM_ACCESS_CONDITION_GROUP_LAYOUT)]; }; diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index eb6bde35ad..97ffb7ffcd 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -50,7 +50,7 @@ import { ThemedSubmissionEditComponent } from './edit/themed-submission-edit.com import { ThemedSubmissionSubmitComponent } from './submit/themed-submission-submit.component'; import { ThemedSubmissionImportExternalComponent } from './import-external/themed-submission-import-external.component'; import { FormModule } from '../shared/form/form.module'; -import { NgbCollapseModule, NgbModalModule, NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; +import { NgbAccordionModule, NgbCollapseModule, NgbModalModule } from '@ng-bootstrap/ng-bootstrap'; import { SubmissionSectionAccessesComponent } from './sections/accesses/section-accesses.component'; import { SubmissionAccessesConfigService } from '../core/config/submission-accesses-config.service'; import { SectionAccessesService } from './sections/accesses/section-accesses.service'; @@ -71,7 +71,6 @@ const ENTRY_COMPONENTS = [ SubmissionSectionLicenseComponent, SubmissionSectionCcLicensesComponent, SubmissionSectionAccessesComponent, - SubmissionSectionUploadFileEditComponent, SubmissionSectionSherpaPoliciesComponent, ]; diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 23ab3a29e5..c3a874535c 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -193,8 +193,8 @@ // "admin.registries.bitstream-formats.table.supportLevel.head": "Support Level", "admin.registries.bitstream-formats.table.supportLevel.head": "Unterstützungsgrad", - // "admin.registries.bitstream-formats.title": "DSpace Angular :: Bitstream Format Registry", - "admin.registries.bitstream-formats.title": "DSpace Angular :: Referenzliste der Dateiformate", + // "admin.registries.bitstream-formats.title": "Bitstream Format Registry", + "admin.registries.bitstream-formats.title": "Referenzliste der Dateiformate", @@ -234,8 +234,8 @@ // "admin.registries.metadata.schemas.table.namespace": "Namespace", "admin.registries.metadata.schemas.table.namespace": "Namensraum", - // "admin.registries.metadata.title": "DSpace Angular :: Metadata Registry", - "admin.registries.metadata.title": "DSpace Angular :: Metadatenreferenzliste", + // "admin.registries.metadata.title": "Metadata Registry", + "admin.registries.metadata.title": "Metadatenreferenzliste", @@ -252,7 +252,7 @@ "admin.registries.schema.fields.no-items": "Es gibt keine Metadatenfelder in diesem Schema.", // "admin.registries.schema.fields.table.delete": "Delete selected", - "admin.registries.schema.fields.table.delete": "Ausgewähltes löschen", + "admin.registries.schema.fields.table.delete": "Auswahl löschen", // "admin.registries.schema.fields.table.field": "Field", "admin.registries.schema.fields.table.field": "Feld", @@ -311,8 +311,8 @@ // "admin.registries.schema.return": "Return", "admin.registries.schema.return": "Zurück", - // "admin.registries.schema.title": "DSpace Angular :: Metadata Schema Registry", - "admin.registries.schema.title": "DSpace Angular :: Referenzliste der Metadatenschemata", + // "admin.registries.schema.title": "Metadata Schema Registry", + "admin.registries.schema.title": "Referenzliste der Metadatenschemata", @@ -328,8 +328,8 @@ // "admin.access-control.epeople.actions.stop-impersonating": "Stop impersonating EPerson", "admin.access-control.epeople.actions.stop-impersonating": "Von Person ausloggen", - // "admin.access-control.epeople.title": "DSpace Angular :: EPeople", - "admin.access-control.epeople.title": "DSpace Angular :: Personen", + // "admin.access-control.epeople.title": "EPeople", + "admin.access-control.epeople.title": "Personen", // "admin.access-control.epeople.head": "EPeople", "admin.access-control.epeople.head": "Personen", @@ -444,14 +444,14 @@ - // "admin.access-control.groups.title": "DSpace Angular :: Groups", - "admin.access-control.groups.title": "DSpace Angular :: Gruppen", + // "admin.access-control.groups.title": "Groups", + "admin.access-control.groups.title": "Gruppen", - // "admin.access-control.groups.title.singleGroup": "DSpace Angular :: Edit Group", - "admin.access-control.groups.title.singleGroup": "DSpace Angular :: Gruppe bearbeiten", + // "admin.access-control.groups.title.singleGroup": "Edit Group", + "admin.access-control.groups.title.singleGroup": "Gruppe bearbeiten", - // "admin.access-control.groups.title.addGroup": "DSpace Angular :: New Group", - "admin.access-control.groups.title.addGroup": "DSpace Angular :: Neue Gruppe", + // "admin.access-control.groups.title.addGroup": "New Group", + "admin.access-control.groups.title.addGroup": "Neue Gruppe", // "admin.access-control.groups.head": "Groups", "admin.access-control.groups.head": "Gruppen", @@ -1008,9 +1008,12 @@ // "browse.startsWith.type_text": "Or enter first few letters:", "browse.startsWith.type_text": "Oder geben Sie die ersten Buchstaben ein:", - - // "browse.title": "Browsing {{ collection }} by {{ field }} {{ value }}", - "browse.title": "Auflistung von {{ collection }} nach {{ field }} {{ value }}", + + // "browse.title": "Browsing {{ collection }} by {{ field }}{{ startsWith }} {{ value }}", + "browse.title": "Auflistung {{ collection }} nach {{ field }}{{ startsWith }} {{ value }}", + + // "browse.title.page": "Browsing {{ collection }} by {{ field }} {{ value }}", + "browse.title.page": "Auflistung {{ collection }} nach {{ field }} {{ value }}", // "chips.remove": "Remove chip", @@ -1356,9 +1359,12 @@ // "community.edit.logo.delete.title": "Delete logo", "community.edit.logo.delete.title": "Logo löschen", + + // "communityList.breadcrumbs": "Community List", + "communityList.breadcrumbs": "Bereichsliste", - // "communityList.tabTitle": "DSpace - Community List", - "communityList.tabTitle": "DSpace - Bereichsliste", + // "communityList.tabTitle": "Community List", + "communityList.tabTitle": "Bereichsliste", // "communityList.title": "List of Communities", "communityList.title": "Liste der Bereiche", @@ -1375,7 +1381,7 @@ "community.create.notifications.success": "Bereich erfolgreich angelegt", // "community.create.sub-head": "Create a Sub-Community for Community {{ parent }}", - "community.create.sub-head": "Teilbeirech im Bereich {{ parent }} anlegen", + "community.create.sub-head": "Teilbereich im Bereich {{ parent }} anlegen", // "community.curate.header": "Curate Community: {{community}}", "community.curate.header": "Bereich: {{community}} pflegen", @@ -1562,7 +1568,7 @@ "community.form.errors.title.required": "Bitte geben Sie einen Namen für den Bereich ein.", // "community.form.rights": "Copyright text (HTML)", - "community.form.rights": "Copyrighterklärung (HTML)", + "community.form.rights": "Copyright Text (HTML)", // "community.form.tableofcontents": "News (HTML)", "community.form.tableofcontents": "Neuigkeiten (HTML)", @@ -1755,7 +1761,7 @@ "dso-selector.create.community.sub-level": "Einen neuen Bereich anlegen in", // "dso-selector.create.community.top-level": "Create a new top-level community", - "dso-selector.create.community.top-level": "Einen neuen Bereich auf oberster Ebene anlgen", + "dso-selector.create.community.top-level": "Einen neuen Bereich auf oberster Ebene anlegen", // "dso-selector.create.item.head": "New item", "dso-selector.create.item.head": "Neues Item", @@ -2141,8 +2147,8 @@ // "home.search-form.placeholder": "Search the repository ...", "home.search-form.placeholder": "Durchsuche Repositorium", - // "home.title": "DSpace Angular :: Home", - "home.title": "DSpace Angular :: Startseite", + // "home.title": "Home", + "home.title": "Startseite", // "home.top-level-communities.head": "Communities in DSpace", "home.top-level-communities.head": "Hauptbereiche in DSpace", @@ -2803,8 +2809,8 @@ // "item.search.results.head": "Item Search Results", "item.search.results.head": "Item-Suchergebnisse", - // "item.search.title": "DSpace Angular :: Item Search", - "item.search.title": "DSpace Angular :: Item-Suche", + // "item.search.title": "Item Search", + "item.search.title": "Item-Suche", @@ -3000,8 +3006,8 @@ // "journal.search.results.head": "Journal Search Results", "journal.search.results.head": "Suchergebnisse für Zeitschriften", - // "journal.search.title": "DSpace Angular :: Journal Search", - "journal.search.title": "DSpace Angular :: Zeitschriftensuche", + // "journal.search.title": "Journal Search", + "journal.search.title": "Zeitschriftensuche", @@ -3629,8 +3635,8 @@ // "person.search.results.head": "Person Search Results", "person.search.results.head": "Ergebnisse der Personensuche", - // "person.search.title": "DSpace Angular :: Person Search", - "person.search.title": "DSpace Angular :: Personensuche", + // "person.search.title": "Person Search", + "person.search.title": "Personensuche", @@ -3922,8 +3928,8 @@ // "publication.search.results.head": "Publication Search Results", "publication.search.results.head": "Suchergebnisse Publikationen", - // "publication.search.title": "DSpace Angular :: Publication Search", - "publication.search.title": "DSpace Angular :: Publikationssuche", + // "publication.search.title": "Publication Search", + "publication.search.title": "Publikationssuche", // "register-email.title": "New user registration", @@ -4081,9 +4087,27 @@ // "relationships.isContributorOf": "Contributors", "relationships.isContributorOf": "Beteiligte", - - - + + // "relationships.isContributorOf.OrgUnit": "Contributor (Organizational Unit)", + "relationships.isContributorOf.OrgUnit": "Beteiligte (Organisationseinheit)", + + // "relationships.isContributorOf.Person": "Contributor", + "relationships.isContributorOf.Person": "Beteiligte (Person)", + + // "relationships.isFundingAgencyOf.OrgUnit": "Funder", + "relationships.isFundingAgencyOf.OrgUnit": "Förderer", + + + // "repository.image.logo": "Repository logo", + "repository.image.logo": "Repository Logo", + + // "repository.title.prefix": "DSpace Angular :: ", + "repository.title.prefix": "DSpace Angular :: ", + + // "repository.title.prefixDSpace": "DSpace Angular ::", + "repository.title.prefixDSpace": "DSpace Angular ::", + + // "resource-policies.add.button": "Add", "resource-policies.add.button": "Hinzufügen", @@ -4245,8 +4269,8 @@ // "search.switch-configuration.title": "Show", "search.switch-configuration.title": "Zeige", - // "search.title": "DSpace Angular :: Search", - "search.title": "DSpace Angular :: Suche", + // "search.title": "Search", + "search.title": "Suche", // "search.breadcrumbs": "Search", "search.breadcrumbs": "Suche", @@ -4615,6 +4639,8 @@ "statistics.table.header.views": "Aufrufe", + // "submission.edit.breadcrumbs": "Edit Submission", + "submission.edit.breadcrumbs": "Einreichung bearbeiten", // "submission.edit.title": "Edit Submission", "submission.edit.title": "Einreichung bearbeiten", @@ -4640,6 +4666,12 @@ // "submission.general.discard.submit": "Discard", "submission.general.discard.submit": "Verwerfen", + // "submission.general.info.saved": "Saved", + "submission.general.info.saved": "Gespeichert", + + // "submission.general.info.pending-changes": "Unsaved changes", + "submission.general.info.pending-changes": "Ungespeicherte Änderungen", + // "submission.general.save": "Save", "submission.general.save": "Speichern", @@ -5199,7 +5231,7 @@ "submission.workflow.generic.delete": "Löschen", // "submission.workflow.generic.delete-help": "If you would to discard this item, select \"Delete\". You will then be asked to confirm it.", - "submission.workflow.generic.delete-help": "Wenn Sie dieses Item möchten, wählen Sie bitte \"Löschen\". Sie werden danach um Bestätigung gebeten.", + "submission.workflow.generic.delete-help": "Wenn Sie dieses Item verwerfen möchten, wählen Sie bitte \"Löschen\". Sie werden danach um Bestätigung gebeten.", // "submission.workflow.generic.edit": "Edit", "submission.workflow.generic.edit": "Bearbeiten", @@ -5279,6 +5311,13 @@ // "submission.workflow.tasks.pool.show-detail": "Show detail", "submission.workflow.tasks.pool.show-detail": "Details anzeigen", + // "submission.workspace.generic.view": "View", + "submission.workspace.generic.view": "Anzeige", + + // "submission.workspace.generic.view-help": "Select this option to view the item's metadata.", + "submission.workspace.generic.view-help": "Wählen Sie diese Option, um die Metadaten des Items anzuzeigen.", + + // "thumbnail.default.alt": "Thumbnail Image", "thumbnail.default.alt": "Vorschaubild", @@ -5358,10 +5397,14 @@ "virtual-metadata.delete-relationship.modal-head": "Wählen Sie die Items aus, deren virtuelle Metadaten Sie als echte Metadaten speichern möchten.", - + // "workspace.search.results.head": "Your submissions", + "workspace.search.results.head": "Ihre Veröffentlichungen", + // "workflowAdmin.search.results.head": "Administer Workflow", "workflowAdmin.search.results.head": "Workflow verwalten", - + + // "workflow.search.results.head": "Workflow tasks", + "workflow.search.results.head": "Workflow-Aufgaben", // "workflow-item.delete.notification.success.title": "Deleted", diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 860db5aac6..1abcd551a6 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1291,6 +1291,8 @@ "curation.form.submit.error.content": "An error occured when trying to start the curation task.", + "curation.form.submit.error.invalid-handle": "Couldn't determine the handle for this object", + "curation.form.handle.label": "Handle:", "curation.form.handle.hint": "Hint: Enter [your-handle-prefix]/0 to run a task across entire site (not all tasks may support this capability)",