[CST-11045] Handle coar-notify section errors

This commit is contained in:
Alisa Ismailati
2023-11-02 12:48:04 +01:00
parent 5dcd2d0ff7
commit 83761f1697
7 changed files with 127 additions and 167 deletions

View File

@@ -45,17 +45,17 @@ export const notifyPatterns = [
category: 'Announcements' category: 'Announcements'
}, },
{ {
name: 'Request Endorsement', name: 'endorsement',
description: 'This pattern is used to request endorsement of a resource owned by the origin system.', description: 'This pattern is used to request endorsement of a resource owned by the origin system.',
category: 'Requests' category: 'Requests'
}, },
{ {
name: 'Request Ingest', name: 'ingest',
description: 'This pattern is used to request that the target system ingest a resource.', description: 'This pattern is used to request that the target system ingest a resource.',
category: 'Requests' category: 'Requests'
}, },
{ {
name: 'Request Review', name: 'review',
description: 'This pattern is used to request a review of a resource owned by the origin system.', description: 'This pattern is used to request a review of a resource owned by the origin system.',
category: 'Requests' category: 'Requests'
}, },

View File

@@ -1,74 +0,0 @@
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',
}
];

View File

@@ -1,7 +1,7 @@
<div> <div class="container-fluid">
<ng-container> <ng-container *ngIf="patterns.length > 0">
<div *ngFor="let pattern of patterns; let i = index" class="col mt-3"> <div *ngFor="let pattern of patterns; let i = index" class="col">
<label class="mt-2 row" <label class="row col-form-label"
> >
{{'submission.section.section-coar-notify.control.label' | translate : {pattern : pattern} }} {{'submission.section.section-coar-notify.control.label' | translate : {pattern : pattern} }}
</label </label
@@ -21,6 +21,7 @@
type="text" type="text"
[readonly]="true" [readonly]="true"
ngbDropdownAnchor ngbDropdownAnchor
[ngClass]="{'border-danger': (getShownSectionErrors$(pattern, serviceIndex) | async)?.length > 0}"
class="form-control w-100 scrollable-dropdown-input" class="form-control w-100 scrollable-dropdown-input"
[value]="ldnServiceByPattern[pattern][serviceIndex]?.name" [value]="ldnServiceByPattern[pattern][serviceIndex]?.name"
(click)="myDropdown.open()" (click)="myDropdown.open()"
@@ -68,17 +69,6 @@
</div> </div>
</div> </div>
</div> </div>
<!-- TODO: NEXT version
<div
class="col-sm-1"
>
<button
(click)="removeService(pattern, serviceIndex)"
class="btn btn-outline-dark trash-button"
>
<i class="fas fa-trash"></i>
</button>
</div> -->
</div> </div>
<small <small
class="row text-muted" class="row text-muted"
@@ -86,6 +76,11 @@
> >
{{'submission.section.section-coar-notify.small.notification' | translate : {pattern : pattern} }} {{'submission.section.section-coar-notify.small.notification' | translate : {pattern : pattern} }}
</small> </small>
<ng-container *ngIf="(getShownSectionErrors$(pattern, serviceIndex) | async)?.length > 0">
<small class="row text-danger" *ngFor="let error of (getShownSectionErrors$(pattern, serviceIndex) | async)">
{{ error.message | translate}}
</small>
</ng-container>
<div <div
class="row mt-1" class="row mt-1"
*ngIf="ldnServiceByPattern[pattern][serviceIndex]" *ngIf="ldnServiceByPattern[pattern][serviceIndex]"
@@ -94,19 +89,24 @@
class="alert alert-info w-100 d-flex align-items-center flex-row" class="alert alert-info w-100 d-flex align-items-center flex-row"
> >
<i class="fa-solid fa-circle-info fa-xl ml-2"></i> <i class="fa-solid fa-circle-info fa-xl ml-2"></i>
<div class="mt-1 ml-4"> <div class="ml-4">
<b>{{ 'submission.section.section-coar-notify.selection.description' | translate }}</b> <div>{{ 'submission.section.section-coar-notify.selection.description' | translate }}</div>
<br /> <div *ngIf="ldnServiceByPattern[pattern][serviceIndex]?.description; else noDesc">
{{ ldnServiceByPattern[pattern][serviceIndex].description }} {{ ldnServiceByPattern[pattern][serviceIndex].description }}
</div> </div>
<ng-template #noDesc>
<span class="text-muted">
{{ 'submission.section.section-coar-notify.selection.no-description' | translate }}
</span>
</ng-template>
</div> </div>
</div> </div>
<div class="row mt-1" *ngIf="sectionData.errorsToShow.length > 0"> </div>
<!--TODO: get error message and display here --> <div class="row" *ngIf="(getShownSectionErrors$(pattern, serviceIndex) | async)?.length > 0">
<div <div
class="alert alert-danger w-100 d-flex align-items-center flex-row" class="alert alert-danger w-100 d-flex align-items-center flex-row"
> >
<div class="mt-1 ml-4"> <div class="ml-4">
<span> <span>
{{ 'submission.section.section-coar-notify.notification.error' | translate }} {{ 'submission.section.section-coar-notify.notification.error' | translate }}
</span> </span>
@@ -114,12 +114,11 @@
</div> </div>
</div> </div>
</div> </div>
<!-- TODO: NEXT version
<div class="row mt-1">
<span (click)="addService(pattern, newService)" class="ds-form-add-more btn btn-link mb-2">
{{ 'ldn-new-service.form.label.addPattern' | translate }}
</span>
</div> -->
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="patterns.length === 0">
<p>
{{'submission.section.section-coar-notify.info.no-pattern' | translate }}
</p>
</ng-container>
</div> </div>

