diff --git a/frontend/src/metabase-lib/lib/metadata/Field.ts b/frontend/src/metabase-lib/lib/metadata/Field.ts index ac11d50174d27..a6585472faf98 100644 --- a/frontend/src/metabase-lib/lib/metadata/Field.ts +++ b/frontend/src/metabase-lib/lib/metadata/Field.ts @@ -48,6 +48,7 @@ class FieldInner extends Base { name: string; description: string | null; semantic_type: string | null; + database_required: boolean; fingerprint?: FieldFingerprint; base_type: string | null; table?: Table; diff --git a/frontend/src/metabase/writeback/containers/WritebackForm.tsx b/frontend/src/metabase/writeback/containers/WritebackForm.tsx index 1f0414346d4e3..e8ee28b08998a 100644 --- a/frontend/src/metabase/writeback/containers/WritebackForm.tsx +++ b/frontend/src/metabase/writeback/containers/WritebackForm.tsx @@ -3,11 +3,13 @@ import { t } from "ttag"; import Form from "metabase/containers/Form"; +import validate from "metabase/lib/validate"; import { TYPE } from "metabase/lib/types"; import Field from "metabase-lib/lib/metadata/Field"; import Table from "metabase-lib/lib/metadata/Table"; +import { isEditableField } from "../utils"; import CategoryFieldPicker from "./CategoryFieldPicker"; export interface WritebackFormProps { @@ -55,23 +57,41 @@ function getFieldTypeProps(field: Field) { return { type: "input" }; } +function getFieldValidationProp(field: Field) { + let validator = validate as any; + + if (field.database_required) { + validator = validator.required(); + } + + return { + validate: validator, + }; +} + function WritebackForm({ table, row, onSubmit, ...props }: WritebackFormProps) { - const editableFields = useMemo( - () => table.fields.filter(field => field.id && !field.isPK()), - [table], - ); + const editableFields = useMemo(() => table.fields.filter(isEditableField), [ + table, + ]); const form = useMemo(() => { return { fields: editableFields.map(field => { const fieldIndex = table.fields.findIndex(f => f.id === field.id); const initialValue = row ? row[fieldIndex] : undefined; + + let title = field.displayName(); + if (field.database_required) { + title += " (" + t`required` + ")"; + } + return { name: field.name, - title: field.displayName(), + title, description: field.description, initial: initialValue, ...getFieldTypeProps(field), + ...getFieldValidationProp(field), }; }), }; diff --git a/frontend/src/metabase/writeback/utils.ts b/frontend/src/metabase/writeback/utils.ts index d2bc00f687f33..6c743ff396ad9 100644 --- a/frontend/src/metabase/writeback/utils.ts +++ b/frontend/src/metabase/writeback/utils.ts @@ -1,6 +1,7 @@ import { getTemplateTagParameterTarget } from "metabase/parameters/utils/cards"; import Database from "metabase-lib/lib/metadata/Database"; +import Field from "metabase-lib/lib/metadata/Field"; import { Database as IDatabase } from "metabase-types/types/Database"; import { DashCard } from "metabase-types/types/Dashboard"; @@ -17,6 +18,23 @@ export const isDatabaseWritebackEnabled = (database?: IDatabase | null) => export const isWritebackSupported = (database?: Database | null) => !!database?.hasFeature(DB_WRITEBACK_FEATURE); +export const isEditableField = (field: Field) => { + const isRealField = typeof field.id === "number"; + if (!isRealField) { + // Filters out custom, aggregated columns, etc. + return false; + } + + if (field.isPK()) { + // Most of the time PKs are auto-generated, + // but there are rare cases when they're not + // In this case they're marked as `database_required` + return field.database_required; + } + + return true; +}; + export const isActionButtonDashCard = (dashCard: DashCard) => dashCard.visualization_settings?.virtual_card?.display === "action-button";