From d5b420be9c26f1c5387252b4ef629b15b124102b Mon Sep 17 00:00:00 2001 From: Drew Ansbacher Date: Wed, 18 Aug 2021 17:42:30 -0600 Subject: [PATCH] commit --- .../form-object/form-error.component.html | 2 +- ui/src/app/services/form.service.ts | 85 +++++++++++++++++-- 2 files changed, 81 insertions(+), 6 deletions(-) diff --git a/ui/src/app/components/form-object/form-error.component.html b/ui/src/app/components/form-object/form-error.component.html index 7cfbedf30..d8a3a8681 100644 --- a/ui/src/app/components/form-object/form-error.component.html +++ b/ui/src/app/components/form-object/form-error.component.html @@ -25,7 +25,7 @@ List not in range

- {{ spec['pattern-description'] }} + {{ control.errors.listNotUnique.value }}

\ No newline at end of file diff --git a/ui/src/app/services/form.service.ts b/ui/src/app/services/form.service.ts index c17a365e6..5d4bdf77a 100644 --- a/ui/src/app/services/form.service.ts +++ b/ui/src/app/services/form.service.ts @@ -1,7 +1,8 @@ import { Injectable } from '@angular/core' import { AbstractControl, FormArray, FormBuilder, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms' -import { ConfigSpec, isValueSpecListOf, ListValueSpecNumber, ListValueSpecObject, ListValueSpecString, ListValueSpecUnion, UniqueBy, ValueSpec, ValueSpecEnum, ValueSpecList, ValueSpecNumber, ValueSpecObject, ValueSpecString, ValueSpecUnion } from '../pkg-config/config-types' +import { ConfigSpec, isValueSpecListOf, ListValueSpecNumber, ListValueSpecObject, ListValueSpecOf, ListValueSpecString, ListValueSpecUnion, UniqueBy, ValueSpec, ValueSpecEnum, ValueSpecList, ValueSpecListOf, ValueSpecNumber, ValueSpecObject, ValueSpecString, ValueSpecUnion } from '../pkg-config/config-types' import { getDefaultString, Range } from '../pkg-config/config-utilities' +const Mustache = require('mustache') @Injectable({ providedIn: 'root', @@ -186,7 +187,20 @@ export function listUnique (spec: ValueSpecList): ValidatorFn { for (let idx = 0; idx < control.value.length; idx++) { for (let idx2 = idx + 1; idx2 < control.value.length; idx2++) { if (listItemEquals(spec, control.value[idx], control.value[idx2])) { - return { listNotUnique: { value: control.value } } + let display1: string + let display2: string + let uniqueMessage = isObjectOrUnion(spec.spec) ? uniqueByMessageWrapper(spec.spec['unique-by'], spec.spec, control.value[idx]) : '' + + + if (isObjectOrUnion(spec.spec) && spec.spec['display-as']) { + display1 = `"${(Mustache as any).render(spec.spec['display-as'], control.value[idx])}"` + display2 = `"${(Mustache as any).render(spec.spec['display-as'], control.value[idx2])}"` + } else { + display1 = `Entry ${idx + 1}` + display2 = `Entry ${idx2 + 1}` + } + + return { listNotUnique: { value: `${display1} and ${display2} are not unique.${uniqueMessage}` } } } } } @@ -239,8 +253,7 @@ function listObjEquals (uniqueBy: UniqueBy, spec: ListValueSpecObject, val1: any if (uniqueBy === null) { return false } else if (typeof uniqueBy === 'string') { - const item = itemEquals(spec.spec[uniqueBy], val1[uniqueBy], val2[uniqueBy]) - return item + return itemEquals(spec.spec[uniqueBy], val1[uniqueBy], val2[uniqueBy]) } else if ('any' in uniqueBy) { for (let subSpec of uniqueBy.any) { if (listObjEquals(subSpec, spec, val1, val2)) { @@ -306,4 +319,66 @@ function unionEquals (uniqueBy: UniqueBy, spec: ValueSpecUnion | ListValueSpecUn } return true } -} \ No newline at end of file +} + +function uniqueByMessageWrapper (uniqueBy: UniqueBy, spec: ListValueSpecObject | ListValueSpecUnion, obj: object) { + let configSpec: ConfigSpec + if (isUnion(spec)) { + const variantKey = obj[spec.tag.id] + configSpec = spec.variants[variantKey] + } else { + configSpec = spec.spec + } + + const message = uniqueByMessage(uniqueBy, configSpec) + if (message) { + return ' Must be unique by: ' + message + '.' + } +} + +function uniqueByMessage (uniqueBy: UniqueBy, configSpec: ConfigSpec, outermost = true): string { + let joinFunc + const subSpecs = [] + if (uniqueBy === null) { + return null + } else if (typeof uniqueBy === 'string') { + return configSpec[uniqueBy] ? configSpec[uniqueBy].name : uniqueBy + } else if ('any' in uniqueBy) { + joinFunc = ' OR ' + for (let subSpec of uniqueBy.any) { + subSpecs.push(uniqueByMessage(subSpec, configSpec, false)) + } + } else if ('all' in uniqueBy) { + joinFunc = ' AND ' + for (let subSpec of uniqueBy.all) { + subSpecs.push(uniqueByMessage(subSpec, configSpec, false)) + } + } + const ret = subSpecs.filter(ss => ss).join(joinFunc) + return outermost || subSpecs.filter(ss => ss).length === 1 ? ret : '(' + ret + ')' +} + +function isObjectOrUnion (spec: any): spec is ListValueSpecObject | ListValueSpecUnion { + // only lists of objects and unions have unique-by + return spec['unique-by'] !== undefined +} + +function isUnion (spec: any): spec is ListValueSpecUnion { + // only unions have tag + return !!spec.tag +} + +const sampleUniqueBy: UniqueBy = { + all: [ + 'last name', + { any: [ + 'favorite color', + null, + ] }, + { any: [ + 'favorite color', + 'first name', + null, + ] }, + ], +}