View File

@@ -1,3 +1,4 @@
// Getting styles for NgbDropdown
@import '../../../shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.scss'; @import '../../../shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.scss';
@import '../../../shared/form/form.component.scss'; @import '../../../shared/form/form.component.scss';

View File

@@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SubmissionSectionCoarNotifyComponent } from './section-coar-notify.component'; import { SubmissionSectionCoarNotifyComponent } from './section-coar-notify.component';
describe('LdnServiceComponent', () => { fdescribe('LdnServiceComponent', () => {
let component: SubmissionSectionCoarNotifyComponent; let component: SubmissionSectionCoarNotifyComponent;
let fixture: ComponentFixture<SubmissionSectionCoarNotifyComponent>; let fixture: ComponentFixture<SubmissionSectionCoarNotifyComponent>;

View File

@@ -1,5 +1,5 @@
import { ChangeDetectorRef, Component, Inject } from '@angular/core'; import { ChangeDetectorRef, Component, Inject } from '@angular/core';
import { Observable, Subscription, of } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import { SectionModelComponent } from '../models/section.model'; import { SectionModelComponent } from '../models/section.model';
import { renderSectionFor } from '../sections-decorator'; import { renderSectionFor } from '../sections-decorator';
import { SectionsType } from '../sections-type'; import { SectionsType } from '../sections-type';
@@ -9,18 +9,15 @@ import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/jso
import { SectionsService } from '../sections.service'; import { SectionsService } from '../sections.service';
import { SectionDataObject } from '../models/section-data.model'; import { SectionDataObject } from '../models/section-data.model';
import { hasNoValue, hasValue, isNotEmpty } from '../../../shared/empty.util'; import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../../shared/empty.util';
import { getFirstCompletedRemoteData, getPaginatedListPayload, getRemoteDataPayload } from '../../../core/shared/operators'; import { getFirstCompletedRemoteData, getPaginatedListPayload, getRemoteDataPayload } from '../../../core/shared/operators';
import { LdnServicesService } from '../../../admin/admin-ldn-services/ldn-services-data/ldn-services-data.service'; 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 { LdnService } from '../../../admin/admin-ldn-services/ldn-services-model/ldn-services.model';
import { CoarNotifyConfigDataService } from './coar-notify-config-data.service'; import { CoarNotifyConfigDataService } from './coar-notify-config-data.service';
import { filter, map, tap } from 'rxjs/operators'; import { filter, map, take, tap } from 'rxjs/operators';
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'; import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap';
import { SubmissionSectionError } from '../../objects/submission-section-error.model';
export interface CoarNotifyDropdownSelector {
ldnService: LdnService;
}
/** /**
* This component represents a section that contains the submission section-coar-notify form. * This component represents a section that contains the submission section-coar-notify form.
@@ -34,7 +31,14 @@ export interface CoarNotifyDropdownSelector {
@renderSectionFor(SectionsType.CoarNotify) @renderSectionFor(SectionsType.CoarNotify)
export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent { export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent {
/**
* Contains an array of string patterns.
*/
patterns: string[] = []; patterns: string[] = [];
/**
* An object that maps string keys to arrays of LdnService objects.
* Used to store LdnService objects by pattern.
*/
ldnServiceByPattern: { [key: string]: LdnService[] } = {}; ldnServiceByPattern: { [key: string]: LdnService[] } = {};
/** /**
* A map representing all services for each pattern * A map representing all services for each pattern
@@ -49,8 +53,6 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent
*/ */
previousServices: { [key: string]: {[key: number]: number} } = {}; previousServices: { [key: string]: {[key: number]: number} } = {};
private _ldnServicesPerPattern: Map<string, LdnService[]> = new Map();
/** /**
* The [[JsonPatchOperationPathCombiner]] object * The [[JsonPatchOperationPathCombiner]] object
* @type {JsonPatchOperationPathCombiner} * @type {JsonPatchOperationPathCombiner}
@@ -84,6 +86,7 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent
*/ */
onSectionInit() { onSectionInit() {
this.setCoarNotifyConfig(); this.setCoarNotifyConfig();
this.getSectionServerErrorsAndSetErrorsToDisplay();
this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionData.id); this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionData.id);
} }
@@ -162,7 +165,6 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent
this.filterServices(pattern) this.filterServices(pattern)
.subscribe((services: LdnService[]) => { .subscribe((services: LdnService[]) => {
const selectedServices = services.filter((service) => { const selectedServices = services.filter((service) => {
this._ldnServicesPerPattern.set(pattern, services);
const selection = (this.sectionData.data[pattern] as LdnService[]).find((s: LdnService) => s.id === service.id); const selection = (this.sectionData.data[pattern] as LdnService[]).find((s: LdnService) => s.id === service.id);
this.addService(pattern, selection); this.addService(pattern, selection);
return this.sectionData.data[pattern].includes(service.id); return this.sectionData.data[pattern].includes(service.id);
@@ -177,6 +179,11 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent
}); });
} }
/**
* Adds a new service to the selected services for the given pattern.
* @param pattern - The pattern to add the new service to.
* @param newService - The new service to add.
*/
addService(pattern: string, newService: LdnService) { addService(pattern: string, newService: LdnService) {
// Your logic to add a new service to the selected services for the pattern // Your logic to add a new service to the selected services for the pattern
// Example: Push the newService to the array corresponding to the pattern // Example: Push the newService to the array corresponding to the pattern
@@ -186,6 +193,10 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent
this.ldnServiceByPattern[pattern].push(newService); this.ldnServiceByPattern[pattern].push(newService);
} }
/**
* Removes the service at the specified index from the array corresponding to the pattern.
* (part of next phase of implementation)
*/
removeService(pattern: string, serviceIndex: number) { removeService(pattern: string, serviceIndex: number) {
if (this.ldnServiceByPattern[pattern]) { if (this.ldnServiceByPattern[pattern]) {
// Remove the service at the specified index from the array // Remove the service at the specified index from the array
@@ -193,45 +204,6 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent
} }
} }
/**
* 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);
}
/**
* Unsubscribe from all subscriptions
*/
onSectionDestroy() {
this.subs
.filter((subscription) => hasValue(subscription))
.forEach((subscription) => subscription.unsubscribe());
}
/** /**
* Method called when dropdowns for the section are initialized * Method called when dropdowns for the section are initialized
* Retrieve services with corresponding patterns to the dropdowns. * Retrieve services with corresponding patterns to the dropdowns.
@@ -252,15 +224,71 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent
); );
} }
/**
* Checks if the given service has the specified inbound pattern type.
* @param service - The service to check.
* @param patternType - The inbound pattern type to look for.
* @returns True if the service has the specified inbound pattern type, false otherwise.
*/
hasInboundPattern(service: any, patternType: string): boolean { hasInboundPattern(service: any, patternType: string): boolean {
return service.notifyServiceInboundPatterns.some((pattern: { pattern: string }) => { return service.notifyServiceInboundPatterns.some((pattern: { pattern: string }) => {
return pattern.pattern === patternType; return pattern.pattern === patternType;
}); });
} }
protected getSectionStatus(): Observable<boolean> { /**
// TODO: check if section is valid * Retrieves server errors for the current section and sets them to display.
return of(true); * @returns An Observable that emits the validation errors for the current section.
*/
private getSectionServerErrorsAndSetErrorsToDisplay() {
this.subs.push(
this.sectionService.getSectionServerErrors(this.submissionId, this.sectionData.id).pipe(
take(1),
filter((validationErrors) => isNotEmpty(validationErrors)),
).subscribe((validationErrors: SubmissionSectionError[]) => {
if (isNotEmpty(validationErrors)) {
validationErrors.forEach((error) => {
this.sectionService.setSectionError(this.submissionId, this.sectionData.id, error);
});
}
}));
} }
/**
* Returns an observable of the errors for the current section that match the given pattern and index.
* @param pattern - The pattern to match against the error paths.
* @param index - The index to match against the error paths.
* @returns An observable of the errors for the current section that match the given pattern and index.
*/
public getShownSectionErrors$(pattern: string, index: number): Observable<SubmissionSectionError[]> {
return this.sectionService.getShownSectionErrors(this.submissionId, this.sectionData.id, this.sectionData.sectionType)
.pipe(
take(1),
filter((validationErrors) => isNotEmpty(validationErrors)),
map((validationErrors: SubmissionSectionError[]) => {
return validationErrors.filter((error) => {
const path = `${pattern}/${index}`;
return error.path.includes(path);
});
})
);
}
/**
* @returns An observable that emits a boolean indicating whether the section has any server errors or not.
*/
protected getSectionStatus(): Observable<boolean> {
return this.sectionService.getSectionServerErrors(this.submissionId, this.sectionData.id).pipe(
map((validationErrors) => isEmpty(validationErrors)
));
}
/**
* Unsubscribe from all subscriptions
*/
onSectionDestroy() {
this.subs
.filter((subscription) => hasValue(subscription))
.forEach((subscription) => subscription.unsubscribe());
}
} }

View File

@@ -5020,8 +5020,14 @@
"submission.section.section-coar-notify.selection.description": "Selected service's description:", "submission.section.section-coar-notify.selection.description": "Selected service's description:",
"submission.section.section-coar-notify.selection.no-description": "No further information is available",
"submission.section.section-coar-notify.notification.error": "The selected service is not suitable for the current item. Please check the description for details about which record can be managed by this service.", "submission.section.section-coar-notify.notification.error": "The selected service is not suitable for the current item. Please check the description for details about which record can be managed by this service.",
"submission.section.section-coar-notify.info.no-pattern": "No patterns found in the submission.",
"error.validation.coarnotify.invalidfilter": "Invalid filter, try to select another service or none.",
"submitter.empty": "N/A", "submitter.empty": "N/A",
"subscriptions.title": "Subscriptions", "subscriptions.title": "Subscriptions",