Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/src/baserow/core/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def ready(self):
RuntimeGreaterThan,
RuntimeGreaterThanOrEqual,
RuntimeHour,
RuntimeIf,
RuntimeIsEven,
RuntimeIsOdd,
RuntimeLessThan,
Expand Down Expand Up @@ -100,6 +101,7 @@ def ready(self):
formula_runtime_function_registry.register(RuntimeRandomFloat())
formula_runtime_function_registry.register(RuntimeRandomBool())
formula_runtime_function_registry.register(RuntimeGenerateUUID())
formula_runtime_function_registry.register(RuntimeIf())

from baserow.core.permission_manager import (
AllowIfTemplatePermissionManagerType,
Expand Down
9 changes: 9 additions & 0 deletions backend/src/baserow/core/formula/argument_types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.core.exceptions import ValidationError

from baserow.core.formula.validator import (
ensure_boolean,
ensure_datetime,
ensure_numeric,
ensure_object,
Expand Down Expand Up @@ -78,3 +79,11 @@ def test(self, value):

def parse(self, value):
return ensure_object(value)


class BooleanBaserowRuntimeFormulaArgumentType(BaserowRuntimeFormulaArgumentType):
def test(self, value):
return isinstance(value, bool)

def parse(self, value):
return ensure_boolean(value)
15 changes: 15 additions & 0 deletions backend/src/baserow/core/formula/runtime_formula_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from baserow.core.formula.argument_types import (
AddableBaserowRuntimeFormulaArgumentType,
BooleanBaserowRuntimeFormulaArgumentType,
DateTimeBaserowRuntimeFormulaArgumentType,
DictBaserowRuntimeFormulaArgumentType,
NumberBaserowRuntimeFormulaArgumentType,
Expand Down Expand Up @@ -396,3 +397,17 @@ class RuntimeGenerateUUID(RuntimeFormulaFunction):

def execute(self, context: FormulaContext, args: FormulaArgs):
return str(uuid.uuid4())


class RuntimeIf(RuntimeFormulaFunction):
type = "if"

def validate_type_of_args(self, args) -> Optional[FormulaArg]:
arg_type = BooleanBaserowRuntimeFormulaArgumentType()
if not arg_type.test(args[0]):
return args[0]

return None

def execute(self, context: FormulaContext, args: FormulaArgs):
return args[1] if args[0] else args[2]
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "refactor",
"message": "Added support for Advanced Formulas.",
"domain": "builder",
"issue_number": 3258,
"issue_origin": "github",
"bullet_points": [],
"created_at": "2025-10-30"
}
4 changes: 3 additions & 1 deletion web-frontend/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -671,11 +671,13 @@
"randomFloatDescription": "Returns a random float from the range specified by the arguments.",
"randomBoolDescription": "Returns a random boolean of true or false.",
"generateUUIDDescription": "Returns a random UUID4 string.",
"ifDescription": "If the first argument is true, returns the second argument, otherwise returns the third argument.",
"formulaTypeFormula": "Formula",
"formulaTypeOperator": "Operator",
"categoryText": "Text",
"categoryNumber": "Number",
"categoryBoolean": "Boolean",
"categoryDate": "Date"
"categoryDate": "Date",
"caregoryCondition": "Condition"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,8 @@ export default {
}
},
emitAdvancedChange() {
if (isFormulaValid(this.advancedFormulaValue)) {
const functions = new RuntimeFunctionCollection(this.$registry)
if (isFormulaValid(this.advancedFormulaValue, functions)) {
this.isFormulaInvalid = false
this.$emit('input', this.advancedFormulaValue)
} else {
Expand Down
4 changes: 4 additions & 0 deletions web-frontend/modules/core/enums.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,8 @@ export const FORMULA_CATEGORY = {
category: 'categoryFile',
iconClass: 'baserow-icon-file',
},
CONDITION: {
category: 'categoryCondition',
iconClass: 'iconoir-code-brackets-square',
},
}
5 changes: 3 additions & 2 deletions web-frontend/modules/core/formula/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ export const resolveFormula = (
}
}

export const isFormulaValid = (formula) => {
export const isFormulaValid = (formula, functions) => {
if (!formula) {
return true
}

try {
parseBaserowFormula(formula)
const tree = parseBaserowFormula(formula)
new JavascriptExecutor(functions).visit(tree)
return true
} catch (err) {
return false
Expand Down
7 changes: 2 additions & 5 deletions web-frontend/modules/core/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ import {
RuntimeRound,
RuntimeIsEven,
RuntimeIsOdd,
RuntimeDateTimeFormat,
RuntimeDay,
RuntimeMonth,
RuntimeYear,
Expand All @@ -122,6 +121,7 @@ import {
RuntimeRandomFloat,
RuntimeRandomBool,
RuntimeGenerateUUID,
RuntimeIf,
} from '@baserow/modules/core/runtimeFormulaTypes'

import priorityBus from '@baserow/modules/core/plugins/priorityBus'
Expand Down Expand Up @@ -282,10 +282,6 @@ export default (context, inject) => {
registry.register('runtimeFormulaFunction', new RuntimeRound(context))
registry.register('runtimeFormulaFunction', new RuntimeIsEven(context))
registry.register('runtimeFormulaFunction', new RuntimeIsOdd(context))
registry.register(
'runtimeFormulaFunction',
new RuntimeDateTimeFormat(context)
)
registry.register('runtimeFormulaFunction', new RuntimeDay(context))
registry.register('runtimeFormulaFunction', new RuntimeMonth(context))
registry.register('runtimeFormulaFunction', new RuntimeYear(context))
Expand All @@ -299,6 +295,7 @@ export default (context, inject) => {
registry.register('runtimeFormulaFunction', new RuntimeRandomFloat(context))
registry.register('runtimeFormulaFunction', new RuntimeRandomBool(context))
registry.register('runtimeFormulaFunction', new RuntimeGenerateUUID(context))
registry.register('runtimeFormulaFunction', new RuntimeIf(context))

registry.register('roles', new AdminRoleType(context))
registry.register('roles', new MemberRoleType(context))
Expand Down
34 changes: 31 additions & 3 deletions web-frontend/modules/core/runtimeFormulaArgumentTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ensureNumeric,
ensureDateTime,
ensureObject,
ensureBoolean,
} from '@baserow/modules/core/utils/validator'

export class BaserowRuntimeFormulaArgumentType {
Expand Down Expand Up @@ -51,20 +52,47 @@ export class TextBaserowRuntimeFormulaArgumentType extends BaserowRuntimeFormula

export class DateTimeBaserowRuntimeFormulaArgumentType extends BaserowRuntimeFormulaArgumentType {
test(value) {
return value instanceof Date
if (value instanceof Date) {
return true
}
try {
ensureDateTime(value, { useStrict: false })
return true
} catch (e) {
return false
}
}

parse(value) {
return ensureDateTime(value)
return ensureDateTime(value, { useStrict: false })
}
}

export class ObjectBaserowRuntimeFormulaArgumentType extends BaserowRuntimeFormulaArgumentType {
test(value) {
return value instanceof Object
if (value instanceof Object) {
return true
}

try {
ensureObject(value)
return true
} catch (e) {
return false
}
}

parse(value) {
return ensureObject(value)
}
}

export class BooleanBaserowRuntimeFormulaArgumentType extends BaserowRuntimeFormulaArgumentType {
test(value) {
return typeof value === 'boolean'
}

parse(value) {
return ensureBoolean(value)
}
}
76 changes: 64 additions & 12 deletions web-frontend/modules/core/runtimeFormulaTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
TextBaserowRuntimeFormulaArgumentType,
DateTimeBaserowRuntimeFormulaArgumentType,
ObjectBaserowRuntimeFormulaArgumentType,
BooleanBaserowRuntimeFormulaArgumentType,
} from '@baserow/modules/core/runtimeFormulaArgumentTypes'
import {
InvalidFormulaArgumentType,
Expand Down Expand Up @@ -84,7 +85,7 @@ export class RuntimeFormulaFunction extends Registerable {
* @returns {boolean} - If the number is correct.
*/
validateNumberOfArgs(args) {
return this.numArgs === null || args.length <= this.numArgs
return this.numArgs === null || args.length === this.numArgs
}

/**
Expand Down Expand Up @@ -913,10 +914,8 @@ export class RuntimeDateTimeFormat extends RuntimeFormulaFunction {
}

execute(context, args) {
// Backend uses Python's datetime formatting syntax, e.g. `%Y-%m-%d %H:%M:%S`
// but I haven't yet found a way to replicate this in pure JS. Maybe
// we can rely on a 3rd party lib?
return 'TODO'
// TODO see: https://github.com/baserow/baserow/issues/4141
throw new Error("This formula function hasn't been implemented.")
}

getDescription() {
Expand All @@ -925,9 +924,7 @@ export class RuntimeDateTimeFormat extends RuntimeFormulaFunction {
}

getExamples() {
return [
"datetime_format('2025-10-16 11:05:38.547989', '%Y-%m-%d', 'Asia/Dubai') = '2025-10-16'",
]
return ["datetime_format(now(), '%Y-%m-%d', 'Asia/Dubai') = '2025-10-16'"]
}
}

Expand Down Expand Up @@ -989,7 +986,8 @@ export class RuntimeMonth extends RuntimeFormulaFunction {
}

getExamples() {
return ["month('2025-10-16 11:05:38') = '10'"]
// Month is 0 indexed
return ["month('2025-10-16 11:05:38') = '9'"]
}
}

Expand Down Expand Up @@ -1082,7 +1080,7 @@ export class RuntimeMinute extends RuntimeFormulaFunction {
}

getExamples() {
return ["minute('2025-10-16 11:05:38') = '05'"]
return ["minute('2025-10-16T11:05:38') = '05'"]
}
}

Expand Down Expand Up @@ -1133,6 +1131,10 @@ export class RuntimeNow extends RuntimeFormulaFunction {
execute(context, args) {
return new Date()
}

getExamples() {
return ["now() = '2025-10-16 11:05:38'"]
}
}

export class RuntimeToday extends RuntimeFormulaFunction {
Expand Down Expand Up @@ -1183,7 +1185,7 @@ export class RuntimeGetProperty extends RuntimeFormulaFunction {
}

execute(context, args) {
return new args[0][args[1]]()
return args[0][args[1]]
}

getDescription() {
Expand All @@ -1192,7 +1194,7 @@ export class RuntimeGetProperty extends RuntimeFormulaFunction {
}

getExamples() {
return ['get_property(\'{"cherry": "red"}\', "fruit")']
return ["get_property('{\"cherry\": \"red\"}', 'cherry') = 'red'"]
}
}

Expand Down Expand Up @@ -1279,6 +1281,10 @@ export class RuntimeRandomBool extends RuntimeFormulaFunction {
return FORMULA_CATEGORY.BOOLEAN
}

validateNumberOfArgs(args) {
return args.length === 0
}

execute(context, args) {
return Math.random() < 0.5
}
Expand Down Expand Up @@ -1306,6 +1312,10 @@ export class RuntimeGenerateUUID extends RuntimeFormulaFunction {
return FORMULA_CATEGORY.TEXT
}

validateNumberOfArgs(args) {
return args.length === 0
}

execute(context, args) {
return crypto.randomUUID()
}
Expand All @@ -1319,3 +1329,45 @@ export class RuntimeGenerateUUID extends RuntimeFormulaFunction {
return ["generate_uuid() = '9b772ad6-08bc-4d19-958d-7f1c21a4f4ef'"]
}
}

export class RuntimeIf extends RuntimeFormulaFunction {
static getType() {
return 'if'
}

static getFormulaType() {
return FORMULA_TYPE.FUNCTION
}

static getCategoryType() {
return FORMULA_CATEGORY.CONDITION
}

validateNumberOfArgs(args) {
return args.length === 3
}

validateTypeOfArgs(args) {
const argType = new BooleanBaserowRuntimeFormulaArgumentType()
if (!argType.test(args[0])) {
return args[0]
}
return null
}

execute(context, args) {
return args[0] ? args[1] : args[2]
}

getDescription() {
const { i18n } = this.app
return i18n.t('runtimeFormulaTypes.ifDescription')
}

getExamples() {
return [
'if(true, true, false)',
"if(random_bool(), 'Random bool is true', 'Random bool is false')",
]
}
}
Loading
Loading