diff --git a/changelog/entries/unreleased/bug/5230_fix_richeditor_display_in_row_comment_notifications.json b/changelog/entries/unreleased/bug/5230_fix_richeditor_display_in_row_comment_notifications.json new file mode 100644 index 0000000000..e27bcc6dc6 --- /dev/null +++ b/changelog/entries/unreleased/bug/5230_fix_richeditor_display_in_row_comment_notifications.json @@ -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" +} \ No newline at end of file diff --git a/changelog/entries/unreleased/bug/fixes_if_formulas_in_the_application_builder_silently_render.json b/changelog/entries/unreleased/bug/fixes_if_formulas_in_the_application_builder_silently_render.json new file mode 100644 index 0000000000..5324205a48 --- /dev/null +++ b/changelog/entries/unreleased/bug/fixes_if_formulas_in_the_application_builder_silently_render.json @@ -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" +} \ No newline at end of file diff --git a/premium/web-frontend/modules/baserow_premium/components/row_comments/RowCommentMentionNotification.vue b/premium/web-frontend/modules/baserow_premium/components/row_comments/RowCommentMentionNotification.vue index f71d976183..5de6492fa6 100644 --- a/premium/web-frontend/modules/baserow_premium/components/row_comments/RowCommentMentionNotification.vue +++ b/premium/web-frontend/modules/baserow_premium/components/row_comments/RowCommentMentionNotification.vue @@ -27,7 +27,7 @@ diff --git a/premium/web-frontend/modules/baserow_premium/components/row_comments/RowCommentNotification.vue b/premium/web-frontend/modules/baserow_premium/components/row_comments/RowCommentNotification.vue index 7af1f98b08..c85791d938 100644 --- a/premium/web-frontend/modules/baserow_premium/components/row_comments/RowCommentNotification.vue +++ b/premium/web-frontend/modules/baserow_premium/components/row_comments/RowCommentNotification.vue @@ -25,7 +25,7 @@ diff --git a/web-frontend/modules/core/utils/object.js b/web-frontend/modules/core/utils/object.js index ba6095b0ee..80bc1a6ba6 100644 --- a/web-frontend/modules/core/utils/object.js +++ b/web-frontend/modules/core/utils/object.js @@ -111,12 +111,12 @@ 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) { @@ -124,13 +124,18 @@ export function getValueAtPath(context, path) { } 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: @@ -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) diff --git a/web-frontend/test/unit/core/utils/object.spec.js b/web-frontend/test/unit/core/utils/object.spec.js index 50e63ec8e9..618f37a1ea 100644 --- a/web-frontend/test/unit/core/utils/object.spec.js +++ b/web-frontend/test/unit/core/utils/object.spec.js @@ -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') + }) + }) })