Adding the ability to set formula response type - returning native values.

This commit is contained in:
mike12345567 2024-11-07 15:53:04 +00:00
parent 888820a0de
commit 7c2bfec22c
5 changed files with 79 additions and 15 deletions

View File

@ -371,6 +371,7 @@
delete editableColumn.relationshipType delete editableColumn.relationshipType
delete editableColumn.formulaType delete editableColumn.formulaType
delete editableColumn.constraints delete editableColumn.constraints
delete editableColumn.responseType
// Add in defaults and initial definition // Add in defaults and initial definition
const definition = fieldDefinitions[type?.toUpperCase()] const definition = fieldDefinitions[type?.toUpperCase()]
@ -386,6 +387,7 @@
editableColumn.relationshipType = RelationshipType.MANY_TO_MANY editableColumn.relationshipType = RelationshipType.MANY_TO_MANY
} else if (editableColumn.type === FieldType.FORMULA) { } else if (editableColumn.type === FieldType.FORMULA) {
editableColumn.formulaType = "dynamic" editableColumn.formulaType = "dynamic"
editableColumn.responseType = FIELDS.STRING.type
} }
} }
@ -767,6 +769,25 @@
</div> </div>
</div> </div>
{/if} {/if}
<div class="split-label">
<div class="label-length">
<Label size="M">Response Type</Label>
</div>
<div class="input-length">
<Select
bind:value={editableColumn.responseType}
options={[
FIELDS.STRING,
FIELDS.NUMBER,
FIELDS.BOOLEAN,
FIELDS.DATETIME,
]}
getOptionLabel={option => option.name}
getOptionValue={option => option.type}
tooltip="Formulas by default will return a string - however if you need a native type the response can be coerced."
/>
</div>
</div>
<div class="split-label"> <div class="split-label">
<div class="label-length"> <div class="label-length">
<Label size="M">Formula</Label> <Label size="M">Formula</Label>

View File

@ -1,5 +1,21 @@
<script> <script>
import TextCell from "./TextCell.svelte" import TextCell from "./TextCell.svelte"
import DateCell from "./DateCell.svelte"
import NumberCell from "./NumberCell.svelte"
import BooleanCell from "./BooleanCell.svelte"
import { FieldType } from "@budibase/types"
export let schema
$: responseType = schema.responseType
</script> </script>
<TextCell {...$$props} readonly /> {#if responseType === FieldType.NUMBER}
<NumberCell {...$$props} readonly />
{:else if responseType === FieldType.BOOLEAN}
<BooleanCell {...$$props} readonly />
{:else if responseType === FieldType.DATETIME}
<DateCell {...$$props} readonly />
{:else}
<TextCell {...$$props} readonly />
{/if}

View File

@ -163,33 +163,33 @@ async function processDefaultValues(table: Table, row: Row) {
/** /**
* This will coerce a value to the correct types based on the type transform map * This will coerce a value to the correct types based on the type transform map
* @param row The value to coerce * @param value The value to coerce
* @param type The type fo coerce to * @param type The type fo coerce to
* @returns The coerced value * @returns The coerced value
*/ */
export function coerce(row: any, type: string) { export function coerce(value: unknown, type: string) {
// no coercion specified for type, skip it // no coercion specified for type, skip it
if (!TYPE_TRANSFORM_MAP[type]) { if (!TYPE_TRANSFORM_MAP[type]) {
return row return value
} }
// eslint-disable-next-line no-prototype-builtins // eslint-disable-next-line no-prototype-builtins
if (TYPE_TRANSFORM_MAP[type].hasOwnProperty(row)) { if (TYPE_TRANSFORM_MAP[type].hasOwnProperty(value)) {
// @ts-ignore // @ts-ignore
return TYPE_TRANSFORM_MAP[type][row] return TYPE_TRANSFORM_MAP[type][value]
} else if (TYPE_TRANSFORM_MAP[type].parse) { } else if (TYPE_TRANSFORM_MAP[type].parse) {
// @ts-ignore // @ts-ignore
return TYPE_TRANSFORM_MAP[type].parse(row) return TYPE_TRANSFORM_MAP[type].parse(value)
} }
return row return value
} }
/** /**
* Given an input route this function will apply all the necessary pre-processing to it, such as coercion * Given an input route this function will apply all the necessary pre-processing to it, such as coercion
* of column values or adding auto-column values. * of column values or adding auto-column values.
* @param user the user which is performing the input. * @param userId the ID of the user which is performing the input.
* @param row the row which is being created/updated. * @param row the row which is being created/updated.
* @param table the table which the row is being saved to. * @param source the table/view which the row is being saved to.
* @param opts some input processing options (like disabling auto-column relationships). * @param opts some input processing options (like disabling auto-column relationships).
* @returns the row which has been prepared to be written to the DB. * @returns the row which has been prepared to be written to the DB.
*/ */

View File

@ -10,11 +10,13 @@ import {
FieldType, FieldType,
OperationFieldTypeEnum, OperationFieldTypeEnum,
AIOperationEnum, AIOperationEnum,
AIFieldMetadata,
} from "@budibase/types" } from "@budibase/types"
import { OperationFields } from "@budibase/shared-core" import { OperationFields } from "@budibase/shared-core"
import tracer from "dd-trace" import tracer from "dd-trace"
import { context } from "@budibase/backend-core" import { context } from "@budibase/backend-core"
import * as pro from "@budibase/pro" import * as pro from "@budibase/pro"
import { coerce } from "./index"
interface FormulaOpts { interface FormulaOpts {
dynamic?: boolean dynamic?: boolean
@ -67,7 +69,18 @@ export async function processFormulas<T extends Row | Row[]>(
continue continue
} }
const responseType = schema.responseType
const isStatic = schema.formulaType === FormulaType.STATIC const isStatic = schema.formulaType === FormulaType.STATIC
const formula = schema.formula
// coerce static values
if (isStatic) {
rows.forEach(row => {
if (row[column] && responseType) {
row[column] = coerce(row[column], responseType)
}
})
}
if ( if (
schema.formula == null || schema.formula == null ||
@ -80,12 +93,17 @@ export async function processFormulas<T extends Row | Row[]>(
for (let i = 0; i < rows.length; i++) { for (let i = 0; i < rows.length; i++) {
let row = rows[i] let row = rows[i]
let context = contextRows ? contextRows[i] : row let context = contextRows ? contextRows[i] : row
let formula = schema.formula
rows[i] = { rows[i] = {
...row, ...row,
[column]: tracer.trace("processStringSync", {}, span => { [column]: tracer.trace("processStringSync", {}, span => {
span?.addTags({ table_id: table._id, column, static: isStatic }) span?.addTags({ table_id: table._id, column, static: isStatic })
return processStringSync(formula, context) const result = processStringSync(formula, context)
try {
return responseType ? coerce(result, responseType) : result
} catch (err) {
// if the coercion fails, we return empty row contents
return undefined
}
}), }),
} }
} }
@ -117,12 +135,13 @@ export async function processAIColumns<T extends Row | Row[]>(
continue continue
} }
const operation = schema.operation
const aiSchema: AIFieldMetadata = schema
const rowUpdates = rows.map((row, i) => { const rowUpdates = rows.map((row, i) => {
const contextRow = contextRows ? contextRows[i] : row const contextRow = contextRows ? contextRows[i] : row
// Check if the type is bindable and pass through HBS if so // Check if the type is bindable and pass through HBS if so
const operationField = const operationField = OperationFields[operation as AIOperationEnum]
OperationFields[schema.operation as AIOperationEnum]
for (const key in schema) { for (const key in schema) {
const fieldType = operationField[key as keyof typeof operationField] const fieldType = operationField[key as keyof typeof operationField]
if (fieldType === OperationFieldTypeEnum.BINDABLE_TEXT) { if (fieldType === OperationFieldTypeEnum.BINDABLE_TEXT) {
@ -131,7 +150,10 @@ export async function processAIColumns<T extends Row | Row[]>(
} }
} }
const prompt = llm.buildPromptFromAIOperation({ schema, row }) const prompt = llm.buildPromptFromAIOperation({
schema: aiSchema,
row,
})
return tracer.trace("processAIColumn", {}, async span => { return tracer.trace("processAIColumn", {}, async span => {
span?.addTags({ table_id: table._id, column }) span?.addTags({ table_id: table._id, column })

View File

@ -115,6 +115,11 @@ export interface FormulaFieldMetadata extends BaseFieldSchema {
type: FieldType.FORMULA type: FieldType.FORMULA
formula: string formula: string
formulaType?: FormulaType formulaType?: FormulaType
responseType?:
| FieldType.STRING
| FieldType.NUMBER
| FieldType.BOOLEAN
| FieldType.DATETIME
} }
export interface AIFieldMetadata extends BaseFieldSchema { export interface AIFieldMetadata extends BaseFieldSchema {