From 98d18c0233c164fe857231d0353a681d60578d86 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 29 Nov 2018 11:55:04 -0700 Subject: [PATCH 1/6] [SHIBUI-996] Added exception handler for database constraint exception. Added i18n error message. --- .../ui/controller/support/RestControllersSupport.java | 8 ++++++++ backend/src/main/resources/i18n/messages.properties | 1 + backend/src/main/resources/i18n/messages_en.properties | 1 + 3 files changed, 10 insertions(+) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/support/RestControllersSupport.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/support/RestControllersSupport.java index 1605b86dd..899a5b9e5 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/support/RestControllersSupport.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/support/RestControllersSupport.java @@ -1,14 +1,17 @@ package edu.internet2.tier.shibboleth.admin.ui.controller.support; import com.google.common.collect.ImmutableMap; +import edu.internet2.tier.shibboleth.admin.ui.controller.ErrorResponse; import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository; +import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.client.HttpClientErrorException; +import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.NOT_FOUND; /** @@ -38,4 +41,9 @@ public ResponseEntity notFoundHandler(HttpClientErrorException ex) { } throw ex; } + + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleDatabaseConstraintViolation(ConstraintViolationException ex) { + return ResponseEntity.status(BAD_REQUEST).body(new ErrorResponse("400", "message.database-constraint")); + } } diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index 31c4de9b8..f3068bdc3 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -397,6 +397,7 @@ message.wizard-status=Step { index } of { length } message.entity-id-min-unique=You must add at least one entity id target and they must each be unique. message.required-for-scripts=Required for Scripts message.required-for-regex=Required for Regex +message.database-constraint=There was a database constraint problem processing the request. Check the request to insure that fields that must be unique are truly unique. tooltip.entity-id=Entity ID tooltip.service-provider-name=Service Provider Name (Dashboard Display Only) diff --git a/backend/src/main/resources/i18n/messages_en.properties b/backend/src/main/resources/i18n/messages_en.properties index 3d44ca8eb..74edcc0a3 100644 --- a/backend/src/main/resources/i18n/messages_en.properties +++ b/backend/src/main/resources/i18n/messages_en.properties @@ -398,6 +398,7 @@ message.wizard-status=Step { index } of { length } message.entity-id-min-unique=You must add at least one entity id target and they must each be unique. message.required-for-scripts=Required for Scripts message.required-for-regex=Required for Regex +message.database-constraint=There was a database constraint problem processing the request. Check the request to insure that fields that must be unique are truly unique. tooltip.entity-id=Entity ID tooltip.service-provider-name=Service Provider Name (Dashboard Display Only) From b098b62e735345066ffd8ba361d0377c19916821 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 29 Nov 2018 14:22:56 -0700 Subject: [PATCH 2/6] SHIBUI-996 Renamed file --- .../container/select-filter.component.html | 3 ++ .../container/select-filter.component.scss | 0 .../container/select-filter.component.spec.ts | 0 .../container/select-filter.component.ts | 39 +++++++++++++++++++ .../filter/service/filter-resolver.service.ts | 7 ++++ 5 files changed, 49 insertions(+) create mode 100644 ui/src/app/metadata/filter/container/select-filter.component.html create mode 100644 ui/src/app/metadata/filter/container/select-filter.component.scss create mode 100644 ui/src/app/metadata/filter/container/select-filter.component.spec.ts create mode 100644 ui/src/app/metadata/filter/container/select-filter.component.ts create mode 100644 ui/src/app/metadata/filter/service/filter-resolver.service.ts diff --git a/ui/src/app/metadata/filter/container/select-filter.component.html b/ui/src/app/metadata/filter/container/select-filter.component.html new file mode 100644 index 000000000..3793ba3bf --- /dev/null +++ b/ui/src/app/metadata/filter/container/select-filter.component.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/ui/src/app/metadata/filter/container/select-filter.component.scss b/ui/src/app/metadata/filter/container/select-filter.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/ui/src/app/metadata/filter/container/select-filter.component.spec.ts b/ui/src/app/metadata/filter/container/select-filter.component.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/ui/src/app/metadata/filter/container/select-filter.component.ts b/ui/src/app/metadata/filter/container/select-filter.component.ts new file mode 100644 index 000000000..97fe09d0f --- /dev/null +++ b/ui/src/app/metadata/filter/container/select-filter.component.ts @@ -0,0 +1,39 @@ +import { Component, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Observable, Subscription } from 'rxjs'; +import { distinctUntilChanged, map } from 'rxjs/operators'; +import { Store } from '@ngrx/store'; +import { NgbPopoverConfig } from '@ng-bootstrap/ng-bootstrap'; + +import { MetadataFilter } from '../../domain/model/metadata-filter'; +import { SelectFilter } from '../action/collection.action'; +import * as fromFilter from '../reducer'; + +@Component({ + selector: 'select-filter-page', + templateUrl: './select-filter.component.html', + styleUrls: ['./select-filter.component.scss'], + providers: [NgbPopoverConfig] +}) +export class SelectFilterComponent implements OnDestroy { + actionsSubscription: Subscription; + filter$: Observable; + + constructor( + private store: Store, + private route: ActivatedRoute + ) { + this.actionsSubscription = this.route.params.pipe( + distinctUntilChanged(), + map(params => { + return new SelectFilter(params.id); + }) + ).subscribe(store); + + this.filter$ = this.store.select(fromFilter.getSelectedFilter); + } + + ngOnDestroy() { + this.actionsSubscription.unsubscribe(); + } +} /* istanbul ignore next */ diff --git a/ui/src/app/metadata/filter/service/filter-resolver.service.ts b/ui/src/app/metadata/filter/service/filter-resolver.service.ts new file mode 100644 index 000000000..e6b2e76f6 --- /dev/null +++ b/ui/src/app/metadata/filter/service/filter-resolver.service.ts @@ -0,0 +1,7 @@ +this.provider$ + .pipe( + takeUntil(this.ngUnsubscribe) + ) + .subscribe(p => { + this.store.dispatch(new LoadFilterRequest(p.resourceId)); + }); \ No newline at end of file From bfceb8c9caa24711d9ea9b25d00e35606aa07197 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 29 Nov 2018 14:23:30 -0700 Subject: [PATCH 3/6] SHIBUI-996 Added name unique validation --- .../container/edit-filter.component.html | 2 +- .../filter/container/edit-filter.component.ts | 13 ++++- .../filter/container/filter.component.html | 2 +- .../filter/container/filter.component.scss | 0 .../filter/container/filter.component.ts | 16 +++---- .../container/new-filter.component.html | 2 +- .../filter/container/new-filter.component.ts | 8 +++- ui/src/app/metadata/filter/filter.module.ts | 8 ++-- .../filter/model/entity-attributes.filter.ts | 30 +++++++++++- ui/src/app/metadata/filter/reducer/index.ts | 3 ++ .../filter/service/filter-resolver.service.ts | 7 --- .../app/metadata/provider/provider.routing.ts | 47 +++++++++++-------- 12 files changed, 91 insertions(+), 47 deletions(-) delete mode 100644 ui/src/app/metadata/filter/container/filter.component.scss delete mode 100644 ui/src/app/metadata/filter/service/filter-resolver.service.ts diff --git a/ui/src/app/metadata/filter/container/edit-filter.component.html b/ui/src/app/metadata/filter/container/edit-filter.component.html index 60790fdff..e42137ebf 100644 --- a/ui/src/app/metadata/filter/container/edit-filter.component.html +++ b/ui/src/app/metadata/filter/container/edit-filter.component.html @@ -28,7 +28,7 @@ diff --git a/ui/src/app/metadata/filter/container/edit-filter.component.ts b/ui/src/app/metadata/filter/container/edit-filter.component.ts index a98cbc62d..21983f5a9 100644 --- a/ui/src/app/metadata/filter/container/edit-filter.component.ts +++ b/ui/src/app/metadata/filter/container/edit-filter.component.ts @@ -11,7 +11,7 @@ import { UpdateFilterRequest } from '../action/collection.action'; import { CancelCreateFilter, UpdateFilterChanges } from '../action/filter.action'; import { PreviewEntity } from '../../domain/action/entity.action'; import { EntityAttributesFilterEntity } from '../../domain/entity'; -import { shareReplay } from 'rxjs/operators'; +import { shareReplay, map, withLatestFrom } from 'rxjs/operators'; @Component({ selector: 'edit-filter-page', @@ -33,6 +33,8 @@ export class EditFilterComponent { filter: MetadataFilter; isValid: boolean; + validators$: Observable<{ [key: string]: any }>; + actions: any; constructor( @@ -50,6 +52,15 @@ export class EditFilterComponent { this.isValid = valid.value ? valid.value.length === 0 : true; }); + this.validators$ = this.store.select(fromFilter.getFilterNames).pipe( + withLatestFrom( + this.store.select(fromFilter.getSelectedFilter) + ), + map(([names, provider]) => this.definition.getValidators( + names.filter(n => n !== provider.name) + )) + ); + this.store .select(fromFilter.getFilter) .subscribe(filter => this.filter = filter); diff --git a/ui/src/app/metadata/filter/container/filter.component.html b/ui/src/app/metadata/filter/container/filter.component.html index 3793ba3bf..eb3040a74 100644 --- a/ui/src/app/metadata/filter/container/filter.component.html +++ b/ui/src/app/metadata/filter/container/filter.component.html @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/ui/src/app/metadata/filter/container/filter.component.scss b/ui/src/app/metadata/filter/container/filter.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/ui/src/app/metadata/filter/container/filter.component.ts b/ui/src/app/metadata/filter/container/filter.component.ts index 2f2d0aa44..6dc02e037 100644 --- a/ui/src/app/metadata/filter/container/filter.component.ts +++ b/ui/src/app/metadata/filter/container/filter.component.ts @@ -3,35 +3,31 @@ import { ActivatedRoute } from '@angular/router'; import { Observable, Subscription } from 'rxjs'; import { distinctUntilChanged, map } from 'rxjs/operators'; import { Store } from '@ngrx/store'; -import { NgbPopoverConfig } from '@ng-bootstrap/ng-bootstrap'; import { MetadataFilter } from '../../domain/model/metadata-filter'; -import { SelectFilter } from '../action/collection.action'; +import { LoadFilterRequest } from '../action/collection.action'; import * as fromFilter from '../reducer'; - @Component({ selector: 'filter-page', templateUrl: './filter.component.html', - styleUrls: ['./filter.component.scss'], - providers: [NgbPopoverConfig] + styleUrls: [], + providers: [] }) export class FilterComponent implements OnDestroy { actionsSubscription: Subscription; - filter$: Observable; + filters$: Observable; constructor( private store: Store, private route: ActivatedRoute ) { - this.actionsSubscription = this.route.params.pipe( + this.actionsSubscription = this.route.parent.params.pipe( distinctUntilChanged(), map(params => { - return new SelectFilter(params.id); + return new LoadFilterRequest(params.providerId); }) ).subscribe(store); - - this.filter$ = this.store.select(fromFilter.getSelectedFilter); } ngOnDestroy() { diff --git a/ui/src/app/metadata/filter/container/new-filter.component.html b/ui/src/app/metadata/filter/container/new-filter.component.html index 1b0d62945..e56dc4634 100644 --- a/ui/src/app/metadata/filter/container/new-filter.component.html +++ b/ui/src/app/metadata/filter/container/new-filter.component.html @@ -28,7 +28,7 @@ diff --git a/ui/src/app/metadata/filter/container/new-filter.component.ts b/ui/src/app/metadata/filter/container/new-filter.component.ts index aeb56e019..043936fc2 100644 --- a/ui/src/app/metadata/filter/container/new-filter.component.ts +++ b/ui/src/app/metadata/filter/container/new-filter.component.ts @@ -1,7 +1,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { Subject, Observable, of } from 'rxjs'; -import { takeUntil, shareReplay } from 'rxjs/operators'; +import { takeUntil, shareReplay, withLatestFrom, map, filter } from 'rxjs/operators'; import * as fromFilter from '../reducer'; import { MetadataFilterTypes } from '../model'; @@ -33,6 +33,8 @@ export class NewFilterComponent implements OnDestroy, OnInit { filter: MetadataFilter; isValid: boolean; + validators$: Observable<{ [key: string]: any }>; + constructor( private store: Store, private schemaService: SchemaService @@ -42,6 +44,10 @@ export class NewFilterComponent implements OnDestroy, OnInit { this.schema$ = this.schemaService.get(this.definition.schema).pipe(shareReplay()); this.isSaving$ = this.store.select(fromFilter.getCollectionSaving); this.model$ = of({}); + + this.validators$ = this.store.select(fromFilter.getFilterNames).pipe( + map((names) => this.definition.getValidators(names)) + ); } ngOnInit(): void { diff --git a/ui/src/app/metadata/filter/filter.module.ts b/ui/src/app/metadata/filter/filter.module.ts index 34997c563..e090745f2 100644 --- a/ui/src/app/metadata/filter/filter.module.ts +++ b/ui/src/app/metadata/filter/filter.module.ts @@ -13,7 +13,7 @@ import { NgbPopoverModule, NgbModalModule } from '@ng-bootstrap/ng-bootstrap'; import { SearchDialogComponent } from './component/search-dialog.component'; import { SharedModule } from '../../shared/shared.module'; import { EditFilterComponent } from './container/edit-filter.component'; -import { FilterComponent } from './container/filter.component'; +import { SelectFilterComponent } from './container/select-filter.component'; import { SearchIdEffects } from './effect/search.effect'; import { FilterExistsGuard } from './guard/filter-exists.guard'; import { DomainModule } from '../domain/domain.module'; @@ -21,13 +21,15 @@ import { ModuleWithProviders } from '@angular/compiler/src/core'; import { FilterCollectionEffects } from './effect/collection.effect'; import { FormModule } from '../../schema-form/schema-form.module'; import { I18nModule } from '../../i18n/i18n.module'; +import { FilterComponent } from './container/filter.component'; @NgModule({ declarations: [ NewFilterComponent, EditFilterComponent, - FilterComponent, - SearchDialogComponent + SelectFilterComponent, + SearchDialogComponent, + FilterComponent ], entryComponents: [ SearchDialogComponent diff --git a/ui/src/app/metadata/filter/model/entity-attributes.filter.ts b/ui/src/app/metadata/filter/model/entity-attributes.filter.ts index 0d559c094..5c40ecfb1 100644 --- a/ui/src/app/metadata/filter/model/entity-attributes.filter.ts +++ b/ui/src/app/metadata/filter/model/entity-attributes.filter.ts @@ -5,8 +5,34 @@ export const EntityAttributesFilter: FormDefinition = { label: 'EntityAttributes', type: 'EntityAttributes', schema: '/api/ui/EntityAttributesFilters', - getValidators(): any { - const validators = {}; + getValidators(namesList: string[] = []): any { + const validators = { + '/': (value, property, form_current) => { + let errors; + // iterate all customer + Object.keys(value).forEach((key) => { + const item = value[key]; + const validatorKey = `/${key}`; + const validator = validators.hasOwnProperty(validatorKey) ? validators[validatorKey] : null; + const error = validator ? validator(item, { path: `/${key}` }, form_current) : null; + if (error) { + errors = errors || []; + errors.push(error); + } + }); + return errors; + }, + '/name': (value, property, form) => { + console.log(namesList); + const err = namesList.indexOf(value) > -1 ? { + code: 'INVALID_NAME', + path: `#${property.path}`, + message: 'message.name-must-be-unique', + params: [value] + } : null; + return err; + } + }; return validators; }, parser: (changes: any): MetadataFilter => changes, diff --git a/ui/src/app/metadata/filter/reducer/index.ts b/ui/src/app/metadata/filter/reducer/index.ts index caacf0254..495bf1e52 100644 --- a/ui/src/app/metadata/filter/reducer/index.ts +++ b/ui/src/app/metadata/filter/reducer/index.ts @@ -4,6 +4,7 @@ import * as fromFilter from './filter.reducer'; import * as fromSearch from './search.reducer'; import * as fromCollection from './collection.reducer'; import * as utils from '../../domain/domain.util'; +import { MetadataFilter } from '../../domain/model'; export interface FilterState { filter: fromFilter.FilterState; @@ -71,6 +72,8 @@ export const getAdditionalFilterOrder = createSelector(getFilterEntities, getCol export const getAdditionalFilters = createSelector(getFilterList, getAdditionalFilterOrder, utils.mergeOrderFn); export const getPluginFilterOrder = createSelector(getFilterEntities, getCollectionOrder, pluginOrderFn); +export const getFilterNames = createSelector(getAllFilters, (filters: MetadataFilter[]) => filters.map(f => f.name).filter(f => !!f)); + /* * Combine pieces of State */ diff --git a/ui/src/app/metadata/filter/service/filter-resolver.service.ts b/ui/src/app/metadata/filter/service/filter-resolver.service.ts deleted file mode 100644 index e6b2e76f6..000000000 --- a/ui/src/app/metadata/filter/service/filter-resolver.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -this.provider$ - .pipe( - takeUntil(this.ngUnsubscribe) - ) - .subscribe(p => { - this.store.dispatch(new LoadFilterRequest(p.resourceId)); - }); \ No newline at end of file diff --git a/ui/src/app/metadata/provider/provider.routing.ts b/ui/src/app/metadata/provider/provider.routing.ts index 7acd52fec..fa1a4c3b5 100644 --- a/ui/src/app/metadata/provider/provider.routing.ts +++ b/ui/src/app/metadata/provider/provider.routing.ts @@ -8,9 +8,10 @@ import { ProviderEditStepComponent } from './container/provider-edit-step.compon import { ProviderSelectComponent } from './container/provider-select.component'; import { ProviderFilterListComponent } from './container/provider-filter-list.component'; import { NewFilterComponent } from '../filter/container/new-filter.component'; -import { FilterComponent } from '../filter/container/filter.component'; +import { SelectFilterComponent } from '../filter/container/select-filter.component'; import { EditFilterComponent } from '../filter/container/edit-filter.component'; import { CanDeactivateGuard } from '../../core/service/can-deactivate.guard'; +import { FilterComponent } from '../filter/container/filter.component'; export const ProviderRoutes: Routes = [ { @@ -36,6 +37,31 @@ export const ProviderRoutes: Routes = [ path: ':providerId', component: ProviderSelectComponent, children: [ + { + path: '', + component: FilterComponent, + children: [ + { + path: 'filters', + component: ProviderFilterListComponent + }, + { + path: 'filter/new', + component: NewFilterComponent + }, + { + path: 'filter/:id', + component: SelectFilterComponent, + canActivate: [], + children: [ + { + path: 'edit', + component: EditFilterComponent + } + ] + } + ] + }, { path: 'edit', component: ProviderEditComponent, @@ -49,25 +75,6 @@ export const ProviderRoutes: Routes = [ canDeactivate: [ CanDeactivateGuard ] - }, - { - path: 'filters', - component: ProviderFilterListComponent - }, - { - path: 'filter/new', - component: NewFilterComponent - }, - { - path: 'filter/:id', - component: FilterComponent, - canActivate: [], - children: [ - { - path: 'edit', - component: EditFilterComponent - } - ] } ] } From 2091f7c776933ec7c57bd7ca477a77afd085eadb Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 30 Nov 2018 08:25:14 -0700 Subject: [PATCH 4/6] SHIBUI-996 Fixed validation of filter names --- ui/src/app/metadata/filter/effect/collection.effect.ts | 8 -------- .../provider/container/provider-select.component.ts | 2 -- ui/src/app/metadata/provider/provider.routing.ts | 8 ++++---- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/ui/src/app/metadata/filter/effect/collection.effect.ts b/ui/src/app/metadata/filter/effect/collection.effect.ts index f2975bd34..e04265eb2 100644 --- a/ui/src/app/metadata/filter/effect/collection.effect.ts +++ b/ui/src/app/metadata/filter/effect/collection.effect.ts @@ -166,14 +166,6 @@ export class FilterCollectionEffects { ) ); - /* - @Effect() - reloadOrderAfterChange$ = this.actions$.pipe( - ofType(FilterCollectionActionTypes.SET_ORDER_FILTER_SUCCESS), - map(() => new GetOrderFilterRequest()) - ); - */ - @Effect() setOrder$ = this.actions$.pipe( ofType(FilterCollectionActionTypes.SET_ORDER_FILTER_REQUEST), diff --git a/ui/src/app/metadata/provider/container/provider-select.component.ts b/ui/src/app/metadata/provider/container/provider-select.component.ts index 8fe540eeb..baba7f626 100644 --- a/ui/src/app/metadata/provider/container/provider-select.component.ts +++ b/ui/src/app/metadata/provider/container/provider-select.component.ts @@ -31,8 +31,6 @@ export class ProviderSelectComponent implements OnDestroy { map(params => new SelectProviderRequest(params.providerId)) ).subscribe(store); - this.route.params.subscribe(params => console.log(params)); - this.provider$ = this.store.select(fromProviders.getSelectedProvider).pipe(skipWhile(p => !p)); this.provider$.subscribe(provider => this.setDefinition(provider)); diff --git a/ui/src/app/metadata/provider/provider.routing.ts b/ui/src/app/metadata/provider/provider.routing.ts index fa1a4c3b5..1dcbc4fbb 100644 --- a/ui/src/app/metadata/provider/provider.routing.ts +++ b/ui/src/app/metadata/provider/provider.routing.ts @@ -41,10 +41,6 @@ export const ProviderRoutes: Routes = [ path: '', component: FilterComponent, children: [ - { - path: 'filters', - component: ProviderFilterListComponent - }, { path: 'filter/new', component: NewFilterComponent @@ -62,6 +58,10 @@ export const ProviderRoutes: Routes = [ } ] }, + { + path: 'filters', + component: ProviderFilterListComponent + }, { path: 'edit', component: ProviderEditComponent, From 03d3b2449e9a13c447fdff2861eb4b66f3ee24c3 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 30 Nov 2018 08:56:11 -0700 Subject: [PATCH 5/6] SHIBUI-996 Fixed issue with filter validation --- .../model/entity-attributes.filter.spec.ts | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/ui/src/app/metadata/filter/model/entity-attributes.filter.spec.ts b/ui/src/app/metadata/filter/model/entity-attributes.filter.spec.ts index ba27a427d..f21b2db4b 100644 --- a/ui/src/app/metadata/filter/model/entity-attributes.filter.spec.ts +++ b/ui/src/app/metadata/filter/model/entity-attributes.filter.spec.ts @@ -1,8 +1,37 @@ import { EntityAttributesFilter } from './entity-attributes.filter'; describe('Entity Attributes filter form', () => { - it('should return an empty object for validators', () => { - expect(EntityAttributesFilter.getValidators()).toEqual({}); + describe('getValidators', () => { + it('should return an empty object for validators', () => { + expect(Object.keys(EntityAttributesFilter.getValidators())).toEqual([ + '/', + '/name' + ]); + }); + + describe('name `/name` validator', () => { + const validators = EntityAttributesFilter.getValidators(['foo', 'bar']); + + it('should return an invalid object when provided values are invalid based on name', () => { + expect(validators['/name']('foo', { path: '/name' })).toBeDefined(); + }); + + it('should return null when provided values are valid based on name', () => { + expect(validators['/name']('baz', { path: '/name' })).toBeNull(); + }); + }); + + describe('parent `/` validator', () => { + const validators = EntityAttributesFilter.getValidators(['foo', 'bar']); + + it('should return a list of child errors', () => { + expect(validators['/']({ name: 'foo' }, { path: '/name' }, {}).length).toBe(1); + }); + + it('should ignore properties that don\'t exist a list of child errors', () => { + expect(validators['/']({ foo: 'bar' }, { path: '/foo' }, {})).toBeUndefined(); + }); + }); }); describe('transformer', () => { From 452397dd1d5e18a9680f624d9c6ee46cc5d9cb3a Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Tue, 4 Dec 2018 09:24:27 -0700 Subject: [PATCH 6/6] [SHIBUI-996] Fixed typo. --- backend/src/main/resources/i18n/messages.properties | 2 +- backend/src/main/resources/i18n/messages_en.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index f3068bdc3..274d61981 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -397,7 +397,7 @@ message.wizard-status=Step { index } of { length } message.entity-id-min-unique=You must add at least one entity id target and they must each be unique. message.required-for-scripts=Required for Scripts message.required-for-regex=Required for Regex -message.database-constraint=There was a database constraint problem processing the request. Check the request to insure that fields that must be unique are truly unique. +message.database-constraint=There was a database constraint problem processing the request. Check the request to ensure that fields that must be unique are truly unique. tooltip.entity-id=Entity ID tooltip.service-provider-name=Service Provider Name (Dashboard Display Only) diff --git a/backend/src/main/resources/i18n/messages_en.properties b/backend/src/main/resources/i18n/messages_en.properties index 0baf78e37..ca4fa40c6 100644 --- a/backend/src/main/resources/i18n/messages_en.properties +++ b/backend/src/main/resources/i18n/messages_en.properties @@ -403,7 +403,7 @@ message.wizard-status=Step { index } of { length } message.entity-id-min-unique=You must add at least one entity id target and they must each be unique. message.required-for-scripts=Required for Scripts message.required-for-regex=Required for Regex -message.database-constraint=There was a database constraint problem processing the request. Check the request to insure that fields that must be unique are truly unique. +message.database-constraint=There was a database constraint problem processing the request. Check the request to ensure that fields that must be unique are truly unique. tooltip.entity-id=Entity ID tooltip.service-provider-name=Service Provider Name (Dashboard Display Only)