From 8f63b54d713977c62c6ec775935a2c26e498a7eb Mon Sep 17 00:00:00 2001 From: Mattia Vianelli Date: Mon, 9 Oct 2023 15:55:24 +0200 Subject: [PATCH 01/14] CST-11045 Located component, working on adding new accordion --- .../models/notify-service-submission.model.ts | 25 +++++++++++++++++++ ...eitem-section-form-notify-service.model.ts | 17 +++++++++++++ .../models/workspaceitem-sections.model.ts | 4 +++ .../form/submission-form.component.html | 11 ++++++-- 4 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/app/core/submission/models/notify-service-submission.model.ts create mode 100644 src/app/core/submission/models/workspaceitem-section-form-notify-service.model.ts diff --git a/src/app/core/submission/models/notify-service-submission.model.ts b/src/app/core/submission/models/notify-service-submission.model.ts new file mode 100644 index 0000000000..f2075103ae --- /dev/null +++ b/src/app/core/submission/models/notify-service-submission.model.ts @@ -0,0 +1,25 @@ +/** + * An interface to represent a notifyService object + */ +export class NotifyServiceObject { + + /** + * The notifyService object + */ + id: string; + + /** + * The access condition name + */ + name: string; + + /** + * Possible start date of the access condition + */ + startDate: string; + + /** + * Possible end date of the access condition + */ + endDate: string; +} diff --git a/src/app/core/submission/models/workspaceitem-section-form-notify-service.model.ts b/src/app/core/submission/models/workspaceitem-section-form-notify-service.model.ts new file mode 100644 index 0000000000..b614eb9140 --- /dev/null +++ b/src/app/core/submission/models/workspaceitem-section-form-notify-service.model.ts @@ -0,0 +1,17 @@ +import {NotifyServiceObject} from './notify-service-submission.model' +/** + * An interface to represent the submission's item accesses condition. + */ +export interface WorkspaceitemSectionNotifyServiceRequestItemDissemination extends NotifyServiceObject { + /** + * The access condition id + */ + id: string; + + /** + * Boolean that indicates whether the current item must be findable via search or browse. + */ + discoverable: boolean; + + +} diff --git a/src/app/core/submission/models/workspaceitem-sections.model.ts b/src/app/core/submission/models/workspaceitem-sections.model.ts index a3ccd49dac..f5747c3326 100644 --- a/src/app/core/submission/models/workspaceitem-sections.model.ts +++ b/src/app/core/submission/models/workspaceitem-sections.model.ts @@ -5,6 +5,7 @@ import { WorkspaceitemSectionUploadObject } from './workspaceitem-section-upload import { WorkspaceitemSectionCcLicenseObject } from './workspaceitem-section-cc-license.model'; import {WorkspaceitemSectionIdentifiersObject} from './workspaceitem-section-identifiers.model'; import { WorkspaceitemSectionSherpaPoliciesObject } from './workspaceitem-section-sherpa-policies.model'; +import { WorkspaceitemSectionNotifyServiceRequestItemDissemination } from './workspaceitem-section-form-notify-service.model'; /** * An interface to represent submission's section object. @@ -25,4 +26,7 @@ export type WorkspaceitemSectionDataType | WorkspaceitemSectionAccessesObject | WorkspaceitemSectionSherpaPoliciesObject | WorkspaceitemSectionIdentifiersObject + | WorkspaceitemSectionNotifyServiceRequestItemDissemination | string; + + diff --git a/src/app/submission/form/submission-form.component.html b/src/app/submission/form/submission-form.component.html index 4a916cfe23..fa04a6793c 100644 --- a/src/app/submission/form/submission-form.component.html +++ b/src/app/submission/form/submission-form.component.html @@ -1,6 +1,7 @@
+

ds-submission-upload-files

@@ -9,7 +10,9 @@
- ds-submission-form-collection

+
+

ds-submission-form-section-add

@@ -28,12 +32,15 @@
+

ds-submission-section-container

+ [sectionData]="object"> +
From 494295cd97ce81bbb45b73d515b9656528326833 Mon Sep 17 00:00:00 2001 From: Mattia Vianelli Date: Tue, 24 Oct 2023 15:26:11 +0200 Subject: [PATCH 02/14] CST-11045 added sections parts --- ...eitem-section-form-notify-service.model.ts | 16 ++- .../ldn-service/ldn-service.component.html | 0 .../ldn-service/ldn-service.component.scss | 0 .../ldn-service/ldn-service.component.spec.ts | 23 ++++ .../ldn-service/ldn-service.component.ts | 129 ++++++++++++++++++ .../sections/ldn-service/ldn-service.model.ts | 27 ++++ src/app/submission/sections/sections-type.ts | 1 + src/app/submission/submission.module.ts | 3 + 8 files changed, 193 insertions(+), 6 deletions(-) create mode 100644 src/app/submission/sections/ldn-service/ldn-service.component.html create mode 100644 src/app/submission/sections/ldn-service/ldn-service.component.scss create mode 100644 src/app/submission/sections/ldn-service/ldn-service.component.spec.ts create mode 100644 src/app/submission/sections/ldn-service/ldn-service.component.ts create mode 100644 src/app/submission/sections/ldn-service/ldn-service.model.ts diff --git a/src/app/core/submission/models/workspaceitem-section-form-notify-service.model.ts b/src/app/core/submission/models/workspaceitem-section-form-notify-service.model.ts index b614eb9140..c952dc6100 100644 --- a/src/app/core/submission/models/workspaceitem-section-form-notify-service.model.ts +++ b/src/app/core/submission/models/workspaceitem-section-form-notify-service.model.ts @@ -1,17 +1,21 @@ import {NotifyServiceObject} from './notify-service-submission.model' +import { LdnService } from '../../../admin/admin-ldn-services/ldn-services-model/ldn-services.model'; /** - * An interface to represent the submission's item accesses condition. + * An interface to represent the submission's item ldn-services condition. */ export interface WorkspaceitemSectionNotifyServiceRequestItemDissemination extends NotifyServiceObject { /** - * The access condition id + * The ldn-review service */ - id: string; + reviewService: LdnService; /** - * Boolean that indicates whether the current item must be findable via search or browse. + * The ldn-endorse service */ - discoverable: boolean; - + endorseService: LdnService; + /** + * The ldn-ingest service + */ + ingestService: LdnService; } diff --git a/src/app/submission/sections/ldn-service/ldn-service.component.html b/src/app/submission/sections/ldn-service/ldn-service.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/submission/sections/ldn-service/ldn-service.component.scss b/src/app/submission/sections/ldn-service/ldn-service.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/submission/sections/ldn-service/ldn-service.component.spec.ts b/src/app/submission/sections/ldn-service/ldn-service.component.spec.ts new file mode 100644 index 0000000000..ec36f1f010 --- /dev/null +++ b/src/app/submission/sections/ldn-service/ldn-service.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LdnServiceComponent } from './ldn-service.component'; + +describe('LdnServiceComponent', () => { + let component: LdnServiceComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LdnServiceComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(LdnServiceComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/submission/sections/ldn-service/ldn-service.component.ts b/src/app/submission/sections/ldn-service/ldn-service.component.ts new file mode 100644 index 0000000000..6690395e72 --- /dev/null +++ b/src/app/submission/sections/ldn-service/ldn-service.component.ts @@ -0,0 +1,129 @@ +import { ChangeDetectorRef, Component, Inject, ViewChild } from '@angular/core'; +import { DynamicFormControlEvent } from '@ng-dynamic-forms/core'; +import { Observable, Subscription } from 'rxjs'; +import { SectionModelComponent } from '../models/section.model'; +import { renderSectionFor } from '../sections-decorator'; +import { SectionsType } from '../sections-type'; +import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner'; +import { FormComponent } from '../../../shared/form/form.component'; +import { CollectionDataService } from '../../../core/data/collection-data.service'; +import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; +import { SectionFormOperationsService } from '../form/section-form-operations.service'; +import { FormService } from '../../../shared/form/form.service'; +import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder'; +import { SectionsService } from '../sections.service'; +import { SubmissionService } from '../../submission.service'; +import { TranslateService } from '@ngx-translate/core'; +import { SectionDataObject } from '../models/section-data.model'; +import { + WorkspaceitemSectionNotifyServiceRequestItemDissemination +} from '../../../core/submission/models/workspaceitem-section-form-notify-service.model'; +import { hasValue } from '../../../shared/empty.util'; +/** + * This component represents a section that contains the submission ldn-service form. + */ +@Component({ + selector: 'ds-ldn-service', + templateUrl: './ldn-service.component.html', + styleUrls: ['./ldn-service.component.scss'] +}) +@renderSectionFor(SectionsType.LdnService) +export class LdnServiceComponent extends SectionModelComponent { + + /** + * The [[JsonPatchOperationPathCombiner]] object + * @type {JsonPatchOperationPathCombiner} + */ + protected pathCombiner: JsonPatchOperationPathCombiner; + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ + protected subs: Subscription[] = []; + + /** + * A boolean representing if div should start collapsed + */ + public isCollapsed = false; + + /** + * 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 {TranslateService} translateService + * @param {string} injectedCollectionId + * @param {SectionDataObject} injectedSectionData + * @param {string} injectedSubmissionId + */ + constructor(protected changeDetectorRef: ChangeDetectorRef, + protected collectionDataService: CollectionDataService, + protected formBuilderService: FormBuilderService, + protected formOperationsService: SectionFormOperationsService, + protected formService: FormService, + protected operationsBuilder: JsonPatchOperationsBuilder, + protected sectionService: SectionsService, + protected submissionService: SubmissionService, + protected translateService: TranslateService, + @Inject('collectionIdProvider') public injectedCollectionId: string, + @Inject('sectionDataProvider') public injectedSectionData: SectionDataObject, + @Inject('submissionIdProvider') public injectedSubmissionId: string) { + super(injectedCollectionId, injectedSectionData, injectedSubmissionId); + } + + /** + * Initialize all instance variables + */ + onSectionInit() { + this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionData.id); + this.subs.push( + this.sectionService.getSectionData(this.submissionId, this.sectionData.id, this.sectionData.sectionType) + .subscribe((ldnServicesSection: WorkspaceitemSectionNotifyServiceRequestItemDissemination) => { + console.log(ldnServicesSection); + }) + ); + } + + + + /** + * 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); + if (value) { + this.operationsBuilder.add(this.pathCombiner.getPath(path), value.value.toString(), false, true); + this.sectionService.dispatchRemoveSectionErrors(this.submissionId, this.sectionData.id); + } else { + this.operationsBuilder.remove(this.pathCombiner.getPath(path)); + } + } + + /** + * Unsubscribe from all subscriptions + */ + onSectionDestroy() { + this.subs + .filter((subscription) => hasValue(subscription)) + .forEach((subscription) => subscription.unsubscribe()); + } + + protected getSectionStatus(): Observable { + return undefined; + } + +} diff --git a/src/app/submission/sections/ldn-service/ldn-service.model.ts b/src/app/submission/sections/ldn-service/ldn-service.model.ts new file mode 100644 index 0000000000..fb441dd362 --- /dev/null +++ b/src/app/submission/sections/ldn-service/ldn-service.model.ts @@ -0,0 +1,27 @@ +export const SECTION_LDN_SERVICE_FORM_LAYOUT = { + + granted: { + element: { + container: 'custom-control custom-checkbox pl-1', + control: 'custom-control-input', + label: 'custom-control-label pt-1' + } + } +}; + +export const SECTION_LDN_SERVICE_FORM_MODEL = [ + { + id: 'granted', + label: 'submission.sections.license.granted-label', + required: true, + value: false, + validators: { + required: null + }, + errorMessages: { + required: 'submission.sections.license.required', + notgranted: 'submission.sections.license.notgranted' + }, + type: 'CHECKBOX', + } +]; diff --git a/src/app/submission/sections/sections-type.ts b/src/app/submission/sections/sections-type.ts index 6bca8a7252..40f6f85e0e 100644 --- a/src/app/submission/sections/sections-type.ts +++ b/src/app/submission/sections/sections-type.ts @@ -9,4 +9,5 @@ export enum SectionsType { SherpaPolicies = 'sherpaPolicy', Identifiers = 'identifiers', Collection = 'collection', + LdnService = 'ldn-service' } diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index cf0ab2b369..8f35bb4420 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -67,6 +67,7 @@ import { } from './sections/sherpa-policies/metadata-information/metadata-information.component'; import { SectionFormOperationsService } from './sections/form/section-form-operations.service'; import {SubmissionSectionIdentifiersComponent} from './sections/identifiers/section-identifiers.component'; +import { LdnServiceComponent } from './sections/ldn-service/ldn-service.component'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator @@ -76,6 +77,7 @@ const ENTRY_COMPONENTS = [ SubmissionSectionCcLicensesComponent, SubmissionSectionAccessesComponent, SubmissionSectionSherpaPoliciesComponent, + LdnServiceComponent ]; const DECLARATIONS = [ @@ -114,6 +116,7 @@ const DECLARATIONS = [ CoreModule.forRoot(), SharedModule, StoreModule.forFeature('submission', submissionReducers, storeModuleConfig as StoreConfig), + EffectsModule.forFeature(), EffectsModule.forFeature(submissionEffects), JournalEntitiesModule.withEntryComponents(), ResearchEntitiesModule.withEntryComponents(), From 7ed4d1457df7e9d794c2b28e63dfd44f726387df Mon Sep 17 00:00:00 2001 From: Mattia Vianelli Date: Wed, 25 Oct 2023 21:09:07 +0200 Subject: [PATCH 03/14] CST-11045 Provided coarnotify section logic and dataservices for configs --- .../ldn-services-data.service.ts | 6 +- .../ldn-service.constrain.model.ts | 2 +- src/app/core/core.module.ts | 12 +- .../form/submission-form.component.html | 5 + .../section-container.component.html | 4 +- .../ldn-service/ldn-service.component.scss | 0 .../ldn-service/ldn-service.component.spec.ts | 23 -- .../ldn-service/ldn-service.component.ts | 129 -------- .../coar-notify-config-data.service.ts | 122 +++++++ .../section-coar-notify-model.ts | 74 +++++ ...ction-coar-notify-service.resource-type.ts | 13 + ...coar-notify-workspaceitems-data-service.ts | 117 +++++++ .../section-coar-notify.component.html | 31 ++ .../section-coar-notify.component.scss} | 0 .../section-coar-notify.component.spec.ts | 23 ++ .../section-coar-notify.component.ts | 308 ++++++++++++++++++ ...mission-coar-notify-workspaceitem.model.ts | 35 ++ .../submission-coar-notify.config.ts | 39 +++ src/app/submission/sections/sections-type.ts | 2 +- src/app/submission/submission.module.ts | 10 +- src/app/submission/submission.service.ts | 1 + 21 files changed, 792 insertions(+), 164 deletions(-) delete mode 100644 src/app/submission/sections/ldn-service/ldn-service.component.scss delete mode 100644 src/app/submission/sections/ldn-service/ldn-service.component.spec.ts delete mode 100644 src/app/submission/sections/ldn-service/ldn-service.component.ts create mode 100644 src/app/submission/sections/section-coar-notify/coar-notify-config-data.service.ts create mode 100644 src/app/submission/sections/section-coar-notify/section-coar-notify-model.ts create mode 100644 src/app/submission/sections/section-coar-notify/section-coar-notify-service.resource-type.ts create mode 100644 src/app/submission/sections/section-coar-notify/section-coar-notify-workspaceitems-data-service.ts create mode 100644 src/app/submission/sections/section-coar-notify/section-coar-notify.component.html rename src/app/submission/sections/{ldn-service/ldn-service.component.html => section-coar-notify/section-coar-notify.component.scss} (100%) create mode 100644 src/app/submission/sections/section-coar-notify/section-coar-notify.component.spec.ts create mode 100644 src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts create mode 100644 src/app/submission/sections/section-coar-notify/submission-coar-notify-workspaceitem.model.ts create mode 100644 src/app/submission/sections/section-coar-notify/submission-coar-notify.config.ts diff --git a/src/app/admin/admin-ldn-services/ldn-services-data/ldn-services-data.service.ts b/src/app/admin/admin-ldn-services/ldn-services-data/ldn-services-data.service.ts index 35f9bee04f..cdd259447a 100644 --- a/src/app/admin/admin-ldn-services/ldn-services-data/ldn-services-data.service.ts +++ b/src/app/admin/admin-ldn-services/ldn-services-data/ldn-services-data.service.ts @@ -28,7 +28,7 @@ import { ChangeAnalyzer } from '../../../core/data/change-analyzer'; import { Operation } from 'fast-json-patch'; import { RestRequestMethod } from 'src/app/core/data/rest-request-method'; import { CreateData, CreateDataImpl } from '../../../core/data/base/create-data'; -import { ldnServiceConstrain } from '../ldn-services-model/ldn-service.constrain.model'; +import { LdnServiceConstrain } from '../ldn-services-model/ldn-service.constrain.model'; import { getFirstCompletedRemoteData } from 'src/app/core/shared/operators'; import { hasValue } from 'src/app/shared/empty.util'; @@ -92,7 +92,7 @@ export class LdnServicesService extends IdentifiableDataService impl return this.deleteData.deleteByHref(href, copyVirtualMetadata); } - public invoke(serviceName: string, serviceId: string, parameters: ldnServiceConstrain[], files: File[]): Observable> { + public invoke(serviceName: string, serviceId: string, parameters: LdnServiceConstrain[], files: File[]): Observable> { const requestId = this.requestService.generateRequestId(); this.getBrowseEndpoint().pipe( take(1), @@ -115,7 +115,7 @@ export class LdnServicesService extends IdentifiableDataService impl ); } - private getInvocationFormData(constrain: ldnServiceConstrain[], files: File[]): FormData { + private getInvocationFormData(constrain: LdnServiceConstrain[], files: File[]): FormData { const form: FormData = new FormData(); form.set('properties', JSON.stringify(constrain)); files.forEach((file: File) => { diff --git a/src/app/admin/admin-ldn-services/ldn-services-model/ldn-service.constrain.model.ts b/src/app/admin/admin-ldn-services/ldn-services-model/ldn-service.constrain.model.ts index 69a9baf273..500cefbd52 100644 --- a/src/app/admin/admin-ldn-services/ldn-services-model/ldn-service.constrain.model.ts +++ b/src/app/admin/admin-ldn-services/ldn-services-model/ldn-service.constrain.model.ts @@ -1,3 +1,3 @@ -export class ldnServiceConstrain { +export class LdnServiceConstrain { void: any; } diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 88da00f02c..d0f2dbbbaf 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -190,7 +190,11 @@ import { SuggestionSource } from './suggestion-notifications/reciter-suggestions import { LdnServicesService } from '../admin/admin-ldn-services/ldn-services-data/ldn-services-data.service'; import { LdnService } from '../admin/admin-ldn-services/ldn-services-model/ldn-services.model'; import { LdnItemfiltersService } from '../admin/admin-ldn-services/ldn-services-data/ldn-itemfilters-data.service'; -import { Itemfilter } from "../admin/admin-ldn-services/ldn-services-model/ldn-service-itemfilters"; +import { Itemfilter } from '../admin/admin-ldn-services/ldn-services-model/ldn-service-itemfilters'; +import { + CoarNotifyConfigDataService +} from '../submission/sections/section-coar-notify/coar-notify-config-data.service'; +import { SubmissionCoarNotifyConfig } from '../submission/sections/section-coar-notify/submission-coar-notify.config'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -315,7 +319,8 @@ const PROVIDERS = [ OrcidHistoryDataService, SupervisionOrderDataService, LdnServicesService, - LdnItemfiltersService + LdnItemfiltersService, + CoarNotifyConfigDataService ]; /** @@ -398,7 +403,8 @@ export const models = SuggestionTarget, SuggestionSource, LdnService, - Itemfilter + Itemfilter, + SubmissionCoarNotifyConfig ]; diff --git a/src/app/submission/form/submission-form.component.html b/src/app/submission/form/submission-form.component.html index fa04a6793c..c79364e2af 100644 --- a/src/app/submission/form/submission-form.component.html +++ b/src/app/submission/form/submission-form.component.html @@ -38,9 +38,14 @@ [sectionData]="object"> +
+ diff --git a/src/app/submission/sections/container/section-container.component.html b/src/app/submission/sections/container/section-container.component.html index e6ae9d1b9c..f39ba72ffa 100644 --- a/src/app/submission/sections/container/section-container.component.html +++ b/src/app/submission/sections/container/section-container.component.html @@ -42,10 +42,10 @@
-
+
aaaaaaaaa
-
\ No newline at end of file + diff --git a/src/app/submission/sections/ldn-service/ldn-service.component.scss b/src/app/submission/sections/ldn-service/ldn-service.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/app/submission/sections/ldn-service/ldn-service.component.spec.ts b/src/app/submission/sections/ldn-service/ldn-service.component.spec.ts deleted file mode 100644 index ec36f1f010..0000000000 --- a/src/app/submission/sections/ldn-service/ldn-service.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { LdnServiceComponent } from './ldn-service.component'; - -describe('LdnServiceComponent', () => { - let component: LdnServiceComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ LdnServiceComponent ] - }) - .compileComponents(); - - fixture = TestBed.createComponent(LdnServiceComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/submission/sections/ldn-service/ldn-service.component.ts b/src/app/submission/sections/ldn-service/ldn-service.component.ts deleted file mode 100644 index 6690395e72..0000000000 --- a/src/app/submission/sections/ldn-service/ldn-service.component.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { ChangeDetectorRef, Component, Inject, ViewChild } from '@angular/core'; -import { DynamicFormControlEvent } from '@ng-dynamic-forms/core'; -import { Observable, Subscription } from 'rxjs'; -import { SectionModelComponent } from '../models/section.model'; -import { renderSectionFor } from '../sections-decorator'; -import { SectionsType } from '../sections-type'; -import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner'; -import { FormComponent } from '../../../shared/form/form.component'; -import { CollectionDataService } from '../../../core/data/collection-data.service'; -import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; -import { SectionFormOperationsService } from '../form/section-form-operations.service'; -import { FormService } from '../../../shared/form/form.service'; -import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder'; -import { SectionsService } from '../sections.service'; -import { SubmissionService } from '../../submission.service'; -import { TranslateService } from '@ngx-translate/core'; -import { SectionDataObject } from '../models/section-data.model'; -import { - WorkspaceitemSectionNotifyServiceRequestItemDissemination -} from '../../../core/submission/models/workspaceitem-section-form-notify-service.model'; -import { hasValue } from '../../../shared/empty.util'; -/** - * This component represents a section that contains the submission ldn-service form. - */ -@Component({ - selector: 'ds-ldn-service', - templateUrl: './ldn-service.component.html', - styleUrls: ['./ldn-service.component.scss'] -}) -@renderSectionFor(SectionsType.LdnService) -export class LdnServiceComponent extends SectionModelComponent { - - /** - * The [[JsonPatchOperationPathCombiner]] object - * @type {JsonPatchOperationPathCombiner} - */ - protected pathCombiner: JsonPatchOperationPathCombiner; - - /** - * Array to track all subscriptions and unsubscribe them onDestroy - * @type {Array} - */ - protected subs: Subscription[] = []; - - /** - * A boolean representing if div should start collapsed - */ - public isCollapsed = false; - - /** - * 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 {TranslateService} translateService - * @param {string} injectedCollectionId - * @param {SectionDataObject} injectedSectionData - * @param {string} injectedSubmissionId - */ - constructor(protected changeDetectorRef: ChangeDetectorRef, - protected collectionDataService: CollectionDataService, - protected formBuilderService: FormBuilderService, - protected formOperationsService: SectionFormOperationsService, - protected formService: FormService, - protected operationsBuilder: JsonPatchOperationsBuilder, - protected sectionService: SectionsService, - protected submissionService: SubmissionService, - protected translateService: TranslateService, - @Inject('collectionIdProvider') public injectedCollectionId: string, - @Inject('sectionDataProvider') public injectedSectionData: SectionDataObject, - @Inject('submissionIdProvider') public injectedSubmissionId: string) { - super(injectedCollectionId, injectedSectionData, injectedSubmissionId); - } - - /** - * Initialize all instance variables - */ - onSectionInit() { - this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionData.id); - this.subs.push( - this.sectionService.getSectionData(this.submissionId, this.sectionData.id, this.sectionData.sectionType) - .subscribe((ldnServicesSection: WorkspaceitemSectionNotifyServiceRequestItemDissemination) => { - console.log(ldnServicesSection); - }) - ); - } - - - - /** - * 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); - if (value) { - this.operationsBuilder.add(this.pathCombiner.getPath(path), value.value.toString(), false, true); - this.sectionService.dispatchRemoveSectionErrors(this.submissionId, this.sectionData.id); - } else { - this.operationsBuilder.remove(this.pathCombiner.getPath(path)); - } - } - - /** - * Unsubscribe from all subscriptions - */ - onSectionDestroy() { - this.subs - .filter((subscription) => hasValue(subscription)) - .forEach((subscription) => subscription.unsubscribe()); - } - - protected getSectionStatus(): Observable { - return undefined; - } - -} diff --git a/src/app/submission/sections/section-coar-notify/coar-notify-config-data.service.ts b/src/app/submission/sections/section-coar-notify/coar-notify-config-data.service.ts new file mode 100644 index 0000000000..7b8d309667 --- /dev/null +++ b/src/app/submission/sections/section-coar-notify/coar-notify-config-data.service.ts @@ -0,0 +1,122 @@ +import { Injectable } from '@angular/core'; +import { dataService } from '../../../core/data/base/data-service.decorator'; +import { IdentifiableDataService } from '../../../core/data/base/identifiable-data.service'; +import { FindAllData, FindAllDataImpl } from '../../../core/data/base/find-all-data'; +import { DeleteData, DeleteDataImpl } from '../../../core/data/base/delete-data'; +import { RequestService } from '../../../core/data/request.service'; +import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service'; +import { ObjectCacheService } from '../../../core/cache/object-cache.service'; +import { HALEndpointService } from '../../../core/shared/hal-endpoint.service'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { FindListOptions } from '../../../core/data/find-list-options.model'; +import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model'; +import { Observable } from 'rxjs'; +import { RemoteData } from '../../../core/data/remote-data'; +import { PaginatedList } from '../../../core/data/paginated-list.model'; +import { NoContent } from '../../../core/shared/NoContent.model'; +import { map, take } from 'rxjs/operators'; +import { URLCombiner } from '../../../core/url-combiner/url-combiner'; +import { MultipartPostRequest } from '../../../core/data/request.models'; +import { RestRequest } from '../../../core/data/rest-request.model'; +import { SUBMISSION_COAR_NOTIFY_CONFIG } from './section-coar-notify-service.resource-type'; +import { SubmissionCoarNotifyConfig } from './submission-coar-notify.config'; +import { CreateData, CreateDataImpl } from '../../../core/data/base/create-data'; +import { PatchData, PatchDataImpl } from '../../../core/data/base/patch-data'; +import { ChangeAnalyzer } from '../../../core/data/change-analyzer'; +import { Operation } from 'fast-json-patch'; +import { RestRequestMethod } from '../../../core/data/rest-request-method'; +import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; +import { hasValue } from '../../../shared/empty.util'; + + +/** + * A service responsible for fetching/sending data from/to the REST API on the CoarNotifyConfig endpoint + */ +@Injectable() +@dataService(SUBMISSION_COAR_NOTIFY_CONFIG) +export class CoarNotifyConfigDataService extends IdentifiableDataService implements FindAllData, DeleteData, PatchData, CreateData { + createData: CreateDataImpl; + private findAllData: FindAllDataImpl; + private deleteData: DeleteDataImpl; + private patchData: PatchDataImpl; + private comparator: ChangeAnalyzer; + + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + ) { + super('submissioncoarnotifyconfigs', requestService, rdbService, objectCache, halService); + + this.findAllData = new FindAllDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive); + this.deleteData = new DeleteDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive, this.constructIdEndpoint); + this.patchData = new PatchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.comparator, this.responseMsToLive, this.constructIdEndpoint); + this.createData = new CreateDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive); + } + + + create(object: SubmissionCoarNotifyConfig): Observable> { + return this.createData.create(object); + } + + patch(object: SubmissionCoarNotifyConfig, operations: Operation[]): Observable> { + return this.patchData.patch(object, operations); + } + + update(object: SubmissionCoarNotifyConfig): Observable> { + return this.patchData.update(object); + } + + commitUpdates(method?: RestRequestMethod): void { + return this.patchData.commitUpdates(method); + } + + createPatchFromCache(object: SubmissionCoarNotifyConfig): Observable { + return this.patchData.createPatchFromCache(object); + } + + findAll(options?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig[]): Observable>> { + return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + } + + public delete(objectId: string, copyVirtualMetadata?: string[]): Observable> { + return this.deleteData.delete(objectId, copyVirtualMetadata); + } + + public deleteByHref(href: string, copyVirtualMetadata?: string[]): Observable> { + return this.deleteData.deleteByHref(href, copyVirtualMetadata); + } + + public invoke(serviceName: string, serviceId: string, files: File[]): Observable> { + const requestId = this.requestService.generateRequestId(); + this.getBrowseEndpoint().pipe( + take(1), + map((endpoint: string) => new URLCombiner(endpoint, serviceName, 'submissioncoarnotifyconfigmodel', serviceId).toString()), + map((endpoint: string) => { + const body = this.getInvocationFormData(files); + return new MultipartPostRequest(requestId, endpoint, body); + }) + ).subscribe((request: RestRequest) => this.requestService.send(request)); + + return this.rdbService.buildFromRequestUUID(requestId); + } + + public SubmissionCoarNotifyConfigModelWithNameExistsAndCanExecute(scriptName: string): Observable { + return this.findById(scriptName).pipe( + getFirstCompletedRemoteData(), + map((rd: RemoteData) => { + return hasValue(rd.payload); + }), + ); + } + + private getInvocationFormData(files: File[]): FormData { + const form: FormData = new FormData(); + files.forEach((file: File) => { + form.append('file', file); + }); + return form; + } +} diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify-model.ts b/src/app/submission/sections/section-coar-notify/section-coar-notify-model.ts new file mode 100644 index 0000000000..c8904fdca3 --- /dev/null +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify-model.ts @@ -0,0 +1,74 @@ +export const REQUEST_REVIEW_DROPDOWN = { + element: { + container: 'custom-control custom-select pl-1', + control: 'custom-select', + label: 'custom-control-label pt-1' + } +}; + +export const REQUEST_ENDORSEMENT_DROPDOWN = { + element: { + container: 'custom-control custom-select pl-1', + control: 'custom-select', + label: 'custom-control-label pt-1' + } +}; + +export const REQUEST_INGEST_DROPDOWN = { + element: { + container: 'custom-control custom-select pl-1', + control: 'custom-select', + label: 'custom-control-label pt-1' + } +}; + +export const SECTION_COAR_FORM_LAYOUT = { + requestReview: REQUEST_REVIEW_DROPDOWN, + requestEndorsement: REQUEST_ENDORSEMENT_DROPDOWN, + requestIngest: REQUEST_INGEST_DROPDOWN +}; + +export const SECTION_COAR_FORM_MODEL = [ + { + id: 'requestReview', + label: 'submission.sections.license.request-review-label', + required: false, + value: '', + validators: { + required: null + }, + errorMessages: { + required: 'submission.sections.license.required', + notgranted: 'submission.sections.license.notgranted' + }, + type: 'SELECT', + }, + { + id: 'requestEndorsement', + label: 'submission.sections.license.request-endorsement-label', + required: false, + value: '', + validators: { + required: null + }, + errorMessages: { + required: 'submission.sections.license.required', + notgranted: 'submission.sections.license.notgranted' + }, + type: 'SELECT', + }, + { + id: 'requestIngest', + label: 'submission.sections.license.request-ingest-label', + required: false, + value: '', + validators: { + required: null + }, + errorMessages: { + required: 'submission.sections.license.required', + notgranted: 'submission.sections.license.notgranted' + }, + type: 'SELECT', + } +]; diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify-service.resource-type.ts b/src/app/submission/sections/section-coar-notify/section-coar-notify-service.resource-type.ts new file mode 100644 index 0000000000..53e41783ce --- /dev/null +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify-service.resource-type.ts @@ -0,0 +1,13 @@ +/** + * The resource type for Ldn-Services + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +import { ResourceType } from '../../../core/shared/resource-type'; + + +export const SUBMISSION_COAR_NOTIFY_CONFIG = new ResourceType('submissioncoarnotifyconfig'); + +export const COAR_NOTIFY_WORKSPACEITEM = new ResourceType('workspaceitem'); + diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify-workspaceitems-data-service.ts b/src/app/submission/sections/section-coar-notify/section-coar-notify-workspaceitems-data-service.ts new file mode 100644 index 0000000000..dd287b096a --- /dev/null +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify-workspaceitems-data-service.ts @@ -0,0 +1,117 @@ +import { Injectable } from '@angular/core'; +import { dataService } from '../../../core/data/base/data-service.decorator'; +import { IdentifiableDataService } from '../../../core/data/base/identifiable-data.service'; +import { FindAllData, FindAllDataImpl } from '../../../core/data/base/find-all-data'; +import { DeleteData, DeleteDataImpl } from '../../../core/data/base/delete-data'; +import { RequestService } from '../../../core/data/request.service'; +import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service'; +import { ObjectCacheService } from '../../../core/cache/object-cache.service'; +import { HALEndpointService } from '../../../core/shared/hal-endpoint.service'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { FindListOptions } from '../../../core/data/find-list-options.model'; +import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model'; +import { Observable } from 'rxjs'; +import { RemoteData } from '../../../core/data/remote-data'; +import { PaginatedList } from '../../../core/data/paginated-list.model'; +import { NoContent } from '../../../core/shared/NoContent.model'; +import { map, take } from 'rxjs/operators'; +import { URLCombiner } from '../../../core/url-combiner/url-combiner'; +import { MultipartPostRequest } from '../../../core/data/request.models'; +import { RestRequest } from '../../../core/data/rest-request.model'; +import { COAR_NOTIFY_WORKSPACEITEM } from './section-coar-notify-service.resource-type'; +import { LdnService } from '../../../admin/admin-ldn-services/ldn-services-model/ldn-services.model'; +import { SubmissionCoarNotifyConfig } from './submission-coar-notify.config'; + + +/** + * A service responsible for fetching/sending data from/to the REST API on the ldnservices endpoint + */ +@Injectable() +@dataService(COAR_NOTIFY_WORKSPACEITEM) +export class SectionCoarNotifyWorkspaceitemsDataService extends IdentifiableDataService implements FindAllData, DeleteData, PatchData, CreateData { + createData: CreateDataImpl; + private findAllData: FindAllDataImpl; + private deleteData: DeleteDataImpl; + private patchData: PatchDataImpl; + private comparator: ChangeAnalyzer; + + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + ) { + super('ldnservices', requestService, rdbService, objectCache, halService); + + this.findAllData = new FindAllDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive); + this.deleteData = new DeleteDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive, this.constructIdEndpoint); + this.patchData = new PatchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.comparator, this.responseMsToLive, this.constructIdEndpoint); + this.createData = new CreateDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive); + } + + + create(object: LdnService): Observable> { + return this.createData.create(object); + } + + patch(object: LdnService, operations: Operation[]): Observable> { + return this.patchData.patch(object, operations); + } + + update(object: LdnService): Observable> { + return this.patchData.update(object); + } + + commitUpdates(method?: RestRequestMethod): void { + return this.patchData.commitUpdates(method); + } + + createPatchFromCache(object: LdnService): Observable { + return this.patchData.createPatchFromCache(object); + } + + findAll(options?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig[]): Observable>> { + return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + } + + public delete(objectId: string, copyVirtualMetadata?: string[]): Observable> { + return this.deleteData.delete(objectId, copyVirtualMetadata); + } + + public deleteByHref(href: string, copyVirtualMetadata?: string[]): Observable> { + return this.deleteData.deleteByHref(href, copyVirtualMetadata); + } + + public invoke(serviceName: string, serviceId: string, parameters: ldnServiceConstrain[], files: File[]): Observable> { + const requestId = this.requestService.generateRequestId(); + this.getBrowseEndpoint().pipe( + take(1), + map((endpoint: string) => new URLCombiner(endpoint, serviceName, 'processes', serviceId).toString()), + map((endpoint: string) => { + const body = this.getInvocationFormData(parameters, files); + return new MultipartPostRequest(requestId, endpoint, body); + }) + ).subscribe((request: RestRequest) => this.requestService.send(request)); + + return this.rdbService.buildFromRequestUUID(requestId); + } + + public ldnServiceWithNameExistsAndCanExecute(scriptName: string): Observable { + return this.findById(scriptName).pipe( + getFirstCompletedRemoteData(), + map((rd: RemoteData) => { + return hasValue(rd.payload); + }), + ); + } + + private getInvocationFormData(constrain: ldnServiceConstrain[], files: File[]): FormData { + const form: FormData = new FormData(); + form.set('properties', JSON.stringify(constrain)); + files.forEach((file: File) => { + form.append('file', file); + }); + return form; + } +} diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html new file mode 100644 index 0000000000..4d0d92c714 --- /dev/null +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html @@ -0,0 +1,31 @@ +
+ + + +
diff --git a/src/app/submission/sections/ldn-service/ldn-service.component.html b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.scss similarity index 100% rename from src/app/submission/sections/ldn-service/ldn-service.component.html rename to src/app/submission/sections/section-coar-notify/section-coar-notify.component.scss diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.spec.ts b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.spec.ts new file mode 100644 index 0000000000..c83479284a --- /dev/null +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SubmissionSectionCoarNotifyComponent } from './section-coar-notify.component'; + +describe('LdnServiceComponent', () => { + let component: SubmissionSectionCoarNotifyComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [SubmissionSectionCoarNotifyComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SubmissionSectionCoarNotifyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts new file mode 100644 index 0000000000..992eae0b52 --- /dev/null +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts @@ -0,0 +1,308 @@ +import { ChangeDetectorRef, Component, Inject, ViewChild } from '@angular/core'; +import { DynamicFormControlEvent, DynamicFormControlModel, DynamicFormLayout } from '@ng-dynamic-forms/core'; +import { Observable, Subscription } from 'rxjs'; +import { SectionModelComponent } from '../models/section.model'; +import { renderSectionFor } from '../sections-decorator'; +import { SectionsType } from '../sections-type'; +import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner'; +import { FormComponent } from '../../../shared/form/form.component'; +import { CollectionDataService } from '../../../core/data/collection-data.service'; +import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; +import { SectionFormOperationsService } from '../form/section-form-operations.service'; +import { FormService } from '../../../shared/form/form.service'; +import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder'; +import { SectionsService } from '../sections.service'; +import { SubmissionService } from '../../submission.service'; +import { TranslateService } from '@ngx-translate/core'; +import { SectionDataObject } from '../models/section-data.model'; + +import { hasValue, isNotEmpty } from '../../../shared/empty.util'; + +import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; +import { LdnServicesService } from '../../../admin/admin-ldn-services/ldn-services-data/ldn-services-data.service'; +import { isLoading } from '../../../core/data/request-entry-state.model'; +import { LdnService } from '../../../admin/admin-ldn-services/ldn-services-model/ldn-services.model'; +import { SECTION_COAR_FORM_LAYOUT, SECTION_COAR_FORM_MODEL } from './section-coar-notify-model'; +import { + CoarNotifyConfigDataService +} from './coar-notify-config-data.service'; +import { RemoteData } from '../../../core/data/remote-data'; +import { PaginatedList } from '../../../core/data/paginated-list.model'; +import { SubmissionCoarNotifyConfig } from './submission-coar-notify.config'; +import { FormFieldPreviousValueObject } from '../../../shared/form/builder/models/form-field-previous-value-object'; +import { UntypedFormGroup } from '@angular/forms'; +import { AlertType } from '../../../shared/alert/aletr-type'; + +export interface CoarNotifyDropdownSelector { + ldnService: LdnService; +} + +/** + * This component represents a section that contains the submission section-coar-notify form. + */ +@Component({ + selector: 'ds-submission-section-coar-notify', + templateUrl: './section-coar-notify.component.html', + styleUrls: ['./section-coar-notify.component.scss'] +}) +@renderSectionFor(SectionsType.CoarNotify) + +export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent { + + requestReview: LdnService; + requestEndorsement: LdnService; + requestIngest: LdnService; + + coarNotifyConfigRD$: Observable>>; + + ldnServicesRD$: Observable>>; + + selectedServiceValues: any[] = []; + + /** + * The AlertType enumeration + * @type {AlertType} + */ + public AlertTypeEnum = AlertType; + /** + * The form model + * @type {DynamicFormControlModel[]} + */ + public formModel: DynamicFormControlModel[]; + /** + * The form id + * @type {string} + */ + public formId: string; + /** + * The [[DynamicFormLayout]] object + * @type {DynamicFormLayout} + */ + public formLayout: DynamicFormLayout = SECTION_COAR_FORM_LAYOUT; + /** + * A FormGroup that combines all inputs + */ + formGroup: UntypedFormGroup; + /** + * A boolean representing if div should start collapsed + */ + public isCollapsed = false; + protected readonly AlertType = AlertType; + /** + * The [[JsonPatchOperationPathCombiner]] object + * @type {JsonPatchOperationPathCombiner} + */ + protected pathCombiner: JsonPatchOperationPathCombiner; + /** + * A map representing all field on their way to be removed + * @type {Map} + */ + protected fieldsOnTheirWayToBeRemoved: Map = new Map(); + /** + * The [FormFieldPreviousValueObject] object + * @type {FormFieldPreviousValueObject} + */ + protected previousValue: FormFieldPreviousValueObject = new FormFieldPreviousValueObject(); + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ + protected subs: Subscription[] = []; + protected readonly isLoading = isLoading; + /** + * The FormComponent reference + */ + @ViewChild('formRef') private formRef: FormComponent; + + /** + * Initialize instance variables + * + * @param {ChangeDetectorRef} changeDetectorRef + * @param ldnServicesService + * @param {CollectionDataService} collectionDataService + * @param {FormBuilderService} formBuilderService + * @param {SectionFormOperationsService} formOperationsService + * @param {FormService} formService + * @param {JsonPatchOperationsBuilder} operationsBuilder + * @param {SectionsService} sectionService + * @param {SubmissionService} submissionService + * @param {TranslateService} translateService + * @param {CoarNotifyConfigDataService} coarNotifyConfigDataService + * @param {string} injectedCollectionId + * @param {SectionDataObject} injectedSectionData + * @param {string} injectedSubmissionId + */ + constructor(protected changeDetectorRef: ChangeDetectorRef, + protected ldnServicesService: LdnServicesService, + protected collectionDataService: CollectionDataService, + protected formBuilderService: FormBuilderService, + protected formOperationsService: SectionFormOperationsService, + protected formService: FormService, + protected operationsBuilder: JsonPatchOperationsBuilder, + protected sectionService: SectionsService, + protected submissionService: SubmissionService, + protected translateService: TranslateService, + protected coarNotifyConfigDataService: CoarNotifyConfigDataService, + @Inject('collectionIdProvider') public injectedCollectionId: string, + @Inject('sectionDataProvider') public injectedSectionData: SectionDataObject, + @Inject('submissionIdProvider') public injectedSubmissionId: string) { + super(injectedCollectionId, injectedSectionData, injectedSubmissionId); + } + + /** + * Initialize all instance variables + */ + onSectionInit() { + this.formModel = this.formBuilderService.fromJSON(SECTION_COAR_FORM_MODEL); + this.setCoarNotifyConfig(); + this.fetchLdnServices(); + this.coarNotifyConfigRD$.subscribe(data => { + console.log(data); + }); + this.ldnServicesRD$.subscribe(data => { + console.log(data); + }); + this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionData.id); + } + + /** + * Method called when section is initialized + * Retriev available NotifyConfigs + */ + setCoarNotifyConfig() { + this.coarNotifyConfigRD$ = this.coarNotifyConfigDataService.findAll().pipe( + getFirstCompletedRemoteData()); + } + + /** + * Handle the customEvent (ex. drag-drop move event). + * The customEvent is stored inside event.$event + * @param event + */ + onCustomEvent(event: DynamicFormControlEvent) { + this.formOperationsService.dispatchOperationsFromEvent( + this.pathCombiner, + event, + this.previousValue, + null); + } + + /** + * 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); + if (value) { + this.operationsBuilder.add(this.pathCombiner.getPath(path), value.value.toString(), false, true); + this.sectionService.dispatchRemoveSectionErrors(this.submissionId, this.sectionData.id); + } else { + this.operationsBuilder.remove(this.pathCombiner.getPath(path)); + } + } + + /** + * Method called when a form remove event is fired. + * Dispatch form operations based on changes. + * + * @param event + * the [[DynamicFormControlEvent]] emitted + */ + onRemove(event: DynamicFormControlEvent): void { + const fieldId = this.formBuilderService.getId(event.model); + const fieldIndex = this.formOperationsService.getArrayIndexFromEvent(event); + + // Keep track that this field will be removed + if (this.fieldsOnTheirWayToBeRemoved.has(fieldId)) { + const indexes = this.fieldsOnTheirWayToBeRemoved.get(fieldId); + indexes.push(fieldIndex); + this.fieldsOnTheirWayToBeRemoved.set(fieldId, indexes); + } else { + this.fieldsOnTheirWayToBeRemoved.set(fieldId, [fieldIndex]); + } + + this.formOperationsService.dispatchOperationsFromEvent( + this.pathCombiner, + event, + this.previousValue, + this.hasStoredValue(fieldId, fieldIndex)); + + } + + /** + * 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]) && + !this.isFieldToRemove(fieldId, index); + } else { + return false; + } + } + + /** + * Check if the specified field is on the way to be removed + * + * @param fieldId + * the section data retrieved from the serverù + * @param index + * the section data retrieved from the server + */ + isFieldToRemove(fieldId, index) { + return this.fieldsOnTheirWayToBeRemoved.has(fieldId) && this.fieldsOnTheirWayToBeRemoved.get(fieldId).includes(index); + } + + /** + * 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); + const path = this.formBuilderService.getPath(event.model); + if (this.formBuilderService.hasMappedGroupValue(event.model)) { + this.previousValue.path = path; + this.previousValue.value = this.formOperationsService.getQualdropValueMap(event); + } else if (isNotEmpty(value) && ((typeof value === 'object' && isNotEmpty(value.value)) || (typeof value === 'string'))) { + this.previousValue.path = path; + this.previousValue.value = value; + } + } + + /** + * Unsubscribe from all subscriptions + */ + onSectionDestroy() { + this.subs + .filter((subscription) => hasValue(subscription)) + .forEach((subscription) => subscription.unsubscribe()); + } + + /** + * Method called when section is initialized + * Retriev available NotifyConfigs + */ + fetchLdnServices() { + this.ldnServicesRD$ = this.ldnServicesService.findAll().pipe( + getFirstCompletedRemoteData() + ); + } + + protected getSectionStatus(): Observable { + return undefined; + } + + hasInboundPattern(service: any, patternType: string): boolean { + return service.notifyServiceInboundPatterns.some(pattern => pattern.pattern === patternType); + } +} diff --git a/src/app/submission/sections/section-coar-notify/submission-coar-notify-workspaceitem.model.ts b/src/app/submission/sections/section-coar-notify/submission-coar-notify-workspaceitem.model.ts new file mode 100644 index 0000000000..85374502bc --- /dev/null +++ b/src/app/submission/sections/section-coar-notify/submission-coar-notify-workspaceitem.model.ts @@ -0,0 +1,35 @@ +import { CacheableObject } from '../../../core/cache/cacheable-object.model'; +import { autoserialize, deserialize, deserializeAs, inheritSerialization } from 'cerialize'; + +import { excludeFromEquals } from '../../../core/utilities/equals.decorators'; +import { typedObject } from '../../../core/cache/builders/build-decorators'; +import { COAR_NOTIFY_WORKSPACEITEM } from "./section-coar-notify-service.resource-type"; + + +/** An CoarNotify and its properties. */ +@typedObject +@inheritSerialization(CacheableObject) +export class SubmissionCoarNotifyWorkspaceitemModel extends CacheableObject { + static type = COAR_NOTIFY_WORKSPACEITEM; + + @excludeFromEquals + @autoserialize + endorsement?: number[]; + + @deserializeAs('id') + review?: number[]; + + @autoserialize + ingest?: number[]; + + @deserialize + _links: { + self: { + href: string; + }; + }; + + get self(): string { + return this._links.self.href; + } +} diff --git a/src/app/submission/sections/section-coar-notify/submission-coar-notify.config.ts b/src/app/submission/sections/section-coar-notify/submission-coar-notify.config.ts new file mode 100644 index 0000000000..04973f80c8 --- /dev/null +++ b/src/app/submission/sections/section-coar-notify/submission-coar-notify.config.ts @@ -0,0 +1,39 @@ +import { ResourceType } from '../../../core/shared/resource-type'; +import { CacheableObject } from '../../../core/cache/cacheable-object.model'; +import { autoserialize, deserialize, deserializeAs, inheritSerialization } from 'cerialize'; + +import { excludeFromEquals } from '../../../core/utilities/equals.decorators'; +import { typedObject } from '../../../core/cache/builders/build-decorators'; +import { SUBMISSION_COAR_NOTIFY_CONFIG } from './section-coar-notify-service.resource-type'; + + +/** A SubmissionCoarNotifyConfig and its properties. */ +@typedObject +@inheritSerialization(CacheableObject) +export class SubmissionCoarNotifyConfig extends CacheableObject { + static type = SUBMISSION_COAR_NOTIFY_CONFIG; + + @excludeFromEquals + @autoserialize + type: ResourceType; + + @autoserialize + id: string; + + @deserializeAs('id') + uuid: string; + + @autoserialize + patterns: string[]; + + @deserialize + _links: { + self: { + href: string; + }; + }; + + get self(): string { + return this._links.self.href; + } +} diff --git a/src/app/submission/sections/sections-type.ts b/src/app/submission/sections/sections-type.ts index 40f6f85e0e..5f71d1731d 100644 --- a/src/app/submission/sections/sections-type.ts +++ b/src/app/submission/sections/sections-type.ts @@ -9,5 +9,5 @@ export enum SectionsType { SherpaPolicies = 'sherpaPolicy', Identifiers = 'identifiers', Collection = 'collection', - LdnService = 'ldn-service' + CoarNotify = 'coarnotify' } diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index 8f35bb4420..f4f479e204 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -67,7 +67,11 @@ import { } from './sections/sherpa-policies/metadata-information/metadata-information.component'; import { SectionFormOperationsService } from './sections/form/section-form-operations.service'; import {SubmissionSectionIdentifiersComponent} from './sections/identifiers/section-identifiers.component'; -import { LdnServiceComponent } from './sections/ldn-service/ldn-service.component'; +import { SubmissionSectionCoarNotifyComponent } from './sections/section-coar-notify/section-coar-notify.component'; +import { + CoarNotifyConfigDataService +} from './sections/section-coar-notify/coar-notify-config-data.service'; +import { LdnServicesService } from '../admin/admin-ldn-services/ldn-services-data/ldn-services-data.service'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator @@ -77,7 +81,7 @@ const ENTRY_COMPONENTS = [ SubmissionSectionCcLicensesComponent, SubmissionSectionAccessesComponent, SubmissionSectionSherpaPoliciesComponent, - LdnServiceComponent + SubmissionSectionCoarNotifyComponent ]; const DECLARATIONS = [ @@ -138,6 +142,8 @@ const DECLARATIONS = [ SubmissionAccessesConfigDataService, SectionAccessesService, SectionFormOperationsService, + CoarNotifyConfigDataService, + LdnServicesService ] }) diff --git a/src/app/submission/submission.service.ts b/src/app/submission/submission.service.ts index 9eb8cf110a..7057f78c2f 100644 --- a/src/app/submission/submission.service.ts +++ b/src/app/submission/submission.service.ts @@ -298,6 +298,7 @@ export class SubmissionService { sectionObject.id = sectionId; sectionObject.sectionType = sections[sectionId].sectionType; availableSections.push(sectionObject); + console.log(sectionObject); }); return availableSections; }), From 0682b7b45f14d6ae215c2a4770ee088ef2ed5954 Mon Sep 17 00:00:00 2001 From: Mattia Vianelli Date: Wed, 25 Oct 2023 23:03:24 +0200 Subject: [PATCH 04/14] CST-11045 Dynamically creating dropdowns based on the coarconfig --- .../section-coar-notify.component.html | 38 ++++--------- .../section-coar-notify.component.ts | 57 ++++++++++++------- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html index 4d0d92c714..c9d79293ac 100644 --- a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html @@ -1,31 +1,15 @@
- - +
diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts index 992eae0b52..726f5ec6c4 100644 --- a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts @@ -57,12 +57,11 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent ldnServicesRD$: Observable>>; - selectedServiceValues: any[] = []; - /** - * The AlertType enumeration - * @type {AlertType} - */ + patterns: string[] = []; + selectedServices: { [key: string]: LdnService } = {}; + patternsLoaded = false; + public AlertTypeEnum = AlertType; /** * The form model @@ -171,7 +170,15 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent */ setCoarNotifyConfig() { this.coarNotifyConfigRD$ = this.coarNotifyConfigDataService.findAll().pipe( - getFirstCompletedRemoteData()); + getFirstCompletedRemoteData() + ); + + this.coarNotifyConfigRD$.subscribe((data) => { + if (data.hasSucceeded) { + this.patterns = data.payload.page[0].patterns; + this.patternsLoaded = true; + } + }); } /** @@ -181,10 +188,10 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent */ onCustomEvent(event: DynamicFormControlEvent) { this.formOperationsService.dispatchOperationsFromEvent( - this.pathCombiner, - event, - this.previousValue, - null); + this.pathCombiner, + event, + this.previousValue, + null); } /** @@ -223,10 +230,10 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent } this.formOperationsService.dispatchOperationsFromEvent( - this.pathCombiner, - event, - this.previousValue, - this.hasStoredValue(fieldId, fieldIndex)); + this.pathCombiner, + event, + this.previousValue, + this.hasStoredValue(fieldId, fieldIndex)); } @@ -241,8 +248,8 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent hasStoredValue(fieldId, index): boolean { if (isNotEmpty(this.sectionData.data)) { return this.sectionData.data.hasOwnProperty(fieldId) && - isNotEmpty(this.sectionData.data[fieldId][index]) && - !this.isFieldToRemove(fieldId, index); + isNotEmpty(this.sectionData.data[fieldId][index]) && + !this.isFieldToRemove(fieldId, index); } else { return false; } @@ -284,8 +291,8 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent */ onSectionDestroy() { this.subs - .filter((subscription) => hasValue(subscription)) - .forEach((subscription) => subscription.unsubscribe()); + .filter((subscription) => hasValue(subscription)) + .forEach((subscription) => subscription.unsubscribe()); } /** @@ -293,9 +300,19 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent * Retriev available NotifyConfigs */ fetchLdnServices() { - this.ldnServicesRD$ = this.ldnServicesService.findAll().pipe( + this.ldnServicesRD$ = this.ldnServicesService.findAll().pipe( getFirstCompletedRemoteData() - ); + ); + + this.ldnServicesRD$.subscribe((data) => { + if (this.patternsLoaded) { + this.patterns.forEach((pattern) => { + this.selectedServices[pattern] = data.payload.page.find((service) => + this.hasInboundPattern(service, `Request ${pattern}`) + ); + }); + } + }); } protected getSectionStatus(): Observable { From 5ed9f46096404e50e20a9c096d3f4c3225a95bbc Mon Sep 17 00:00:00 2001 From: Mattia Vianelli Date: Thu, 26 Oct 2023 18:47:25 +0200 Subject: [PATCH 05/14] CST-11045 Improved css and remove console log and interpolation, added small notify regarding service compatibility --- .../ldn-services-data.service.ts | 4 ++ .../form/submission-form.component.html | 5 --- .../section-container.component.html | 2 +- .../section-coar-notify.component.html | 42 ++++++++++++------ .../section-coar-notify.component.scss | 15 +++++++ .../section-coar-notify.component.ts | 38 ++++++++++++++-- src/assets/i18n/en.json5 | 2 + src/assets/images/notify_logo.png | Bin 0 -> 19269 bytes 8 files changed, 85 insertions(+), 23 deletions(-) create mode 100644 src/assets/images/notify_logo.png diff --git a/src/app/admin/admin-ldn-services/ldn-services-data/ldn-services-data.service.ts b/src/app/admin/admin-ldn-services/ldn-services-data/ldn-services-data.service.ts index cdd259447a..5ffab2000b 100644 --- a/src/app/admin/admin-ldn-services/ldn-services-data/ldn-services-data.service.ts +++ b/src/app/admin/admin-ldn-services/ldn-services-data/ldn-services-data.service.ts @@ -84,6 +84,10 @@ export class LdnServicesService extends IdentifiableDataService impl return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); } + /*findByPattern(options?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig[]): Observable>> { + return this.findAllData.find + }*/ + public delete(objectId: string, copyVirtualMetadata?: string[]): Observable> { return this.deleteData.delete(objectId, copyVirtualMetadata); } diff --git a/src/app/submission/form/submission-form.component.html b/src/app/submission/form/submission-form.component.html index c79364e2af..8fa1760d8f 100644 --- a/src/app/submission/form/submission-form.component.html +++ b/src/app/submission/form/submission-form.component.html @@ -1,7 +1,6 @@
-

ds-submission-upload-files

@@ -10,7 +9,6 @@
-

ds-submission-form-collection

-

ds-submission-form-section-add

@@ -32,7 +29,6 @@
-

ds-submission-section-container

@@ -44,7 +40,6 @@
diff --git a/src/app/submission/sections/container/section-container.component.html b/src/app/submission/sections/container/section-container.component.html index f39ba72ffa..99bcec168f 100644 --- a/src/app/submission/sections/container/section-container.component.html +++ b/src/app/submission/sections/container/section-container.component.html @@ -42,7 +42,7 @@
-
aaaaaaaaa +
diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html index c9d79293ac..75318b937f 100644 --- a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html @@ -1,15 +1,29 @@ -
- - +
+ +
+ +
+ + {{selectedServices[pattern]?.name}} +
+ +
+
+ Select a service for {{ pattern }} of this item + + Coar-Notify-Pattern + + The selected service is compatible with the item according to its current status. {{ selectedServices[pattern].name }}: {{ selectedServices[pattern].description }} + + + {{ 'ldn-new-service.form.label.addPattern' | translate }} +
+
diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.scss b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.scss index e69de29bb2..c06ef5951c 100644 --- a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.scss +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.scss @@ -0,0 +1,15 @@ +.add-pattern-link { + color: #0048ff; + cursor: pointer; +} +.ds-alert-coar{ + position: relative +} + +.coar-img-submission{ + position: absolute; top: 0; left: 0; width: 50px; height: 50px; +} + +.ds-alert-box{ + +} diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts index 726f5ec6c4..39c8c9753d 100644 --- a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts @@ -61,6 +61,8 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent patterns: string[] = []; selectedServices: { [key: string]: LdnService } = {}; patternsLoaded = false; + selectedService: any; + public AlertTypeEnum = AlertType; /** @@ -181,6 +183,18 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent }); } + + addService() { + this.patterns.push(''); + } + + + removeService(index: number) { + if (index >= 0 && index < this.patterns.length) { + this.patterns.splice(index, 1); + } + } + /** * Handle the customEvent (ex. drag-drop move event). * The customEvent is stored inside event.$event @@ -301,25 +315,43 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent */ fetchLdnServices() { this.ldnServicesRD$ = this.ldnServicesService.findAll().pipe( - getFirstCompletedRemoteData() + getFirstCompletedRemoteData() ); this.ldnServicesRD$.subscribe((data) => { if (this.patternsLoaded) { this.patterns.forEach((pattern) => { this.selectedServices[pattern] = data.payload.page.find((service) => - this.hasInboundPattern(service, `Request ${pattern}`) + this.hasInboundPattern(service, pattern) ); + + //console.log('Pattern:', pattern); + //console.log('Service:', this.selectedServices[pattern]); + + if (this.selectedServices[pattern]) { + //console.log('Name:', this.selectedServices[pattern].name); + //console.log('Description:', this.selectedServices[pattern].description); + } }); } }); } + protected getSectionStatus(): Observable { return undefined; } hasInboundPattern(service: any, patternType: string): boolean { - return service.notifyServiceInboundPatterns.some(pattern => pattern.pattern === patternType); + //console.log('Pattern Type:', patternType); + //console.log('Inbound Patterns in Service:', service.notifyServiceInboundPatterns); + + const hasPattern = service.notifyServiceInboundPatterns.some((pattern: { pattern: string; }) => { + //console.log('Checking Pattern:', pattern.pattern); + return pattern.pattern === patternType; + }); + + //console.log('Has Inbound Pattern:', hasPattern); + return hasPattern; } } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 40f0150022..58453db518 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1982,6 +1982,8 @@ "info.coar-notify.breadcrumbs": "Notify Support", + "submission.sections.notify.info": "The selected service is compatible with the item according to its current status. {{ service.name }}: {{ service.description }}", + "info.feedback.head": "Feedback", "info.feedback.title": "Feedback", diff --git a/src/assets/images/notify_logo.png b/src/assets/images/notify_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0350c641df9cb51c2c6a81c0a6badb96adcd5c87 GIT binary patch literal 19269 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 Date: Fri, 27 Oct 2023 16:23:01 +0200 Subject: [PATCH 06/14] CST-11045 Added jsona5 text for coar submission section --- src/assets/i18n/en.json5 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 58453db518..0a68ae3934 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4762,6 +4762,8 @@ "submission.sections.submit.progressbar.sherpaPolicies": "Publisher open access policy information", + "submission.sections.submit.progressbar.coarnotify": "COAR Notify", + "submission.sections.sherpa-policy.title-empty": "No publisher policy information available. If your work has an associated ISSN, please enter it above to see any related publisher open access policies.", "submission.sections.status.errors.title": "Errors", From 92d66c911a303ad0054f271e867b5fe7c0c11f31 Mon Sep 17 00:00:00 2001 From: Mattia Vianelli Date: Fri, 27 Oct 2023 16:24:17 +0200 Subject: [PATCH 07/14] CST-11045 CSS refactor + dropdown data correctly populated --- .../section-coar-notify.component.html | 52 ++++++--- .../section-coar-notify.component.scss | 15 ++- .../section-coar-notify.component.ts | 109 +++++++++++------- src/app/submission/submission.module.ts | 33 +++--- 4 files changed, 131 insertions(+), 78 deletions(-) diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html index 75318b937f..f2c7fd0947 100644 --- a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html @@ -1,27 +1,41 @@
- -
- - {{selectedServices[pattern]?.name}} -
- + +
+ +
+ +
+
+ Select a service for {{ pattern }} of this item +
+
+
+ Coar-Notify-Pattern + +
The selected service is compatible with the item according to its current status.
+
+
+ + +
{{ selectedServices[pattern].name }}: {{ selectedServices[pattern].description }}
- Select a service for {{ pattern }} of this item - - Coar-Notify-Pattern - - The selected service is compatible with the item according to its current status. {{ selectedServices[pattern].name }}: {{ selectedServices[pattern].description }} - - + +
{{ 'ldn-new-service.form.label.addPattern' | translate }}
diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.scss b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.scss index c06ef5951c..f1504fdff6 100644 --- a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.scss +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.scss @@ -2,14 +2,21 @@ color: #0048ff; cursor: pointer; } -.ds-alert-coar{ + +.ds-alert-coar { position: relative } -.coar-img-submission{ - position: absolute; top: 0; left: 0; width: 50px; height: 50px; +.coar-img-submission { + max-height: var(--ds-header-logo-height); } -.ds-alert-box{ +.icon-check { + color: rgba(6, 68, 6, 0.42); + font-size: var(--ds-header-logo-height); +} + + +.ds-alert-box { } diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts index 39c8c9753d..3cf040acd9 100644 --- a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts @@ -23,15 +23,14 @@ import { LdnServicesService } from '../../../admin/admin-ldn-services/ldn-servic import { isLoading } from '../../../core/data/request-entry-state.model'; import { LdnService } from '../../../admin/admin-ldn-services/ldn-services-model/ldn-services.model'; import { SECTION_COAR_FORM_LAYOUT, SECTION_COAR_FORM_MODEL } from './section-coar-notify-model'; -import { - CoarNotifyConfigDataService -} from './coar-notify-config-data.service'; +import { CoarNotifyConfigDataService } from './coar-notify-config-data.service'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list.model'; import { SubmissionCoarNotifyConfig } from './submission-coar-notify.config'; import { FormFieldPreviousValueObject } from '../../../shared/form/builder/models/form-field-previous-value-object'; import { UntypedFormGroup } from '@angular/forms'; import { AlertType } from '../../../shared/alert/aletr-type'; +import { filter, map, take } from "rxjs/operators"; export interface CoarNotifyDropdownSelector { ldnService: LdnService; @@ -60,8 +59,11 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent patterns: string[] = []; selectedServices: { [key: string]: LdnService } = {}; + patternServices: { [key: string]: LdnService } = {}; + patternsLoaded = false; - selectedService: any; + patternObservables: Observable>[]>; + public AlertTypeEnum = AlertType; @@ -172,7 +174,7 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent */ setCoarNotifyConfig() { this.coarNotifyConfigRD$ = this.coarNotifyConfigDataService.findAll().pipe( - getFirstCompletedRemoteData() + getFirstCompletedRemoteData() ); this.coarNotifyConfigRD$.subscribe((data) => { @@ -202,10 +204,10 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent */ onCustomEvent(event: DynamicFormControlEvent) { this.formOperationsService.dispatchOperationsFromEvent( - this.pathCombiner, - event, - this.previousValue, - null); + this.pathCombiner, + event, + this.previousValue, + null); } /** @@ -244,10 +246,10 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent } this.formOperationsService.dispatchOperationsFromEvent( - this.pathCombiner, - event, - this.previousValue, - this.hasStoredValue(fieldId, fieldIndex)); + this.pathCombiner, + event, + this.previousValue, + this.hasStoredValue(fieldId, fieldIndex)); } @@ -262,8 +264,8 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent hasStoredValue(fieldId, index): boolean { if (isNotEmpty(this.sectionData.data)) { return this.sectionData.data.hasOwnProperty(fieldId) && - isNotEmpty(this.sectionData.data[fieldId][index]) && - !this.isFieldToRemove(fieldId, index); + isNotEmpty(this.sectionData.data[fieldId][index]) && + !this.isFieldToRemove(fieldId, index); } else { return false; } @@ -305,8 +307,8 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent */ onSectionDestroy() { this.subs - .filter((subscription) => hasValue(subscription)) - .forEach((subscription) => subscription.unsubscribe()); + .filter((subscription) => hasValue(subscription)) + .forEach((subscription) => subscription.unsubscribe()); } /** @@ -314,44 +316,73 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent * Retriev available NotifyConfigs */ fetchLdnServices() { - this.ldnServicesRD$ = this.ldnServicesService.findAll().pipe( - getFirstCompletedRemoteData() - ); + if (!this.ldnServicesRD$) { + this.ldnServicesRD$ = this.ldnServicesService.findAll().pipe( + getFirstCompletedRemoteData() + ); + } this.ldnServicesRD$.subscribe((data) => { if (this.patternsLoaded) { this.patterns.forEach((pattern) => { - this.selectedServices[pattern] = data.payload.page.find((service) => - this.hasInboundPattern(service, pattern) - ); + const servicesWithPattern = this.getServicesWithPattern(pattern, data?.payload?.page); - //console.log('Pattern:', pattern); - //console.log('Service:', this.selectedServices[pattern]); + if (servicesWithPattern.length > 0) { + this.selectedServices[pattern] = servicesWithPattern[0]; + } + + console.log('Pattern:', pattern); + console.log('Service:', this.selectedServices[pattern]); if (this.selectedServices[pattern]) { - //console.log('Name:', this.selectedServices[pattern].name); - //console.log('Description:', this.selectedServices[pattern].description); + console.log('Name:', this.selectedServices[pattern].name); + console.log('Description:', this.selectedServices[pattern].description); } }); } }); } + getServicesWithPattern(pattern: string, services: LdnService[] | null): LdnService[] { + if (services) { + return services.filter((service) => this.hasInboundPattern(service, pattern)); + } + return []; + } + + + filterServices(pattern: string): LdnService[] { + let ldnServices: LdnService[] = []; + + this.ldnServicesRD$.pipe( + filter((rd) => rd.hasSucceeded), + map((rd) => rd.payload.page) + ).subscribe((services) => { + ldnServices = services.filter((service) => this.hasInboundPattern(service, pattern)); + }); + + return ldnServices; + } + + + + + + hasInboundPattern(service: any, patternType: string): boolean { + console.log('Pattern Type:', patternType); + console.log('Inbound Patterns in Service:', service.notifyServiceInboundPatterns); + + const hasPattern = service.notifyServiceInboundPatterns.some((pattern: { pattern: string; }) => { + console.log('Checking Pattern:', pattern.pattern); + return pattern.pattern === patternType; + }); + + console.log('Has Inbound Pattern:', hasPattern); + return hasPattern; + } protected getSectionStatus(): Observable { return undefined; } - hasInboundPattern(service: any, patternType: string): boolean { - //console.log('Pattern Type:', patternType); - //console.log('Inbound Patterns in Service:', service.notifyServiceInboundPatterns); - - const hasPattern = service.notifyServiceInboundPatterns.some((pattern: { pattern: string; }) => { - //console.log('Checking Pattern:', pattern.pattern); - return pattern.pattern === patternType; - }); - - //console.log('Has Inbound Pattern:', hasPattern); - return hasPattern; - } } diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index f4f479e204..d839565f8d 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -10,7 +10,7 @@ import { SubmissionFormFooterComponent } from './form/footer/submission-form-foo import { SubmissionFormComponent } from './form/submission-form.component'; import { SubmissionFormSectionAddComponent } from './form/section-add/submission-form-section-add.component'; import { SubmissionSectionContainerComponent } from './sections/container/section-container.component'; -import { CommonModule } from '@angular/common'; +import { CommonModule, NgOptimizedImage } from '@angular/common'; import { Action, StoreConfig, StoreModule } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; import { submissionReducers, SubmissionState } from './submission.reducers'; @@ -115,21 +115,22 @@ const DECLARATIONS = [ ]; @NgModule({ - imports: [ - CommonModule, - CoreModule.forRoot(), - SharedModule, - StoreModule.forFeature('submission', submissionReducers, storeModuleConfig as StoreConfig), - EffectsModule.forFeature(), - EffectsModule.forFeature(submissionEffects), - JournalEntitiesModule.withEntryComponents(), - ResearchEntitiesModule.withEntryComponents(), - FormModule, - NgbModalModule, - NgbCollapseModule, - NgbAccordionModule, - UploadModule, - ], + imports: [ + CommonModule, + CoreModule.forRoot(), + SharedModule, + StoreModule.forFeature('submission', submissionReducers, storeModuleConfig as StoreConfig), + EffectsModule.forFeature(), + EffectsModule.forFeature(submissionEffects), + JournalEntitiesModule.withEntryComponents(), + ResearchEntitiesModule.withEntryComponents(), + FormModule, + NgbModalModule, + NgbCollapseModule, + NgbAccordionModule, + UploadModule, + NgOptimizedImage, + ], declarations: DECLARATIONS, exports: [ ...DECLARATIONS, From b927761df3cfbe847d1d33ffb715f334e7ec0792 Mon Sep 17 00:00:00 2001 From: Mattia Vianelli Date: Mon, 30 Oct 2023 15:33:12 +0100 Subject: [PATCH 08/14] CST-11045 Dropdown behavior improved, working on multiple dropdowns --- .../section-coar-notify.component.html | 15 ++-- .../section-coar-notify.component.ts | 75 ++++++------------- 2 files changed, 31 insertions(+), 59 deletions(-) diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html index f2c7fd0947..2ce46d0520 100644 --- a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html @@ -1,10 +1,11 @@
- +
- + + @@ -14,9 +15,9 @@
- Select a service for {{ pattern }} of this item -
-
+ Select a service for {{ pattern }} of this item +
+
Coar-Notify-Pattern @@ -27,7 +28,7 @@ -
{{ selectedServices[pattern].name }}: {{ selectedServices[pattern].description }}
+
{{ selectedServicesByPattern[pattern].name }}: {{ selectedServicesByPattern[pattern].description }}