From 4248b187bb35e38624a95326bf3203a03ec99f4f Mon Sep 17 00:00:00 2001 From: Rijk van Zanten Date: Wed, 10 Mar 2021 17:35:21 -0500 Subject: [PATCH] Implement unique constraint support (#4467) * Allow creating unique fields * Allow removing unique constriant * Show unique constraint error as validation error in app --- api/src/services/fields.ts | 6 ++++++ app/src/components/v-form/form-field.vue | 13 +++++++++++-- app/src/components/v-form/types.ts | 1 + app/src/composables/use-item/use-item.ts | 6 ++++-- app/src/lang/translations/en-US.yaml | 2 ++ .../data-model/field-detail/components/schema.vue | 11 ++++++++++- .../routes/data-model/field-detail/store.ts | 1 + 7 files changed, 35 insertions(+), 5 deletions(-) diff --git a/api/src/services/fields.ts b/api/src/services/fields.ts index 31294d657d..c9efa5815c 100644 --- a/api/src/services/fields.ts +++ b/api/src/services/fields.ts @@ -368,6 +368,12 @@ export class FieldsService { column.nullable(); } + if (field.schema?.is_unique === true) { + column.unique(); + } else if (field.schema?.is_unique === false && alter === true) { + table.dropUnique([field.field]); + } + if (field.schema?.is_primary_key) { column.primary(); } diff --git a/app/src/components/v-form/form-field.vue b/app/src/components/v-form/form-field.vue index 3c2043b6eb..84bd899eed 100644 --- a/app/src/components/v-form/form-field.vue +++ b/app/src/components/v-form/form-field.vue @@ -54,7 +54,7 @@ - {{ $t(`validationError.${validationError.type}`, validationError) }} + {{ validationMessage }} @@ -69,6 +69,7 @@ import FormFieldInterface from './form-field-interface.vue'; import { ValidationError } from './types'; import { getJSType } from '@/utils/get-js-type'; import { isEqual } from 'lodash'; +import { i18n } from '@/lang'; export default defineComponent({ components: { FormFieldLabel, FormFieldMenu, FormFieldInterface }, @@ -133,7 +134,15 @@ export default defineComponent({ const { showRaw, rawValue } = useRaw(); - return { isDisabled, marked, _value, emitValue, showRaw, rawValue }; + const validationMessage = computed(() => { + if (props.validationError.code === 'RECORD_NOT_UNIQUE') { + return i18n.t('validationError.unique'); + } else { + return i18n.t(`validationError.${props.validationError.type}`, props.validationError); + } + }); + + return { isDisabled, marked, _value, emitValue, showRaw, rawValue, validationMessage }; function emitValue(value: any) { if ( diff --git a/app/src/components/v-form/types.ts b/app/src/components/v-form/types.ts index 0ea885e714..8a2f8932a2 100644 --- a/app/src/components/v-form/types.ts +++ b/app/src/components/v-form/types.ts @@ -9,6 +9,7 @@ export type FormField = DeepPartial & { }; export type ValidationError = { + code: string; field: string; type: FilterOperator; valid?: number | string | (number | string)[]; diff --git a/app/src/composables/use-item/use-item.ts b/app/src/composables/use-item/use-item.ts index 00ba8c316a..1e0efb67a5 100644 --- a/app/src/composables/use-item/use-item.ts +++ b/app/src/composables/use-item/use-item.ts @@ -110,14 +110,16 @@ export function useItem(collection: Ref, primaryKey: Ref err?.extensions?.code === 'FAILED_VALIDATION') + .filter((err: APIError) => validationTypes.includes(err?.extensions?.code)) .map((err: APIError) => { return err.extensions; }); const otherErrors = err.response.data.errors.filter( - (err: APIError) => err?.extensions?.code !== 'FAILED_VALIDATION' + (err: APIError) => validationTypes.includes(err?.extensions?.code) === false ); if (otherErrors.length > 0) { diff --git a/app/src/lang/translations/en-US.yaml b/app/src/lang/translations/en-US.yaml index e348ccf6e0..af4dc8702e 100644 --- a/app/src/lang/translations/en-US.yaml +++ b/app/src/lang/translations/en-US.yaml @@ -82,6 +82,7 @@ validationError: null: Value has to be null nnull: Value can't be null required: Value is required + unique: Value has to be unique all_access: All Access no_access: No Access use_custom: Use Custom @@ -390,6 +391,7 @@ errors: ITEM_LIMIT_REACHED: Item limit reached ITEM_NOT_FOUND: Item not found ROUTE_NOT_FOUND: Not found + RECORD_NOT_UNIQUE: Duplicate value detected UNKNOWN: Unexpected Error value_hashed: Value Securely Hashed bookmark_name: Bookmark name... diff --git a/app/src/modules/settings/routes/data-model/field-detail/components/schema.vue b/app/src/modules/settings/routes/data-model/field-detail/components/schema.vue index 1b866468bb..0a102929b8 100644 --- a/app/src/modules/settings/routes/data-model/field-detail/components/schema.vue +++ b/app/src/modules/settings/routes/data-model/field-detail/components/schema.vue @@ -1,6 +1,5 @@ diff --git a/app/src/modules/settings/routes/data-model/field-detail/store.ts b/app/src/modules/settings/routes/data-model/field-detail/store.ts index 90940bce2f..d532c563a3 100644 --- a/app/src/modules/settings/routes/data-model/field-detail/store.ts +++ b/app/src/modules/settings/routes/data-model/field-detail/store.ts @@ -43,6 +43,7 @@ function initLocalStore(collection: string, field: string, type: typeof localTyp default_value: undefined, max_length: undefined, is_nullable: true, + is_unique: false, numeric_precision: null, numeric_scale: null, },