Skip to content

Commit 108c3ce

Browse files
authored
Advanced formula improvements (baserow#4144)
* Formula should be considered invalid if it can't be resolved. * Add boolean argument type * Improve formula validation * Improve validators. * Lint fix * Add changelog * Clean up import
1 parent 8832606 commit 108c3ce

File tree

12 files changed

+158
-30
lines changed

12 files changed

+158
-30
lines changed

backend/src/baserow/core/apps.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def ready(self):
4949
RuntimeGreaterThan,
5050
RuntimeGreaterThanOrEqual,
5151
RuntimeHour,
52+
RuntimeIf,
5253
RuntimeIsEven,
5354
RuntimeIsOdd,
5455
RuntimeLessThan,
@@ -100,6 +101,7 @@ def ready(self):
100101
formula_runtime_function_registry.register(RuntimeRandomFloat())
101102
formula_runtime_function_registry.register(RuntimeRandomBool())
102103
formula_runtime_function_registry.register(RuntimeGenerateUUID())
104+
formula_runtime_function_registry.register(RuntimeIf())
103105

104106
from baserow.core.permission_manager import (
105107
AllowIfTemplatePermissionManagerType,

backend/src/baserow/core/formula/argument_types.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from django.core.exceptions import ValidationError
22

33
from baserow.core.formula.validator import (
4+
ensure_boolean,
45
ensure_datetime,
56
ensure_numeric,
67
ensure_object,
@@ -78,3 +79,11 @@ def test(self, value):
7879

7980
def parse(self, value):
8081
return ensure_object(value)
82+
83+
84+
class BooleanBaserowRuntimeFormulaArgumentType(BaserowRuntimeFormulaArgumentType):
85+
def test(self, value):
86+
return isinstance(value, bool)
87+
88+
def parse(self, value):
89+
return ensure_boolean(value)

backend/src/baserow/core/formula/runtime_formula_types.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from baserow.core.formula.argument_types import (
1111
AddableBaserowRuntimeFormulaArgumentType,
12+
BooleanBaserowRuntimeFormulaArgumentType,
1213
DateTimeBaserowRuntimeFormulaArgumentType,
1314
DictBaserowRuntimeFormulaArgumentType,
1415
NumberBaserowRuntimeFormulaArgumentType,
@@ -396,3 +397,17 @@ class RuntimeGenerateUUID(RuntimeFormulaFunction):
396397

397398
def execute(self, context: FormulaContext, args: FormulaArgs):
398399
return str(uuid.uuid4())
400+
401+
402+
class RuntimeIf(RuntimeFormulaFunction):
403+
type = "if"
404+
405+
def validate_type_of_args(self, args) -> Optional[FormulaArg]:
406+
arg_type = BooleanBaserowRuntimeFormulaArgumentType()
407+
if not arg_type.test(args[0]):
408+
return args[0]
409+
410+
return None
411+
412+
def execute(self, context: FormulaContext, args: FormulaArgs):
413+
return args[1] if args[0] else args[2]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "refactor",
3+
"message": "Added support for Advanced Formulas.",
4+
"domain": "builder",
5+
"issue_number": 3258,
6+
"issue_origin": "github",
7+
"bullet_points": [],
8+
"created_at": "2025-10-30"
9+
}

web-frontend/locales/en.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -671,11 +671,13 @@
671671
"randomFloatDescription": "Returns a random float from the range specified by the arguments.",
672672
"randomBoolDescription": "Returns a random boolean of true or false.",
673673
"generateUUIDDescription": "Returns a random UUID4 string.",
674+
"ifDescription": "If the first argument is true, returns the second argument, otherwise returns the third argument.",
674675
"formulaTypeFormula": "Formula",
675676
"formulaTypeOperator": "Operator",
676677
"categoryText": "Text",
677678
"categoryNumber": "Number",
678679
"categoryBoolean": "Boolean",
679-
"categoryDate": "Date"
680+
"categoryDate": "Date",
681+
"caregoryCondition": "Condition"
680682
}
681683
}

web-frontend/modules/core/components/formula/FormulaInputField.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,8 @@ export default {
455455
}
456456
},
457457
emitAdvancedChange() {
458-
if (isFormulaValid(this.advancedFormulaValue)) {
458+
const functions = new RuntimeFunctionCollection(this.$registry)
459+
if (isFormulaValid(this.advancedFormulaValue, functions)) {
459460
this.isFormulaInvalid = false
460461
this.$emit('input', this.advancedFormulaValue)
461462
} else {

web-frontend/modules/core/enums.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,8 @@ export const FORMULA_CATEGORY = {
7676
category: 'categoryFile',
7777
iconClass: 'baserow-icon-file',
7878
},
79+
CONDITION: {
80+
category: 'categoryCondition',
81+
iconClass: 'iconoir-code-brackets-square',
82+
},
7983
}

web-frontend/modules/core/formula/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,14 @@ export const resolveFormula = (
3232
}
3333
}
3434

35-
export const isFormulaValid = (formula) => {
35+
export const isFormulaValid = (formula, functions) => {
3636
if (!formula) {
3737
return true
3838
}
3939

4040
try {
41-
parseBaserowFormula(formula)
41+
const tree = parseBaserowFormula(formula)
42+
new JavascriptExecutor(functions).visit(tree)
4243
return true
4344
} catch (err) {
4445
return false

web-frontend/modules/core/plugin.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ import {
108108
RuntimeRound,
109109
RuntimeIsEven,
110110
RuntimeIsOdd,
111-
RuntimeDateTimeFormat,
112111
RuntimeDay,
113112
RuntimeMonth,
114113
RuntimeYear,
@@ -122,6 +121,7 @@ import {
122121
RuntimeRandomFloat,
123122
RuntimeRandomBool,
124123
RuntimeGenerateUUID,
124+
RuntimeIf,
125125
} from '@baserow/modules/core/runtimeFormulaTypes'
126126

127127
import priorityBus from '@baserow/modules/core/plugins/priorityBus'
@@ -282,10 +282,6 @@ export default (context, inject) => {
282282
registry.register('runtimeFormulaFunction', new RuntimeRound(context))
283283
registry.register('runtimeFormulaFunction', new RuntimeIsEven(context))
284284
registry.register('runtimeFormulaFunction', new RuntimeIsOdd(context))
285-
registry.register(
286-
'runtimeFormulaFunction',
287-
new RuntimeDateTimeFormat(context)
288-
)
289285
registry.register('runtimeFormulaFunction', new RuntimeDay(context))
290286
registry.register('runtimeFormulaFunction', new RuntimeMonth(context))
291287
registry.register('runtimeFormulaFunction', new RuntimeYear(context))
@@ -299,6 +295,7 @@ export default (context, inject) => {
299295
registry.register('runtimeFormulaFunction', new RuntimeRandomFloat(context))
300296
registry.register('runtimeFormulaFunction', new RuntimeRandomBool(context))
301297
registry.register('runtimeFormulaFunction', new RuntimeGenerateUUID(context))
298+
registry.register('runtimeFormulaFunction', new RuntimeIf(context))
302299

303300
registry.register('roles', new AdminRoleType(context))
304301
registry.register('roles', new MemberRoleType(context))

web-frontend/modules/core/runtimeFormulaArgumentTypes.js

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
ensureNumeric,
44
ensureDateTime,
55
ensureObject,
6+
ensureBoolean,
67
} from '@baserow/modules/core/utils/validator'
78

89
export class BaserowRuntimeFormulaArgumentType {
@@ -51,20 +52,47 @@ export class TextBaserowRuntimeFormulaArgumentType extends BaserowRuntimeFormula
5152

5253
export class DateTimeBaserowRuntimeFormulaArgumentType extends BaserowRuntimeFormulaArgumentType {
5354
test(value) {
54-
return value instanceof Date
55+
if (value instanceof Date) {
56+
return true
57+
}
58+
try {
59+
ensureDateTime(value, { useStrict: false })
60+
return true
61+
} catch (e) {
62+
return false
63+
}
5564
}
5665

5766
parse(value) {
58-
return ensureDateTime(value)
67+
return ensureDateTime(value, { useStrict: false })
5968
}
6069
}
6170

6271
export class ObjectBaserowRuntimeFormulaArgumentType extends BaserowRuntimeFormulaArgumentType {
6372
test(value) {
64-
return value instanceof Object
73+
if (value instanceof Object) {
74+
return true
75+
}
76+
77+
try {
78+
ensureObject(value)
79+
return true
80+
} catch (e) {
81+
return false
82+
}
6583
}
6684

6785
parse(value) {
6886
return ensureObject(value)
6987
}
7088
}
89+
90+
export class BooleanBaserowRuntimeFormulaArgumentType extends BaserowRuntimeFormulaArgumentType {
91+
test(value) {
92+
return typeof value === 'boolean'
93+
}
94+
95+
parse(value) {
96+
return ensureBoolean(value)
97+
}
98+
}

0 commit comments

Comments
 (0)