diff --git a/backend/uv.lock b/backend/uv.lock index 602f366a0d..4ffd8d5223 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -459,12 +459,12 @@ dev = [ [[package]] name = "baserow-enterprise" -version = "2.1.0" +version = "2.1.1" source = { editable = "../enterprise/backend" } [[package]] name = "baserow-premium" -version = "2.1.0" +version = "2.1.1" source = { editable = "../premium/backend" } [[package]] @@ -1382,12 +1382,14 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/7c/f0a6d0ede2c7bf092d00bc83ad5bafb7e6ec9b4aab2fbdfa6f134dc73327/greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f", size = 275671, upload-time = "2025-12-04T14:23:05.267Z" }, { url = "https://files.pythonhosted.org/packages/44/06/dac639ae1a50f5969d82d2e3dd9767d30d6dbdbab0e1a54010c8fe90263c/greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365", size = 646360, upload-time = "2025-12-04T14:50:10.026Z" }, { url = "https://files.pythonhosted.org/packages/e0/94/0fb76fe6c5369fba9bf98529ada6f4c3a1adf19e406a47332245ef0eb357/greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3", size = 658160, upload-time = "2025-12-04T14:57:45.41Z" }, + { url = "https://files.pythonhosted.org/packages/93/79/d2c70cae6e823fac36c3bbc9077962105052b7ef81db2f01ec3b9bf17e2b/greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45", size = 671388, upload-time = "2025-12-04T15:07:15.789Z" }, { url = "https://files.pythonhosted.org/packages/b8/14/bab308fc2c1b5228c3224ec2bf928ce2e4d21d8046c161e44a2012b5203e/greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955", size = 660166, upload-time = "2025-12-04T14:26:05.099Z" }, { url = "https://files.pythonhosted.org/packages/4b/d2/91465d39164eaa0085177f61983d80ffe746c5a1860f009811d498e7259c/greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55", size = 1615193, upload-time = "2025-12-04T15:04:27.041Z" }, { url = "https://files.pythonhosted.org/packages/42/1b/83d110a37044b92423084d52d5d5a3b3a73cafb51b547e6d7366ff62eff1/greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc", size = 1683653, upload-time = "2025-12-04T14:27:32.366Z" }, { url = "https://files.pythonhosted.org/packages/a0/66/bd6317bc5932accf351fc19f177ffba53712a202f9df10587da8df257c7e/greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931", size = 282638, upload-time = "2025-12-04T14:25:20.941Z" }, { url = "https://files.pythonhosted.org/packages/30/cf/cc81cb030b40e738d6e69502ccbd0dd1bced0588e958f9e757945de24404/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388", size = 651145, upload-time = "2025-12-04T14:50:11.039Z" }, { url = "https://files.pythonhosted.org/packages/9c/ea/1020037b5ecfe95ca7df8d8549959baceb8186031da83d5ecceff8b08cd2/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3", size = 654236, upload-time = "2025-12-04T14:57:47.007Z" }, + { url = "https://files.pythonhosted.org/packages/69/cc/1e4bae2e45ca2fa55299f4e85854606a78ecc37fead20d69322f96000504/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221", size = 662506, upload-time = "2025-12-04T15:07:16.906Z" }, { url = "https://files.pythonhosted.org/packages/57/b9/f8025d71a6085c441a7eaff0fd928bbb275a6633773667023d19179fe815/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b", size = 653783, upload-time = "2025-12-04T14:26:06.225Z" }, { url = "https://files.pythonhosted.org/packages/f6/c7/876a8c7a7485d5d6b5c6821201d542ef28be645aa024cfe1145b35c120c1/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd", size = 1614857, upload-time = "2025-12-04T15:04:28.484Z" }, { url = "https://files.pythonhosted.org/packages/4f/dc/041be1dff9f23dac5f48a43323cd0789cb798342011c19a248d9c9335536/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9", size = 1676034, upload-time = "2025-12-04T14:27:33.531Z" }, diff --git a/changelog/entries/unreleased/bug/4850_resolved_a_bug_which_prevented_user_source_users_from_being_.json b/changelog/entries/unreleased/bug/4850_resolved_a_bug_which_prevented_user_source_users_from_being_.json new file mode 100644 index 0000000000..6d67c98c0d --- /dev/null +++ b/changelog/entries/unreleased/bug/4850_resolved_a_bug_which_prevented_user_source_users_from_being_.json @@ -0,0 +1,9 @@ +{ + "type": "bug", + "message": "Resolved a bug which prevented user source users from being searched.", + "issue_origin": "github", + "issue_number": 4850, + "domain": "builder", + "bullet_points": [], + "created_at": "2026-02-23" +} \ No newline at end of file diff --git a/premium/web-frontend/modules/baserow_premium/pages/admin/license.vue b/premium/web-frontend/modules/baserow_premium/pages/admin/license.vue index 89df94d4f6..d0136a1004 100644 --- a/premium/web-frontend/modules/baserow_premium/pages/admin/license.vue +++ b/premium/web-frontend/modules/baserow_premium/pages/admin/license.vue @@ -216,19 +216,26 @@ const router = useRouter() const { $client, $registry } = useNuxtApp() // Fetch license data -const { data } = await useAsyncData(`license-${route.params.id}`, async () => { - try { - const { data: licenseData } = await LicenseService($client).fetch( - route.params.id - ) - return licenseData - } catch { - throw createError({ - statusCode: 404, - message: 'The license was not found.', - }) +const { data, error } = await useAsyncData( + `license-${route.params.id}`, + async () => { + try { + const { data: licenseData } = await LicenseService($client).fetch( + route.params.id + ) + return licenseData + } catch { + throw createError({ + statusCode: 404, + message: 'The license was not found.', + }) + } } -}) +) + +if (error.value) { + throw error.value +} const license = computed(() => data.value) diff --git a/premium/web-frontend/modules/baserow_premium/pages/admin/licenses.vue b/premium/web-frontend/modules/baserow_premium/pages/admin/licenses.vue index efdf2f3af4..d3f262cd66 100644 --- a/premium/web-frontend/modules/baserow_premium/pages/admin/licenses.vue +++ b/premium/web-frontend/modules/baserow_premium/pages/admin/licenses.vue @@ -169,7 +169,7 @@ const { $client, $registry, $i18n } = useNuxtApp() useHead({ title: $i18n.t('licenses.titleLicenses') }) // Fetch data using useAsyncData and return the values from the callback -const { data } = await useAsyncData('licensesPage', async () => { +const { data, error } = await useAsyncData('licensesPage', async () => { try { const [{ data: instanceData }, { data: licensesData }] = await Promise.all([ SettingsService($client).getInstanceID(), @@ -183,11 +183,16 @@ const { data } = await useAsyncData('licensesPage', async () => { } catch (e) { throw createError({ statusCode: 400, - statusMessage: 'Something went wrong while fetching the licenses.', + message: 'Something went wrong while fetching the licenses.', + fatal: true, }) } }) +if (error.value) { + throw error.value +} + const licenses = computed(() => data.value?.licenses || []) const instanceId = computed(() => data.value?.instanceId || '') diff --git a/web-frontend/modules/automation/pages/automationWorkflow.vue b/web-frontend/modules/automation/pages/automationWorkflow.vue index 154000489e..de33ae5a39 100644 --- a/web-frontend/modules/automation/pages/automationWorkflow.vue +++ b/web-frontend/modules/automation/pages/automationWorkflow.vue @@ -108,7 +108,7 @@ const automationApplicationType = $registry.get( AutomationApplicationType.getType() ) -const { data: pageData } = await useAsyncData( +const { data: pageData, error } = await useAsyncData( () => `automation-workflow-${automationId.value}-${workflowId.value}`, async () => { try { @@ -151,6 +151,10 @@ const { data: pageData } = await useAsyncData( } ) +if (error.value) { + throw error.value +} + // Computed properties from async data const automation = computed(() => pageData.value?.automation ?? null) const workspace = computed(() => pageData.value?.workspace ?? null) diff --git a/web-frontend/modules/builder/middleware/selectWorkspaceBuilderPage.js b/web-frontend/modules/builder/middleware/selectWorkspaceBuilderPage.js index c6fc1af85c..4dc72feee8 100644 --- a/web-frontend/modules/builder/middleware/selectWorkspaceBuilderPage.js +++ b/web-frontend/modules/builder/middleware/selectWorkspaceBuilderPage.js @@ -25,7 +25,6 @@ export default defineNuxtRouteMiddleware(async (to, from) => { throw createError({ statusCode: 404, message: $i18n.t('pageEditor.pageNotFound'), - fatal: false, }) } }) diff --git a/web-frontend/modules/builder/pages/pageEditor.vue b/web-frontend/modules/builder/pages/pageEditor.vue index 97535ce490..64c8ceba56 100644 --- a/web-frontend/modules/builder/pages/pageEditor.vue +++ b/web-frontend/modules/builder/pages/pageEditor.vue @@ -104,12 +104,7 @@ const { ) if (pageError.value) { - // If we have an error we want to display it. - if (pageError.value.statusCode === 404) { - showError(pageError.value) - } else { - throw pageError.value - } + throw pageError.value } const workspace = computed(() => pageData.value.workspace) diff --git a/web-frontend/modules/builder/pages/publicPage.vue b/web-frontend/modules/builder/pages/publicPage.vue index 0a89c0f117..27931c145f 100644 --- a/web-frontend/modules/builder/pages/publicPage.vue +++ b/web-frontend/modules/builder/pages/publicPage.vue @@ -97,7 +97,7 @@ const { } catch (e) { throw createError({ statusCode: 404, - statusMessage: $i18n.t('publicPage.siteNotFound'), + message: $i18n.t('publicPage.siteNotFound'), }) } @@ -173,7 +173,7 @@ const { if (authError) { throw createError({ statusCode: authError.code, - statusMessage: authError.message, + message: authError.message, }) } } @@ -190,7 +190,7 @@ const { if (!found) { throw createError({ statusCode: 404, - statusMessage: $i18n.t('publicPage.pageNotFound'), + message: $i18n.t('publicPage.pageNotFound'), }) } @@ -199,7 +199,7 @@ const { if (pageFound.shared) { throw createError({ statusCode: 404, - statusMessage: $i18n.t('publicPage.pageNotFound'), + message: $i18n.t('publicPage.pageNotFound'), }) } @@ -288,12 +288,7 @@ const { ) if (error.value) { - // If we have an error we want to display it. - if (error.value.statusCode === 404) { - showError(error.value) - } else { - throw error.value - } + throw error.value } const workspace = computed(() => asyncDataResult.value?.workspace) diff --git a/web-frontend/modules/core/components/SelectSearch.vue b/web-frontend/modules/core/components/SelectSearch.vue index 326ac09384..ff6973472b 100644 --- a/web-frontend/modules/core/components/SelectSearch.vue +++ b/web-frontend/modules/core/components/SelectSearch.vue @@ -2,12 +2,12 @@ @@ -18,8 +18,11 @@ export default { props: { value: { type: String, - required: false, - default: null, + default: undefined, + }, + modelValue: { + type: String, + default: undefined, }, placeholder: { type: String, @@ -27,6 +30,17 @@ export default { default: '', }, }, - emits: ['input'], + emits: ['input', 'update:modelValue'], + computed: { + currentValue() { + return this.modelValue !== undefined ? this.modelValue : this.value + }, + }, + methods: { + emitChange(newValue) { + this.$emit('input', newValue) + this.$emit('update:modelValue', newValue) + }, + }, } diff --git a/web-frontend/modules/core/middleware/urlCheck.js b/web-frontend/modules/core/middleware/urlCheck.js index fbe824ad52..845c73628c 100644 --- a/web-frontend/modules/core/middleware/urlCheck.js +++ b/web-frontend/modules/core/middleware/urlCheck.js @@ -35,6 +35,7 @@ export default defineNuxtRouteMiddleware(() => { hideBackButton: true, message: translate('urlCheck.invalidUrlEnvVarTitle', { name }), content: translate('urlCheck.invalidUrlEnvVarDescription', { name }), + fatal: true, }) } } diff --git a/web-frontend/modules/core/module.js b/web-frontend/modules/core/module.js index 487e484b1b..c3975c1b5e 100644 --- a/web-frontend/modules/core/module.js +++ b/web-frontend/modules/core/module.js @@ -112,6 +112,7 @@ export default defineNuxtModule({ ) addPlugin(resolve('plugins/store.js')) + addPlugin(resolve('plugins/errorHandler.js')) addPlugin(resolve('plugins/filters.js')) addPlugin(resolve('plugins/vuexState.js')) addPlugin(resolve('plugin.js')) diff --git a/web-frontend/modules/core/pages/notificationRedirect.vue b/web-frontend/modules/core/pages/notificationRedirect.vue index 642483b085..9f3c11cffb 100644 --- a/web-frontend/modules/core/pages/notificationRedirect.vue +++ b/web-frontend/modules/core/pages/notificationRedirect.vue @@ -15,19 +15,23 @@ const notificationId = route.params.notificationId const { data: notification, error: loadError } = await useAsyncData( () => `notification:${workspaceId}:${notificationId}`, async () => { - const { data } = await notificationService(nuxtApp.$client).markAsRead( - workspaceId, - notificationId - ) - return data + try { + const { data } = await notificationService(nuxtApp.$client).markAsRead( + workspaceId, + notificationId + ) + return data + } catch { + throw createError({ + statusCode: 404, + message: 'Notification not found.', + }) + } } ) if (loadError.value || !notification.value) { - throw createError({ - statusCode: 404, - statusMessage: 'Notification not found.', - }) + throw loadError.value } const notificationType = nuxtApp.$registry.get( @@ -39,7 +43,7 @@ const redirectParams = notificationType.getRoute(notification.value.data) if (!redirectParams) { throw createError({ statusCode: 404, - statusMessage: 'Notification has no route.', + message: 'Notification has no route.', }) } diff --git a/web-frontend/modules/core/pages/settings.vue b/web-frontend/modules/core/pages/settings.vue index 09ea3afd91..aeea635951 100644 --- a/web-frontend/modules/core/pages/settings.vue +++ b/web-frontend/modules/core/pages/settings.vue @@ -42,7 +42,7 @@ const store = nuxtApp.$store const { $i18n } = nuxtApp /* asyncData → useAsyncData */ -const { data: workspace } = await useAsyncData('workspace', async () => { +const { data: workspace, error } = await useAsyncData('workspace', async () => { try { return await store.dispatch( 'workspace/selectById', @@ -53,6 +53,10 @@ const { data: workspace } = await useAsyncData('workspace', async () => { } }) +if (error.value) { + throw error.value +} + /* Registry access */ const registry = nuxtApp.$registry diff --git a/web-frontend/modules/core/pages/workspace.vue b/web-frontend/modules/core/pages/workspace.vue index d6aa32abab..a1d9ade3cc 100644 --- a/web-frontend/modules/core/pages/workspace.vue +++ b/web-frontend/modules/core/pages/workspace.vue @@ -320,7 +320,7 @@ const { } catch (e) { throw createError({ statusCode: 404, - statusMessage: 'Workspace not found.', + message: 'Workspace not found.', }) } @@ -330,12 +330,17 @@ const { } catch { throw createError({ statusCode: 400, - statusMessage: 'Error loading dashboard.', + message: 'Error loading dashboard.', + fatal: true, }) } } ) +if (error.value) { + throw error.value +} + /** * Hydrate local refs from the async data. * Keeps your existing `selectedWorkspace` and `workspaceComponentArguments` diff --git a/web-frontend/modules/core/plugins/errorHandler.js b/web-frontend/modules/core/plugins/errorHandler.js new file mode 100644 index 0000000000..73cde5fa04 --- /dev/null +++ b/web-frontend/modules/core/plugins/errorHandler.js @@ -0,0 +1,14 @@ +import { showError } from '#imports' + +export default defineNuxtPlugin((nuxtApp) => { + const defaultHandler = nuxtApp.vueApp.config.errorHandler + + nuxtApp.vueApp.config.errorHandler = (error, instance, info) => { + if (error.fatal === false) { + showError(error) + return false + } else { + return defaultHandler(error, instance, info) + } + } +}) diff --git a/web-frontend/modules/core/utils/sentryFakeTransport.js b/web-frontend/modules/core/utils/sentryFakeTransport.js new file mode 100644 index 0000000000..50c29ae9b4 --- /dev/null +++ b/web-frontend/modules/core/utils/sentryFakeTransport.js @@ -0,0 +1,5 @@ +import { createTransport } from '@sentry/core' + +export function makeFakeTransport(options) { + return createTransport(options, async () => ({ statusCode: 200 })) +} diff --git a/web-frontend/modules/dashboard/middleware/dashboardLoading.js b/web-frontend/modules/dashboard/middleware/dashboardLoading.js index f67f48a659..6c1f682e3c 100644 --- a/web-frontend/modules/dashboard/middleware/dashboardLoading.js +++ b/web-frontend/modules/dashboard/middleware/dashboardLoading.js @@ -32,7 +32,6 @@ export default defineNuxtRouteMiddleware(async (to, from) => { throw createError({ statusCode: 404, message: 'Dashboard not found.', - fatal: false, }) } } diff --git a/web-frontend/modules/database/middleware/selectWorkspaceDatabaseTable.js b/web-frontend/modules/database/middleware/selectWorkspaceDatabaseTable.js index ae71a3ec10..e93c60139a 100644 --- a/web-frontend/modules/database/middleware/selectWorkspaceDatabaseTable.js +++ b/web-frontend/modules/database/middleware/selectWorkspaceDatabaseTable.js @@ -25,7 +25,6 @@ export default defineNuxtRouteMiddleware(async (to, from) => { throw createError({ statusCode: e.response?.status || 404, message: normalizeError(e).message, - fatal: false, }) } diff --git a/web-frontend/modules/database/pages/form.vue b/web-frontend/modules/database/pages/form.vue index 05e2151b5b..5181e6cc2c 100644 --- a/web-frontend/modules/database/pages/form.vue +++ b/web-frontend/modules/database/pages/form.vue @@ -184,11 +184,7 @@ const { data, error } = await useAsyncData( ) if (error.value) { - if (error.value.statusCode === 404) { - showError(error.value) - } else { - throw error.value - } + throw error.value } if (data.value?.redirect) { diff --git a/web-frontend/modules/database/pages/publicView.vue b/web-frontend/modules/database/pages/publicView.vue index 17681d97c4..23cef75c23 100644 --- a/web-frontend/modules/database/pages/publicView.vue +++ b/web-frontend/modules/database/pages/publicView.vue @@ -132,6 +132,7 @@ const { data, error } = await useAsyncData( throw createError({ statusCode: 500, message: e.message || 'Error loading view.', + fatal: true, }) } } @@ -139,11 +140,7 @@ const { data, error } = await useAsyncData( ) if (error.value) { - if (error.value.statusCode === 404) { - showError(error.value) - } else { - throw error.value - } + throw error.value } if (data.value?.redirect) { diff --git a/web-frontend/sentry.client.config.ts b/web-frontend/sentry.client.config.ts index e3555cf0d0..89a9f2ef63 100644 --- a/web-frontend/sentry.client.config.ts +++ b/web-frontend/sentry.client.config.ts @@ -1,9 +1,14 @@ import { useRuntimeConfig, useAppConfig, useRouter } from '#imports' +import { makeFakeTransport } from './modules/core/utils/sentryFakeTransport' import * as Sentry from '@sentry/nuxt' const config = useRuntimeConfig() const appConfig = useAppConfig() -const dsn = config.public.sentryDsn +const dsn = + config.public.sentryDsn === 'fake' + ? 'https://fake@localhost/1' + : config.public.sentryDsn +const isDev = import.meta.dev && config.public.sentryDsn === 'fake' if (dsn && dsn !== '') { const defaultConfig = { @@ -22,6 +27,17 @@ if (dsn && dsn !== '') { tracesSampleRate: 1.0, replaysSessionSampleRate: 0, replaysOnErrorSampleRate: 1.0, + ...(isDev ? { transport: makeFakeTransport } : {}), + beforeSend(event, hint) { + const err = hint?.originalException + if (err?.fatal === false) return null + if (isDev) { + console.error('[Sentry captured error]', `${err}`) + return null + } else { + return event + } + }, } // Merge with user-provided configuration from app config diff --git a/web-frontend/sentry.server.config.ts b/web-frontend/sentry.server.config.ts index 0466a78c6b..13ec449a0b 100644 --- a/web-frontend/sentry.server.config.ts +++ b/web-frontend/sentry.server.config.ts @@ -1,8 +1,13 @@ import { useRuntimeConfig } from '#imports' import * as Sentry from '@sentry/nuxt' +import { makeFakeTransport } from './modules/core/utils/sentryFakeTransport' const config = useRuntimeConfig() -const dsn = config.public.sentryDsn +const dsn = + config.public.sentryDsn === 'fake' + ? 'https://fake@localhost/1' + : config.public.sentryDsn +const isDev = import.meta.dev && config.public.sentryDsn === 'fake' if (dsn && dsn !== '') { Sentry.init({ @@ -10,5 +15,16 @@ if (dsn && dsn !== '') { release: `baserow-web-frontend@${config.public.version}`, environment: config.public.sentryEnvironment || 'production', tracesSampleRate: 1.0, + ...(isDev ? { transport: makeFakeTransport } : {}), + beforeSend(event, hint) { + const err = hint?.originalException + if (err?.fatal === false) return null + if (isDev) { + console.error('[Sentry captured error]', err) + return null + } else { + return event + } + }, }) }