From 4a3ae5f358bc28277c16bcd4ee2d97b4241796f5 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Wed, 14 Nov 2018 08:27:10 -0700 Subject: [PATCH 1/8] SHIBUI-985 added resolver service --- .../container/new-resolver.component.ts | 4 +-- .../effect/draft-collection.effects.ts | 25 ------------------ .../app/metadata/resolver/resolver.module.ts | 4 ++- .../app/metadata/resolver/resolver.routing.ts | 4 +++ .../resolver/service/create-draft.resolver.ts | 26 +++++++++++++++++++ 5 files changed, 35 insertions(+), 28 deletions(-) create mode 100644 ui/src/app/metadata/resolver/service/create-draft.resolver.ts diff --git a/ui/src/app/metadata/resolver/container/new-resolver.component.ts b/ui/src/app/metadata/resolver/container/new-resolver.component.ts index ab4f09103..ff916d836 100644 --- a/ui/src/app/metadata/resolver/container/new-resolver.component.ts +++ b/ui/src/app/metadata/resolver/container/new-resolver.component.ts @@ -31,9 +31,9 @@ export class NewResolverComponent { }) ); - this.actionsSubscription = this.route.queryParams.pipe( + this.actionsSubscription = this.route.data.pipe( distinctUntilChanged(), - map(params => new SelectDraftRequest(params.id)) + map(data => new SelectDraftRequest(data.draft)) ).subscribe(this.store); } } diff --git a/ui/src/app/metadata/resolver/effect/draft-collection.effects.ts b/ui/src/app/metadata/resolver/effect/draft-collection.effects.ts index f6e203381..ac94d2410 100644 --- a/ui/src/app/metadata/resolver/effect/draft-collection.effects.ts +++ b/ui/src/app/metadata/resolver/effect/draft-collection.effects.ts @@ -63,13 +63,6 @@ export class DraftCollectionEffects { ) ); - @Effect({ dispatch: false }) - addDraftSuccessRedirect$ = this.actions$.pipe( - ofType(DraftActionTypes.ADD_DRAFT_SUCCESS), - map(getPayload), - tap(provider => this.router.navigate(['metadata', 'resolver', provider.entityId, 'wizard'])) - ); - @Effect() updateDraft$ = this.actions$.pipe( ofType(DraftActionTypes.UPDATE_DRAFT_REQUEST), @@ -107,24 +100,6 @@ export class DraftCollectionEffects { map(id => new actions.LoadDraftRequest()) ); - @Effect() - selectDraftError$ = this.actions$.pipe( - ofType(DraftActionTypes.SELECT_ERROR), - map(getPayload), - switchMap(id => - this.draftService - .save({ id: `r-${ Date.now() }`, serviceProviderName: '' }) - .pipe( - map(p => new SelectDraftRequest(p.id)), - catchError(e => of(new SelectDraftError())) - ) - ), - tap(() => { - // this.store.dispatch(new ClearWizard()); - this.store.dispatch(new Clear()); - }) - ); - @Effect() removeDraft$ = this.actions$.pipe( ofType(DraftActionTypes.REMOVE_DRAFT), diff --git a/ui/src/app/metadata/resolver/resolver.module.ts b/ui/src/app/metadata/resolver/resolver.module.ts index 39f4b67f1..c5290431f 100644 --- a/ui/src/app/metadata/resolver/resolver.module.ts +++ b/ui/src/app/metadata/resolver/resolver.module.ts @@ -35,6 +35,7 @@ import { ResolverSelectComponent } from './container/resolver-select.component'; import { MetadataSourceEditor } from '../domain/model/wizards/metadata-source-editor'; import { FinishFormComponent } from './component/finish-form.component'; import { ProviderFormFragmentComponent } from './component/provider-form-fragment.component'; +import { CreateDraftResolverService } from './service/create-draft.resolver'; @NgModule({ declarations: [ @@ -75,7 +76,8 @@ export class ResolverModule { return { ngModule: RootResolverModule, providers: [ - CopyIsSetGuard + CopyIsSetGuard, + CreateDraftResolverService ] }; } diff --git a/ui/src/app/metadata/resolver/resolver.routing.ts b/ui/src/app/metadata/resolver/resolver.routing.ts index 1d080bd5f..9916e2206 100644 --- a/ui/src/app/metadata/resolver/resolver.routing.ts +++ b/ui/src/app/metadata/resolver/resolver.routing.ts @@ -13,6 +13,7 @@ import { ResolverWizardStepComponent } from './container/resolver-wizard-step.co import { ResolverEditComponent } from './container/resolver-edit.component'; import { ResolverEditStepComponent } from './container/resolver-edit-step.component'; import { ResolverSelectComponent } from './container/resolver-select.component'; +import { CreateDraftResolverService } from './service/create-draft.resolver'; export const ResolverRoutes: Routes = [ { @@ -21,6 +22,9 @@ export const ResolverRoutes: Routes = [ { path: 'new', component: NewResolverComponent, + resolve: { + draft: CreateDraftResolverService + }, children: [ { path: '', redirectTo: 'blank/common', pathMatch: 'prefix' }, { diff --git a/ui/src/app/metadata/resolver/service/create-draft.resolver.ts b/ui/src/app/metadata/resolver/service/create-draft.resolver.ts new file mode 100644 index 000000000..d928d4849 --- /dev/null +++ b/ui/src/app/metadata/resolver/service/create-draft.resolver.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { Observable, of } from 'rxjs'; +import { Store } from '@ngrx/store'; +import { MetadataResolver } from '../../domain/model'; +import { AddDraftRequest } from '../action/draft.action'; +import * as fromResolver from '../reducer'; + +@Injectable() +export class CreateDraftResolverService { + constructor( + private store: Store + ) { } + + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Observable { + let id = route.paramMap.get('id'); + let resolver = { + id: `r-${Date.now()}` + }; + if (!id) { + id = resolver.id; + this.store.dispatch(new AddDraftRequest(resolver)); + } + return of(id); + } +} From 3248d54f0d57081be9ed8c1d9dcadab226986def Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 27 Nov 2018 09:35:01 -0700 Subject: [PATCH 2/8] SHIBUI-953 Fixed issue with validation on organizations --- .../resources/i18n/messages_en.properties | 5 + .../resources/metadata-sources-ui-schema.json | 30 +++--- .../wizards/metadata-source-base.spec.ts | 5 +- .../model/wizards/metadata-source-base.ts | 19 +++- .../service/schema.service.spec.ts | 92 ++++++++++++++++++- .../app/schema-form/service/schema.service.ts | 23 +++++ .../widget/array/array.component.ts | 2 +- .../widget/object/object.component.ts | 2 +- .../widget/string/string.component.html | 4 +- 9 files changed, 161 insertions(+), 21 deletions(-) diff --git a/backend/src/main/resources/i18n/messages_en.properties b/backend/src/main/resources/i18n/messages_en.properties index 984f3c21c..131c70010 100644 --- a/backend/src/main/resources/i18n/messages_en.properties +++ b/backend/src/main/resources/i18n/messages_en.properties @@ -355,6 +355,11 @@ message.uri-valid-format=URI must be valid format. message.id-unique=ID must be unique. message.array-items-must-be-unique=Items in list must be unique. +message.org-name-required=Organization Name is required. +message.org-displayName-required=Organization Name is required. +message.org-url-required=Organization Name is required. +message.org-incomplete=These three fields must all be entered if any single field has a value. + message.conflict=Conflict message.data-version-contention=Data Version Contention message.contention-new-version=A newer version of this metadata source has been saved. Below are a list of changes. You can use your changes or their changes. diff --git a/backend/src/main/resources/metadata-sources-ui-schema.json b/backend/src/main/resources/metadata-sources-ui-schema.json index d1353043e..3f002253a 100644 --- a/backend/src/main/resources/metadata-sources-ui-schema.json +++ b/backend/src/main/resources/metadata-sources-ui-schema.json @@ -45,18 +45,24 @@ } }, "dependencies": { - "name": [ - "displayName", - "url" - ], - "displayName": [ - "name", - "url" - ], - "url": [ - "name", - "displayName" - ] + "name": { + "required": [ + "displayName", + "url" + ] + }, + "displayName": { + "required": [ + "name", + "url" + ] + }, + "url": { + "required": [ + "name", + "displayName" + ] + } } }, "contacts": { diff --git a/ui/src/app/metadata/domain/model/wizards/metadata-source-base.spec.ts b/ui/src/app/metadata/domain/model/wizards/metadata-source-base.spec.ts index 3386d65e4..0cf3344ad 100644 --- a/ui/src/app/metadata/domain/model/wizards/metadata-source-base.spec.ts +++ b/ui/src/app/metadata/domain/model/wizards/metadata-source-base.spec.ts @@ -40,7 +40,10 @@ describe('Metadata Source Base class', () => { it('should return a list of validators for the ngx-schema-form', () => { expect(Object.keys(getValidators([]))).toEqual([ '/', - '/entityId' + '/entityId', + '/organization/name', + '/organization/displayName', + '/organization/url' ]); }); }); diff --git a/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts b/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts index 7d29d34c5..8a6604c5a 100644 --- a/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts +++ b/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts @@ -60,6 +60,17 @@ export class MetadataSourceBase implements Wizard { } getValidators(entityIdList: string[]): { [key: string]: any } { + const checkOrg = (value, property, form) => { + const org = property.parent; + const orgValue = org.value || {}; + const err = Object.keys(orgValue) && !value ? { + code: 'ORG_INCOMPLETE', + path: `#${property.path}`, + message: `message.org-incomplete`, + params: [value] + } : null; + return err; + }; const validators = { '/': (value, property, form_current) => { let errors; @@ -68,12 +79,13 @@ export class MetadataSourceBase implements Wizard { 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; + const error = validator ? validator(item, form_current.getProperty(key), form_current) : null; if (error) { errors = errors || []; errors.push(error); } }); + console.log(errors, form_current); return errors; }, '/entityId': (value, property, form) => { @@ -84,7 +96,10 @@ export class MetadataSourceBase implements Wizard { params: [value] } : null; return err; - } + }, + '/organization/name': checkOrg, + '/organization/displayName': checkOrg, + '/organization/url': checkOrg }; return validators; } diff --git a/ui/src/app/schema-form/service/schema.service.spec.ts b/ui/src/app/schema-form/service/schema.service.spec.ts index 0dad960fe..eac84cd95 100644 --- a/ui/src/app/schema-form/service/schema.service.spec.ts +++ b/ui/src/app/schema-form/service/schema.service.spec.ts @@ -1,6 +1,6 @@ -import { TestBed, async, inject } from '@angular/core/testing'; -import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; -import { HttpClientModule, HttpRequest } from '@angular/common/http'; +import { TestBed, inject } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { HttpClientModule } from '@angular/common/http'; import { SchemaService } from './schema.service'; describe(`Schema Service`, () => { @@ -171,5 +171,91 @@ describe(`Schema Service`, () => { })).toBe(true); }) ); + + it(`should return true if dependency is active`, + inject([SchemaService], (service: SchemaService) => { + expect(service.isRequired({ + parent: { + schema: { + properties: { + foo: { type: 'string' }, + bar: { type: 'string' }, + baz: { type: 'string' } + }, + dependencies: { + foo: { required: ['bar', 'baz'] }, + bar: { required: ['foo', 'baz'] }, + baz: { required: ['foo', 'bar'] } + } + }, + value: { + foo: 'abcdef' + } + }, + path: '/bar' + })).toBe(true); + }) + ); + + it(`should return true if the property has an active dependency`, + inject([SchemaService], (service: SchemaService) => { + expect(service.isRequired({ + parent: { + schema: { + properties: { + foo: { type: 'string' }, + bar: { type: 'string' }, + baz: { type: 'string' } + }, + dependencies: { + foo: { required: ['bar', 'baz'] }, + bar: { required: ['foo', 'baz'] }, + baz: { required: ['foo', 'bar'] } + } + }, + value: { + foo: 'abc', + bar: '123' + } + }, + path: '/foo' + })).toBe(true); + }) + ); + + it(`should return false if no dependencies are defined`, + inject([SchemaService], (service: SchemaService) => { + expect(service.isRequired({ + parent: { + schema: { + properties: { + foo: { type: 'string' }, + bar: { type: 'string' }, + baz: { type: 'string' } + } + }, + value: { + foo: true, + baz: true + } + }, + path: '/bar' + })).toBe(false); + }) + ); + }); + + describe('getRequiredDependencies method', () => { + it('should return the provided result if an array', inject([SchemaService], (service: SchemaService) => { + expect(service.getRequiredDependencies(['foo', 'bar'])).toEqual(['foo', 'bar']); + })); + + it('should return the content of the required attribute if provided', inject([SchemaService], (service: SchemaService) => { + expect(service.getRequiredDependencies({required: ['foo', 'bar'] })).toEqual(['foo', 'bar']); + })); + + it('should return an empty array if not provided with required property', inject([SchemaService], (service: SchemaService) => { + expect(service.getRequiredDependencies({ foo: 'bar' })).toEqual([]); + })); }); }); diff --git a/ui/src/app/schema-form/service/schema.service.ts b/ui/src/app/schema-form/service/schema.service.ts index 2114ff283..9cd7346de 100644 --- a/ui/src/app/schema-form/service/schema.service.ts +++ b/ui/src/app/schema-form/service/schema.service.ts @@ -18,6 +18,7 @@ export class SchemaService { if (!formProperty || !formProperty.parent) { return false; } + let requiredFields = formProperty.parent.schema.required || []; let fieldPath = formProperty.path; let controlName = fieldPath.substr(fieldPath.lastIndexOf('/') + 1); @@ -38,6 +39,28 @@ export class SchemaService { required = !required ? requiredFields.indexOf(controlName) > -1 : required; }); } + + if (!required && formProperty.parent instanceof Object) { + const parent = formProperty.parent; + const dependencies = parent.schema.dependencies; + if (dependencies) { + const isDependencyOf = Object.keys(dependencies).filter(d => { + let dep = dependencies[d]; + return this.getRequiredDependencies(dep); + }); + const hasActiveDependencies = dependencies.hasOwnProperty(controlName) && + this.getRequiredDependencies(dependencies[controlName]).filter( + d => parent.value.hasOwnProperty(d) + ); + const isRequired = isDependencyOf.some(d => parent.value.hasOwnProperty(d) && !!parent.value[d]); + required = isRequired || !!hasActiveDependencies.length; + } + } + return required; } + + getRequiredDependencies(dep: any): string[] { + return (dep instanceof Array) ? dep : dep.hasOwnProperty('required') ? dep.required : []; + } } diff --git a/ui/src/app/schema-form/widget/array/array.component.ts b/ui/src/app/schema-form/widget/array/array.component.ts index f47beeca5..520915baa 100644 --- a/ui/src/app/schema-form/widget/array/array.component.ts +++ b/ui/src/app/schema-form/widget/array/array.component.ts @@ -28,7 +28,7 @@ export class CustomArrayComponent extends ArrayWidget implements AfterViewInit, ngAfterViewInit(): void { this.errors$ = this.formProperty.errorsChanges.pipe( - map(errors => errors ? errors.reduce((coll, err) => { + map(errors => errors ? errors.filter(err => err.code !== 'UNRESOLVABLE_REFERENCE').reduce((coll, err) => { coll[err.code] = err; return coll; }, {}) : {}), diff --git a/ui/src/app/schema-form/widget/object/object.component.ts b/ui/src/app/schema-form/widget/object/object.component.ts index 8850e7a74..54445c30e 100644 --- a/ui/src/app/schema-form/widget/object/object.component.ts +++ b/ui/src/app/schema-form/widget/object/object.component.ts @@ -7,4 +7,4 @@ import { ObjectWidget } from 'ngx-schema-form'; selector: 'custom-object', templateUrl: `./object.component.html` }) -export class CustomObjectWidget extends ObjectWidget { } +export class CustomObjectWidget extends ObjectWidget {} diff --git a/ui/src/app/schema-form/widget/string/string.component.html b/ui/src/app/schema-form/widget/string/string.component.html index e8dd0001a..26964a59f 100644 --- a/ui/src/app/schema-form/widget/string/string.component.html +++ b/ui/src/app/schema-form/widget/string/string.component.html @@ -1,7 +1,9 @@