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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "bug",
"message": "Fix RichTextEditor display in row comment notifications",
"issue_origin": "github",
"issue_number": 5230,
"domain": "core",
"bullet_points": [],
"created_at": "2026-04-20"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "bug",
"message": "Fixes if() formulas in the Application Builder silently rendering nothing when the condition references an empty field",
"issue_origin": "github",
"issue_number": null,
"domain": "builder",
"bullet_points": [],
"created_at": "2026-04-21"
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<RichTextEditor
:editable="false"
:mentionable-users="workspace.users"
:value="notification.data.message"
:model-value="notification.data.message"
/>
</nuxt-link>
</template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<RichTextEditor
:editable="false"
:mentionable-users="workspace.users"
:value="notification.data.message"
:model-value="notification.data.message"
/>
</nuxt-link>
</template>
Expand Down
20 changes: 12 additions & 8 deletions web-frontend/modules/core/utils/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,26 +111,31 @@ export function isPromise(p) {
/**
* Get the value at `path` of `obj`, similar to Lodash `get` function.
*
* @param {Object} obj The object that holds the value
* @param {Object} context The object that holds the value
* @param {string | Array[string]} path The path to the value or a list with the path parts
* @param {any} defaultValue The value to return if the path is not found
* @return {Object} The value held by the path
* @param {any} defaultValue The value to return if the path is not found (defaults to `null`)
* @return {Object} The value held by the path, or `defaultValue` if not found
*/
export function getValueAtPath(context, path) {
export function getValueAtPath(context, path, defaultValue = null) {
function _getValueAtPath(obj, keys) {
const [first, ...rest] = keys
if (first === undefined || first === null) {
return obj
}

if (obj === null || obj === undefined) {
throw new Error(`Path '${path}' not found in context '${obj}'`)
return defaultValue
}

if (first in obj) {
return _getValueAtPath(obj[first], rest)
}
if (Array.isArray(obj) && first === '*') {
// When wildcarding an empty array with no remaining path parts,
// return an empty list (mirrors the backend's `get_value_at_path`).
if (obj.length === 0 && rest.length === 0) {
return []
}
const results = obj
// Call recursively this function transforming the `*` in the path in a list
// of indexes present in the object, e.g:
Expand All @@ -139,10 +144,9 @@ export function getValueAtPath(context, path) {
// Remove empty results
// Note: Don't exclude false values such as booleans, empty strings, etc.
.filter((result) => result !== null && result !== undefined)
// Return null in case there are no results
return results.length ? results : null
return results.length ? results : defaultValue
}
return null
return defaultValue
}
const keys = typeof path === 'string' ? _.toPath(path) : path
return _getValueAtPath(context, keys)
Expand Down
82 changes: 82 additions & 0 deletions web-frontend/test/unit/core/utils/object.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,86 @@ describe('test utils object', () => {
}
expect(getValueAtPath(obj, path)).toStrictEqual(result)
})

describe('getValueAtPath defaultValue', () => {
const obj = {
a: { b: { c: 123 } },
nullable: null,
list: [{ d: 456 }, { d: 789, e: 111 }],
nested: [{ nested: [{ a: 1 }, { a: 2 }] }, { nested: [{ a: 3 }] }],
}

test('returns null by default when the path is not found', () => {
expect(getValueAtPath(obj, 'a.b.x')).toBe(null)
expect(getValueAtPath(obj, 'a.x.b')).toBe(null)
expect(getValueAtPath(obj, 'list.5.d')).toBe(null)
expect(getValueAtPath(obj, 'list.0.x')).toBe(null)
expect(getValueAtPath(obj, 'nested.0.nested.5.a')).toBe(null)
expect(getValueAtPath(obj, 'nested.5.nested.0.a')).toBe(null)
})

test('returns the provided default value when the path is not found', () => {
expect(getValueAtPath(obj, 'a.b.x', 'fallback')).toBe('fallback')
expect(getValueAtPath(obj, 'a.b.x', 0)).toBe(0)
expect(getValueAtPath(obj, 'a.b.x', '')).toBe('')
expect(getValueAtPath(obj, 'a.b.x', false)).toBe(false)
expect(getValueAtPath(obj, 'list.5.d', 'fallback')).toBe('fallback')
expect(getValueAtPath(obj, 'list.0.x', 'fallback')).toBe('fallback')
expect(getValueAtPath(obj, 'nested.0.nested.5.a', 'fallback')).toBe(
'fallback'
)
expect(getValueAtPath(obj, 'nested.5.nested.0.a', 'fallback')).toBe(
'fallback'
)
})
})

describe('getValueAtPath defaultValue with wildcards', () => {
const obj = {
list: [{ d: 456 }, { d: 789, e: 111 }],
nested: [{ nested: [{ a: 1 }, { a: 2 }] }, { nested: [{ a: 3 }] }],
empty: [],
}

test('returns null when every wildcard branch misses (default)', () => {
expect(getValueAtPath(obj, 'list.*.x')).toBe(null)
expect(getValueAtPath(obj, 'nested.*.nested.*.x')).toBe(null)
})

test('substitutes the default value at every missing leaf (backend-aligned)', () => {
expect(getValueAtPath(obj, 'list.*.x', 'fallback')).toStrictEqual([
'fallback',
'fallback',
])
expect(
getValueAtPath(obj, 'nested.*.nested.*.x', 'fallback')
).toStrictEqual([['fallback', 'fallback'], ['fallback']])
})

test('mixes existing values with the default value when only some branches miss', () => {
expect(getValueAtPath(obj, 'list.*.e', 'fallback')).toStrictEqual([
'fallback',
111,
])
})

test('does not substitute the default value when wildcard branches all exist', () => {
expect(getValueAtPath(obj, 'list.*.d', 'fallback')).toStrictEqual([
456, 789,
])
expect(
getValueAtPath(obj, 'nested.*.nested.*.a', 'fallback')
).toStrictEqual([[1, 2], [3]])
})

test('returns an empty list when wildcarding an empty array with no remaining path', () => {
expect(getValueAtPath(obj, 'empty.*')).toStrictEqual([])
expect(getValueAtPath(obj, 'empty.*', 'fallback')).toStrictEqual([])
})

test('returns the default value when wildcarding an empty array with a remaining path', () => {
expect(getValueAtPath(obj, 'empty.*.x')).toBe(null)
expect(getValueAtPath(obj, 'empty.*.x', 'fallback')).toBe('fallback')
})
})
})
Loading