Skip to content
Snippets Groups Projects
Verified Commit 8d2314f8 authored by Sebastian Bergandy's avatar Sebastian Bergandy :keyboard:
Browse files

OZG-7473 add validation

Sub task: OZG-7889
parent 9fdf95eb
Branches
Tags
1 merge request!104Administration: Neu hinzugefügte Felder für Statistik speichern
Showing
with 70 additions and 66 deletions
......@@ -5,23 +5,26 @@
<form class="form flex-col" [formGroup]="formService.form" class="flex flex-col gap-2">
<ods-text-editor
[formControlName]="StatistikFieldsFormService.FIELD_NAME"
label="Name *"
label="Name"
placeholder=""
isRequired="true"
data-test-id="statistik-name-text-editor"
dataTestId="statistik-name"
></ods-text-editor>
<div [formGroupName]="StatistikFieldsFormService.FIELD_FORM_IDENTIFIER" class="flex flex-col gap-4">
<ods-text-editor
[formControlName]="StatistikFieldsFormService.FIELD_FORM_ENGINE_NAME"
label="Formengine *"
label="Formengine"
placeholder="Tragen Sie hier die Formengine des Formulars ein."
isRequired="true"
data-test-id="form-engine-name"
dataTestId="form-engine-name"
></ods-text-editor>
<ods-text-editor
[formControlName]="StatistikFieldsFormService.FIELD_FORM_ID"
label="FormID *"
label="FormID"
placeholder="Tragen Sie hier die FormID des Formulars ein."
isRequired="true"
data-test-id="form-id"
dataTestId="form-id"
></ods-text-editor>
......
......@@ -18,12 +18,12 @@ import { StatistikFieldsFormService } from './statistik-fields.formservice';
ButtonComponent,
PlusIconComponent,
ReactiveFormsModule,
TextEditorComponent,
AdminSaveButtonComponent,
AdminCancelButtonComponent,
StatistikFieldsMappingsFormComponent,
SpinnerComponent,
AsyncPipe,
TextEditorComponent,
],
providers: [{ provide: ADMIN_FORMSERVICE, useClass: StatistikFieldsFormService }],
})
......
......@@ -19,15 +19,17 @@
<ods-text-editor
class="flex-1"
formControlName="sourcePath"
label="Pfad *"
label="Pfad"
placeholder="Tragen Sie hier den gesamten Pfad des Datenfeldes ein, das Sie auswerten möchten."
isRequired="true"
[dataTestId]="'source-mapping-field-' + index"
[attr.data-test-id]="'source-mapping-field-' + index"
></ods-text-editor>
<ods-text-editor
class="flex-1"
formControlName="targetPath"
label="Zielfeld *"
label="Zielfeld"
isRequired="true"
placeholder="Tragen Sie hier den gesamten Pfad des Datenfeldes ein, das Sie auswerten möchten."
[dataTestId]="'target-mapping-field-' + index"
[attr.data-test-id]="'target-mapping-field-' + index"
......
......@@ -35,7 +35,7 @@ export class StatistikFieldsFormService extends AbstractFormService<AggregationM
}
protected getPathPrefix(): string {
return 'settingBody';
return EMPTY_STRING;
}
public addMapping(): void {
......
......@@ -29,13 +29,16 @@ import { cold } from 'jest-marbles';
import { DummyLinkRel, DummyListLinkRel } from 'libs/tech-shared/test/dummy';
import { createDummyListResource, createDummyResource, createFilledDummyListResource } from 'libs/tech-shared/test/resource';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { multipleCold, singleCold, singleHot } from '../../../test/marbles';
import { multipleCold, singleCold, singleColdCompleted, singleHot } from '../../../test/marbles';
import { ResourceListService } from './list-resource.service';
import { CreateResourceData, LinkRelationName, ListItemResource, ListResourceServiceConfig } from './resource.model';
import { ResourceRepository } from './resource.repository';
import { ListResource, StateResource, createEmptyStateResource, createStateResource } from './resource.util';
import * as ResourceUtil from './resource.util';
import { createEmptyStateResource, createErrorStateResource, createStateResource, ListResource, StateResource } from './resource.util';
import { ProblemDetail } from '@alfa-client/tech-shared';
import { expect } from '@jest/globals';
import { createProblemDetail } from '../../../test/error';
describe('ListResourceService', () => {
let service: ResourceListService<Resource, ListResource, ListItemResource>;
......@@ -350,6 +353,20 @@ describe('ListResourceService', () => {
});
});
describe('handle error', () => {
it('should return error state resource on unprocessable entity', () => {
const error: ProblemDetail = createProblemDetail();
expect(service._handleError(error)).toBeObservable(singleColdCompleted(createErrorStateResource(error)));
});
it('should throw error', () => {
const error: ProblemDetail = { ...createProblemDetail(), status: 500 };
expect(service._handleError(error)).toBeObservable(cold('#', null, error));
});
});
describe('select', () => {
const selfHref: ResourceUri = 'dummySelfHref';
const dummyResource: Resource = createResourceWithUri(selfHref);
......
......@@ -21,24 +21,16 @@
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
import { Resource, ResourceUri, getUrl, hasLink } from '@ngxp/rest';
import { getUrl, hasLink, Resource, ResourceUri } from '@ngxp/rest';
import { isEqual, isNil, isNull } from 'lodash-es';
import { BehaviorSubject, Observable, combineLatest, debounceTime, filter, first, map, startWith, tap } from 'rxjs';
import { BehaviorSubject, catchError, combineLatest, debounceTime, filter, first, map, Observable, of, startWith, tap, throwError, } from 'rxjs';
import { isUnprocessableEntity } from '../http.util';
import { ProblemDetail } from '../tech.model';
import { isNotNull, isNotUndefined } from '../tech.util';
import { CreateResourceData, ListItemResource, ListResourceServiceConfig } from './resource.model';
import { ResourceRepository } from './resource.repository';
import { mapToFirst, mapToResource } from './resource.rxjs.operator';
import {
ListResource,
StateResource,
createEmptyStateResource,
createStateResource,
doIfLoadingRequired,
getEmbeddedResources,
isInvalidResourceCombination,
isLoadingRequired,
isStateResoureStable,
} from './resource.util';
import { createEmptyStateResource, createErrorStateResource, createStateResource, doIfLoadingRequired, getEmbeddedResources, isInvalidResourceCombination, isLoadingRequired, isStateResoureStable, ListResource, StateResource, } from './resource.util';
/**
* B = Type of baseresource
......@@ -111,6 +103,7 @@ export class ResourceListService<B extends Resource, T extends ListResource, I e
return this.repository.createResource(this.buildCreateResourceData(toCreate, this.config.createLinkRel)).pipe(
map((listItemResource: I) => createStateResource(listItemResource)),
startWith(createEmptyStateResource<I>(true)),
catchError((error: ProblemDetail) => this._handleError(error)),
);
}
......@@ -131,6 +124,13 @@ export class ResourceListService<B extends Resource, T extends ListResource, I e
return this.hasLinkRel(this.config.createLinkRel);
}
_handleError(error: ProblemDetail): Observable<StateResource<I>> {
if (isUnprocessableEntity(error.status)) {
return of(createErrorStateResource(error));
}
return throwError(() => error);
}
public select(uri: ResourceUri): void {
this.setSelectedResourceLoading();
this.repository
......
......@@ -26,7 +26,7 @@ import { faker } from '@faker-js/faker';
import { createInvalidParam, createIssue, createProblemDetail } from '../../../test/error';
import { InvalidParam, Issue } from '../tech.model';
import { VALIDATION_MESSAGES, ValidationMessageCode } from './tech.validation.messages';
import { getControlForInvalidParam, getControlForIssue, getFieldPath, getMessageForInvalidParam, getMessageForIssue, getMessageReason, setInvalidParamValidationError, setIssueValidationError } from './tech.validation.util';
import { getControlForInvalidParam, getControlForIssue, getFieldPath, getMessageForInvalidParam, getMessageForIssue, getMessageReason, setInvalidParamValidationError, setIssueValidationError, } from './tech.validation.util';
describe('ValidationUtils', () => {
const baseField1Control: FormControl = new UntypedFormControl();
......@@ -44,7 +44,7 @@ describe('ValidationUtils', () => {
describe('set issue validation error', () => {
describe('get control for issue', () => {
it('should return base field control', () => {
const issue: Issue = { ...createIssue(), field: 'class.resource.baseField1' };
const issue: Issue = { ...createIssue(), field: 'baseField1' };
const control: AbstractControl = getControlForIssue(form, issue);
......@@ -69,7 +69,7 @@ describe('ValidationUtils', () => {
});
describe('in base field', () => {
const issue: Issue = { ...createIssue(), field: 'class.resource.baseField1' };
const issue: Issue = { ...createIssue(), field: 'baseField1' };
it('should set error in control', () => {
setIssueValidationError(form, issue);
......@@ -144,7 +144,7 @@ describe('ValidationUtils', () => {
it('should return base field control', () => {
const invalidParam: InvalidParam = {
...createInvalidParam(),
name: 'class.resource.baseField1',
name: 'baseField1',
};
const control: AbstractControl = getControlForInvalidParam(form, invalidParam);
......@@ -155,7 +155,7 @@ describe('ValidationUtils', () => {
it('should return sub group field', () => {
const invalidParam: InvalidParam = {
...createInvalidParam(),
name: 'class.resource.subGroup.subGroupField1',
name: 'resource.subGroup.subGroupField1',
};
const control: AbstractControl = getControlForInvalidParam(form, invalidParam, 'resource');
......@@ -166,7 +166,7 @@ describe('ValidationUtils', () => {
it('should ignore path prefix', () => {
const invalidParam: InvalidParam = {
...createInvalidParam(),
name: 'class.resource.baseField1',
name: 'resource.baseField1',
};
const control: AbstractControl = getControlForInvalidParam(form, invalidParam, 'resource');
......@@ -178,7 +178,7 @@ describe('ValidationUtils', () => {
describe('in base field', () => {
const invalidParam: InvalidParam = {
...createInvalidParam(),
name: 'class.resource.baseField1',
name: 'baseField1',
};
it('should set error in control', () => {
......@@ -209,7 +209,7 @@ describe('ValidationUtils', () => {
describe('in subGroup Field', () => {
const invalidParam: InvalidParam = {
...createInvalidParam(),
name: 'class.resource.subGroup.subGroupField1',
name: 'resource.subGroup.subGroupField1',
};
it('should set error in control', () => {
......@@ -243,12 +243,9 @@ describe('ValidationUtils', () => {
});
it('should return field from full path when resource is undefined', () => {
const fieldPath: string = 'field1';
const fullPath: string = `${backendClassName}.${resource}.${fieldPath}`;
const result: string = getFieldPath('field1', undefined);
const result: string = getFieldPath(fullPath, undefined);
expect(result).toBe(fieldPath);
expect(result).toBe('field1');
});
it('should return field from field when resource is undefined', () => {
......@@ -309,9 +306,7 @@ describe('ValidationUtils', () => {
...invalidParam,
reason: ValidationMessageCode.FIELD_INVALID,
});
expect(message).toEqual(
VALIDATION_MESSAGES[ValidationMessageCode.FIELD_INVALID].replace('{field}', label),
);
expect(message).toEqual(VALIDATION_MESSAGES[ValidationMessageCode.FIELD_INVALID].replace('{field}', label));
});
it('should return message with placeholders', () => {
......
......@@ -31,28 +31,18 @@ export function isValidationError(issue: Issue): boolean {
return issue.messageCode.includes('javax.validation.constraints');
}
export function setIssueValidationError(
form: UntypedFormGroup,
issue: Issue,
pathPrefix?: string,
): void {
export function setIssueValidationError(form: UntypedFormGroup, issue: Issue, pathPrefix?: string): void {
const control: AbstractControl = getControlForIssue(form, issue, pathPrefix);
control.setErrors({ [issue.messageCode]: issue });
control.markAsTouched();
}
export function getControlForIssue(
form: UntypedFormGroup,
issue: Issue,
pathPrefix?: string,
): AbstractControl {
export function getControlForIssue(form: UntypedFormGroup, issue: Issue, pathPrefix?: string): AbstractControl {
const fieldPath: string = getFieldPath(issue.field, pathPrefix);
let curControl: AbstractControl = form;
fieldPath
.split('.')
.forEach((field) => (curControl = (<UntypedFormGroup>curControl).controls[field]));
fieldPath.split('.').forEach((field) => (curControl = (<UntypedFormGroup>curControl).controls[field]));
return curControl;
}
......@@ -66,9 +56,7 @@ export function getMessageForIssue(label: string, issue: Issue): string {
}
msg = replacePlaceholder(msg, 'field', label);
issue.parameters.forEach(
(param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)),
);
issue.parameters.forEach((param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)));
return msg;
}
......@@ -84,11 +72,7 @@ export function getMessageCode(apiError: ApiError): string {
return apiError.issues[0].messageCode;
}
export function setInvalidParamValidationError(
form: UntypedFormGroup,
invalidParam: InvalidParam,
pathPrefix?: string,
): void {
export function setInvalidParamValidationError(form: UntypedFormGroup, invalidParam: InvalidParam, pathPrefix?: string): void {
const control: AbstractControl = getControlForInvalidParam(form, invalidParam, pathPrefix);
control.setErrors({ [invalidParam.reason]: invalidParam });
......@@ -112,17 +96,20 @@ export function getMessageForInvalidParam(label: string, invalidParam: InvalidPa
}
msg = replacePlaceholder(msg, 'field', label);
invalidParam.constraintParameters.forEach(
(param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)),
);
invalidParam.constraintParameters.forEach((param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)));
return msg;
}
export function getFieldPath(name: string, pathPrefix: string): string {
const path: string = _mapFormArrayElementNameToPath(name);
if (isEmpty(pathPrefix)) {
return name.split('.').pop();
return path;
}
const indexOfField = path.lastIndexOf(pathPrefix) + pathPrefix.length + 1;
return path.slice(indexOfField);
}
const indexOfField = name.lastIndexOf(pathPrefix) + pathPrefix.length + 1;
return name.slice(indexOfField);
export function _mapFormArrayElementNameToPath(name: string): string {
return name.replace(/\[(\d+?)]\./g, '.$1.');
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment