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 @@