diff --git a/packages/auth/src/security/roles.js b/packages/auth/src/security/roles.js
index 53e1b90d73..baa8fc40dc 100644
--- a/packages/auth/src/security/roles.js
+++ b/packages/auth/src/security/roles.js
@@ -147,7 +147,7 @@ exports.getRole = async (appId, roleId) => {
*/
async function getAllUserRoles(appId, userRoleId) {
if (!userRoleId) {
- return [BUILTIN_IDS.PUBLIC]
+ return [BUILTIN_IDS.BASIC]
}
let currentRole = await exports.getRole(appId, userRoleId)
let roles = currentRole ? [currentRole] : []
@@ -226,7 +226,7 @@ exports.getAllRoles = async appId => {
dbRole => exports.getExternalRoleID(dbRole._id) === builtinRoleId
)[0]
if (dbBuiltin == null) {
- roles.push(builtinRole)
+ roles.push(builtinRole || builtinRoles.BASIC)
} else {
// remove role and all back after combining with the builtin
roles = roles.filter(role => role._id !== dbBuiltin._id)
diff --git a/packages/bbui/src/Tabs/Tabs.svelte b/packages/bbui/src/Tabs/Tabs.svelte
index 3e1080f2cd..77a8526a15 100644
--- a/packages/bbui/src/Tabs/Tabs.svelte
+++ b/packages/bbui/src/Tabs/Tabs.svelte
@@ -15,8 +15,12 @@
const dispatch = createEventDispatcher()
- $: selected = $tab.title
- $: selected = dispatch("select", selected)
+ $: {
+ if ($tab.title !== selected) {
+ selected = $tab.title
+ dispatch("select", selected)
+ }
+ }
let top, left, width, height
$: calculateIndicatorLength($tab)
diff --git a/packages/builder/assets/error.svg b/packages/builder/assets/error.svg
new file mode 100644
index 0000000000..4cc1d753c9
--- /dev/null
+++ b/packages/builder/assets/error.svg
@@ -0,0 +1,34 @@
+
+
diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js
index a462167ce2..edad0fdff5 100644
--- a/packages/builder/src/builderStore/store/frontend.js
+++ b/packages/builder/src/builderStore/store/frontend.js
@@ -122,6 +122,9 @@ export const getFrontendStore = () => {
save: async screen => {
const creatingNewScreen = screen._id === undefined
const response = await api.post(`/api/screens`, screen)
+ if (response.status !== 200) {
+ return
+ }
screen = await response.json()
await store.actions.routing.fetch()
diff --git a/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte b/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte
index f58b9f197f..8b7417c41f 100644
--- a/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte
@@ -10,8 +10,10 @@
let selectedRole = {}
let errors = []
let builtInRoles = ["Admin", "Power", "Basic", "Public"]
+ // Don't allow editing of public role
+ $: editableRoles = $roles.filter(role => role._id !== "PUBLIC")
$: selectedRoleId = selectedRole._id
- $: otherRoles = $roles.filter(role => role._id !== selectedRoleId)
+ $: otherRoles = editableRoles.filter(role => role._id !== selectedRoleId)
$: isCreating = selectedRoleId == null || selectedRoleId === ""
const fetchBasePermissions = async () => {
@@ -96,7 +98,7 @@
label="Role"
value={selectedRoleId}
on:change={changeRole}
- options={$roles}
+ options={editableRoles}
placeholder="Create new role"
getOptionValue={role => role._id}
getOptionLabel={role => role.name}
diff --git a/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte b/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte
index 5ae4ac0ddf..12f5280b83 100644
--- a/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte
+++ b/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte
@@ -5,12 +5,16 @@
import { Screen } from "builderStore/store/screenTemplates/utils/Screen"
import { FrontendTypes } from "constants"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
+ import { ProgressCircle, Layout, Heading, Body } from "@budibase/bbui"
+ import ErrorSVG from "assets/error.svg?raw"
let iframe
let layout
let screen
let confirmDeleteDialog
let idToDelete
+ let loading = true
+ let error
// Create screen slot placeholder for use when a page is selected rather
// than a screen
@@ -68,11 +72,21 @@
onMount(() => {
// Initialise the app when mounted
iframe.contentWindow.addEventListener(
- "bb-ready",
+ "ready",
() => refreshContent(strippedJson),
{ once: true }
)
+ // Catch any app errors
+ iframe.contentWindow.addEventListener(
+ "error",
+ event => {
+ loading = false
+ error = event.detail || "An unknown error occurred"
+ },
+ { once: true }
+ )
+
// Add listener for events sent by cliebt library in preview
iframe.contentWindow.addEventListener("bb-event", event => {
const { type, data } = event.detail
@@ -83,8 +97,10 @@
} else if (type === "delete-component" && data.id) {
idToDelete = data.id
confirmDeleteDialog.show()
+ } else if (type === "preview-loaded") {
+ loading = false
} else {
- console.log(data)
+ console.warning(`Client sent unknown event type: ${type}`)
}
})
})
@@ -99,11 +115,25 @@
+ {#if loading}
+
+ {:else if error}
+
+
+ {@html ErrorSVG}
+ App preview failed to load
+ {error}
+
+
+ {/if}
diff --git a/packages/builder/src/components/design/AppPreview/iframeTemplate.js b/packages/builder/src/components/design/AppPreview/iframeTemplate.js
index 7a7c04b3a1..81b9a9c86f 100644
--- a/packages/builder/src/components/design/AppPreview/iframeTemplate.js
+++ b/packages/builder/src/components/design/AppPreview/iframeTemplate.js
@@ -25,7 +25,9 @@ export default `
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
- box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);
+ }
+ html.loaded {
+ box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.1);
}
body {
flex: 1 1 auto;
@@ -58,13 +60,20 @@ export default `
window["##BUDIBASE_PREVIEW_TYPE##"] = previewType
// Initialise app
- if (window.loadBudibase) {
- loadBudibase()
+ try {
+ if (window.loadBudibase) {
+ window.loadBudibase()
+ document.documentElement.classList.add("loaded")
+ } else {
+ throw "The client library couldn't be loaded"
+ }
+ } catch (error) {
+ window.dispatchEvent(new CustomEvent("error", { detail: error }))
}
}
window.addEventListener("message", receiveMessage)
- window.dispatchEvent(new Event("bb-ready"))
+ window.dispatchEvent(new Event("ready"))
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterEditor.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterEditor.svelte
index eee8fb113c..dcda6ac1f1 100644
--- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterEditor.svelte
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterEditor.svelte
@@ -21,7 +21,7 @@
export let value = []
export let componentInstance
let drawer
- let tempValue = value
+ let tempValue = value || []
$: numFilters = Array.isArray(tempValue)
? tempValue.length
@@ -31,15 +31,6 @@
$: schemaFields = Object.values(schema || {})
$: internalTable = dataSource?.type === "table"
- // Reset value if value is wrong type for the datasource.
- // Lucene editor needs an array, and simple editor needs an object.
- $: {
- if (!Array.isArray(value)) {
- tempValue = []
- dispatch("change", [])
- }
- }
-
const saveFilter = async () => {
dispatch("change", tempValue)
notifications.success("Filters saved.")
diff --git a/packages/builder/src/pages/builder/apps/index.svelte b/packages/builder/src/pages/builder/apps/index.svelte
index 0296f2e5b7..69b6e770f6 100644
--- a/packages/builder/src/pages/builder/apps/index.svelte
+++ b/packages/builder/src/pages/builder/apps/index.svelte
@@ -28,12 +28,7 @@
onMount(async () => {
await organisation.init()
await apps.load()
- // Skip the portal if you only have one app
- if (!$auth.isBuilder && $apps.filter(publishedAppsOnly).length === 1) {
- window.location = `/${publishedApps[0].prodId}`
- } else {
- loaded = true
- }
+ loaded = true
})
const publishedAppsOnly = app => app.status === AppStatus.DEPLOYED
diff --git a/packages/builder/src/pages/builder/portal/manage/_layout.svelte b/packages/builder/src/pages/builder/portal/manage/_layout.svelte
index 98ae140b25..e6c73bc596 100644
--- a/packages/builder/src/pages/builder/portal/manage/_layout.svelte
+++ b/packages/builder/src/pages/builder/portal/manage/_layout.svelte
@@ -9,8 +9,6 @@
$redirect("../")
}
}
-
- $: console.log($page)
{#if $auth.isAdmin}
diff --git a/packages/builder/src/pages/builder/portal/manage/users/[userId].svelte b/packages/builder/src/pages/builder/portal/manage/users/[userId].svelte
index ac5b569411..27cf266c5d 100644
--- a/packages/builder/src/pages/builder/portal/manage/users/[userId].svelte
+++ b/packages/builder/src/pages/builder/portal/manage/users/[userId].svelte
@@ -33,7 +33,7 @@
role: {},
}
- $: defaultRoleId = $userFetch?.data?.builder?.global ? "ADMIN" : ""
+ $: defaultRoleId = $userFetch?.data?.builder?.global ? "ADMIN" : "BASIC"
// Merge the Apps list and the roles response to get something that makes sense for the table
$: appList = Object.keys($apps?.data).map(id => {
const role = $userFetch?.data?.roles?.[id] || defaultRoleId
diff --git a/packages/builder/src/pages/builder/portal/manage/users/_components/UpdateRolesModal.svelte b/packages/builder/src/pages/builder/portal/manage/users/_components/UpdateRolesModal.svelte
index 08e4a2ec8b..436ba28bba 100644
--- a/packages/builder/src/pages/builder/portal/manage/users/_components/UpdateRolesModal.svelte
+++ b/packages/builder/src/pages/builder/portal/manage/users/_components/UpdateRolesModal.svelte
@@ -9,7 +9,7 @@
const dispatch = createEventDispatcher()
const roles = app.roles
- let options = roles.map(role => role._id)
+ let options = roles.map(role => role._id).filter(id => id !== "PUBLIC")
let selectedRole = user?.roles?.[app?._id]
async function updateUserRoles() {
diff --git a/packages/client/src/api/api.js b/packages/client/src/api/api.js
index fcdd9dbd5b..280b580164 100644
--- a/packages/client/src/api/api.js
+++ b/packages/client/src/api/api.js
@@ -43,10 +43,9 @@ const makeApiCall = async ({ method, url, body, json = true }) => {
case 400:
return handleError(`${url}: Bad Request`)
case 403:
- // reload the page incase the token has expired
- if (!url.includes("self")) {
- location.reload()
- }
+ notificationStore.danger(
+ "Your session has expired, or you don't have permission to access that data"
+ )
return handleError(`${url}: Forbidden`)
default:
if (response.status >= 200 && response.status < 400) {
diff --git a/packages/client/src/api/auth.js b/packages/client/src/api/auth.js
index 426d4f08d0..6ea105d9f9 100644
--- a/packages/client/src/api/auth.js
+++ b/packages/client/src/api/auth.js
@@ -24,7 +24,12 @@ export const logIn = async ({ email, password }) => {
export const fetchSelf = async () => {
const user = await API.get({ url: "/api/self" })
if (user?._id) {
- return (await enrichRows([user], TableNames.USERS))[0]
+ if (user.roleId === "PUBLIC") {
+ // Don't try to enrich a public user as it will 403
+ return user
+ } else {
+ return (await enrichRows([user], TableNames.USERS))[0]
+ }
} else {
return null
}
diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte
index 8e4d8864ed..014bea89b3 100644
--- a/packages/client/src/components/ClientApp.svelte
+++ b/packages/client/src/components/ClientApp.svelte
@@ -18,6 +18,8 @@
import SettingsBar from "./preview/SettingsBar.svelte"
import SelectionIndicator from "./preview/SelectionIndicator.svelte"
import HoverIndicator from "./preview/HoverIndicator.svelte"
+ import { Layout, Heading, Body } from "@budibase/bbui"
+ import ErrorSVG from "../../../builder/assets/error.svg"
// Provide contexts
setContext("sdk", SDK)
@@ -25,12 +27,16 @@
setContext("context", createContextStore())
let dataLoaded = false
+ let permissionError = false
// Load app config
onMount(async () => {
await initialise()
await authStore.actions.fetchUser()
dataLoaded = true
+ if ($builderStore.inBuilder) {
+ builderStore.actions.notifyLoaded()
+ }
})
// Register this as a refreshable datasource so that user changes cause
@@ -43,12 +49,21 @@
},
]
- // Redirect to home layout if no matching route
+ // Handle no matching route - this is likely a permission error
$: {
if (dataLoaded && $routeStore.routerLoaded && !$routeStore.activeRoute) {
if ($authStore) {
- routeStore.actions.navigate("/")
+ // There is a logged in user, so handle them
+ if ($screenStore.screens.length) {
+ // Screens exist so navigate back to the home screen
+ const firstRoute = $screenStore.screens[0].routing?.route ?? "/"
+ routeStore.actions.navigate(firstRoute)
+ } else {
+ // No screens likely means the user has no permissions to view this app
+ permissionError = true
+ }
} else {
+ // The user is not logged in, redirect them to login
const returnUrl = `${window.location.pathname}${window.location.hash}`
const encodedUrl = encodeURIComponent(returnUrl)
window.location = `/builder/auth/login?returnUrl=${encodedUrl}`
@@ -57,36 +72,46 @@
}
-{#if dataLoaded && $screenStore.activeLayout}
+{#if dataLoaded}
-
-
- {#key $screenStore.activeLayout._id}
-
- {/key}
+ {#if permissionError}
+
+
+ {@html ErrorSVG}
+ You don't have permission to use this app
+ Ask your administrator to grant you access
+
-
-
-
- {#key $builderStore.selectedComponentId}
+ {:else if $screenStore.activeLayout}
+
+
+ {#key $screenStore.activeLayout._id}
+
+ {/key}
+
+
+
+
+ {#key $builderStore.selectedComponentId}
+ {#if $builderStore.inBuilder}
+
+ {/if}
+ {/key}
+
{#if $builderStore.inBuilder}
-
+
+
{/if}
- {/key}
-
- {#if $builderStore.inBuilder}
-
-
- {/if}
-
+
+ {/if}
{/if}
@@ -102,4 +127,31 @@
#app-root {
position: relative;
}
+
+ .error {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ display: grid;
+ place-items: center;
+ z-index: 1;
+ text-align: center;
+ padding: 20px;
+ }
+ .error :global(svg) {
+ fill: var(--spectrum-global-color-gray-500);
+ width: 80px;
+ height: 80px;
+ }
+ .error :global(h1),
+ .error :global(p) {
+ color: var(--spectrum-global-color-gray-800);
+ }
+ .error :global(p) {
+ font-style: italic;
+ margin-top: -0.5em;
+ }
+ .error :global(h1) {
+ font-weight: 400;
+ }
diff --git a/packages/client/src/store/builder.js b/packages/client/src/store/builder.js
index f229f66b83..401ccfcc79 100644
--- a/packages/client/src/store/builder.js
+++ b/packages/client/src/store/builder.js
@@ -1,7 +1,7 @@
import { writable, derived } from "svelte/store"
import Manifest from "@budibase/standard-components/manifest.json"
-const dispatchEvent = (type, data) => {
+const dispatchEvent = (type, data = {}) => {
window.dispatchEvent(
new CustomEvent("bb-event", {
detail: { type, data },
@@ -64,6 +64,9 @@ const createBuilderStore = () => {
deleteComponent: id => {
dispatchEvent("delete-component", { id })
},
+ notifyLoaded: () => {
+ dispatchEvent("preview-loaded")
+ },
}
return {
...writableStore,
diff --git a/packages/client/src/store/screens.js b/packages/client/src/store/screens.js
index 04ed9ca52f..46255d0822 100644
--- a/packages/client/src/store/screens.js
+++ b/packages/client/src/store/screens.js
@@ -11,17 +11,20 @@ const createScreenStore = () => {
const store = derived(
[config, routeStore, builderStore],
([$config, $routeStore, $builderStore]) => {
- let activeLayout
- let activeScreen
+ let activeLayout, activeScreen
+ let layouts, screens
if ($builderStore.inBuilder) {
// Use builder defined definitions if inside the builder preview
activeLayout = $builderStore.layout
activeScreen = $builderStore.screen
+ layouts = [activeLayout]
+ screens = [activeScreen]
} else {
activeLayout = { props: { _component: "screenslot" } }
// Find the correct screen by matching the current route
- const { screens, layouts } = $config
+ screens = $config.screens
+ layouts = $config.layouts
if ($routeStore.activeRoute) {
activeScreen = screens.find(
screen => screen._id === $routeStore.activeRoute.screenId
@@ -33,7 +36,7 @@ const createScreenStore = () => {
)
}
}
- return { activeLayout, activeScreen }
+ return { layouts, screens, activeLayout, activeScreen }
}
)
diff --git a/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte b/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte
index 670a0f22e2..4943051328 100644
--- a/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte
+++ b/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte
@@ -34,13 +34,55 @@
*:after {
box-sizing: border-box;
}
+
+ #error {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100vh;
+ width: 100vw;
+ display: none;
+ font-family: "Source Sans Pro", sans-serif;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ background: #222;
+ text-align: center;
+ padding: 2rem;
+ gap: 2rem;
+ }
+ #error h1,
+ #error h2 {
+ margin: 0;
+ }
+ #error h1 {
+ color: #ccc;
+ font-size: 3rem;
+ font-weight: 600;
+ }
+ #error h2 {
+ color: #888;
+ font-weight: 400;
+ }
+
+
There was an error loading your app
+
+ The Budibase client library could not be loaded. Try republishing your
+ app.
+
+
diff --git a/packages/server/src/api/routes/tests/application.spec.js b/packages/server/src/api/routes/tests/application.spec.js
index 1930d0a4ec..2333787e6e 100644
--- a/packages/server/src/api/routes/tests/application.spec.js
+++ b/packages/server/src/api/routes/tests/application.spec.js
@@ -9,9 +9,13 @@ jest.mock("../../../utilities/redis", () => ({
updateLock: jest.fn(),
setDebounce: jest.fn(),
checkDebounce: jest.fn(),
+ shutdown: jest.fn(),
}))
-const { clearAllApps, checkBuilderEndpoint } = require("./utilities/TestFunctions")
+const {
+ clearAllApps,
+ checkBuilderEndpoint,
+} = require("./utilities/TestFunctions")
const setup = require("./utilities")
const { AppStatus } = require("../../../db/utils")
@@ -32,7 +36,7 @@ describe("/applications", () => {
.post("/api/applications")
.send({ name: "My App" })
.set(config.defaultHeaders())
- .expect('Content-Type', /json/)
+ .expect("Content-Type", /json/)
.expect(200)
expect(res.body._id).toBeDefined()
})
@@ -42,7 +46,7 @@ describe("/applications", () => {
config,
method: "POST",
url: `/api/applications`,
- body: { name: "My App" }
+ body: { name: "My App" },
})
})
})
@@ -55,7 +59,7 @@ describe("/applications", () => {
const res = await request
.get(`/api/applications?status=${AppStatus.DEV}`)
.set(config.defaultHeaders())
- .expect('Content-Type', /json/)
+ .expect("Content-Type", /json/)
.expect(200)
// two created apps + the inited app
@@ -68,7 +72,7 @@ describe("/applications", () => {
const res = await request
.get(`/api/applications/${config.getAppId()}/definition`)
.set(config.defaultHeaders())
- .expect('Content-Type', /json/)
+ .expect("Content-Type", /json/)
.expect(200)
// should have empty packages
expect(res.body.screens.length).toEqual(1)
@@ -81,7 +85,7 @@ describe("/applications", () => {
const res = await request
.get(`/api/applications/${config.getAppId()}/appPackage`)
.set(config.defaultHeaders())
- .expect('Content-Type', /json/)
+ .expect("Content-Type", /json/)
.expect(200)
expect(res.body.application).toBeDefined()
expect(res.body.screens.length).toEqual(1)
@@ -94,10 +98,10 @@ describe("/applications", () => {
const res = await request
.put(`/api/applications/${config.getAppId()}`)
.send({
- name: "TEST_APP"
+ name: "TEST_APP",
})
.set(config.defaultHeaders())
- .expect('Content-Type', /json/)
+ .expect("Content-Type", /json/)
.expect(200)
expect(res.body.rev).toBeDefined()
})
@@ -113,14 +117,14 @@ describe("/applications", () => {
name: "UPDATED_NAME",
})
.set(headers)
- .expect('Content-Type', /json/)
+ .expect("Content-Type", /json/)
.expect(200)
expect(res.body.rev).toBeDefined()
// retrieve the app to check it
const getRes = await request
.get(`/api/applications/${config.getAppId()}/appPackage`)
.set(headers)
- .expect('Content-Type', /json/)
+ .expect("Content-Type", /json/)
.expect(200)
expect(getRes.body.application.updatedAt).toBeDefined()
})
diff --git a/packages/server/src/middleware/currentapp.js b/packages/server/src/middleware/currentapp.js
index 683b7f8ef3..0e9591456c 100644
--- a/packages/server/src/middleware/currentapp.js
+++ b/packages/server/src/middleware/currentapp.js
@@ -45,10 +45,10 @@ module.exports = async (ctx, next) => {
updateCookie = true
appId = requestAppId
// retrieving global user gets the right role
- roleId = globalUser.roleId || BUILTIN_ROLE_IDS.PUBLIC
+ roleId = globalUser.roleId || BUILTIN_ROLE_IDS.BASIC
} else if (appCookie != null) {
appId = appCookie.appId
- roleId = appCookie.roleId || BUILTIN_ROLE_IDS.PUBLIC
+ roleId = appCookie.roleId || BUILTIN_ROLE_IDS.BASIC
}
// nothing more to do
if (!appId) {
diff --git a/packages/server/src/utilities/fileSystem/index.js b/packages/server/src/utilities/fileSystem/index.js
index ddda274ef5..afacbf8cdf 100644
--- a/packages/server/src/utilities/fileSystem/index.js
+++ b/packages/server/src/utilities/fileSystem/index.js
@@ -238,7 +238,10 @@ exports.readFileSync = (filepath, options = "utf8") => {
*/
exports.cleanup = appIds => {
for (let appId of appIds) {
- fs.rmdirSync(join(budibaseTempDir(), appId), { recursive: true })
+ const path = join(budibaseTempDir(), appId)
+ if (fs.existsSync(path)) {
+ fs.rmdirSync(path, { recursive: true })
+ }
}
}
diff --git a/packages/server/src/utilities/global.js b/packages/server/src/utilities/global.js
index 17ce066551..eddbd63cd7 100644
--- a/packages/server/src/utilities/global.js
+++ b/packages/server/src/utilities/global.js
@@ -19,7 +19,7 @@ exports.updateAppRole = (appId, user) => {
if (!user.roleId && user.builder && user.builder.global) {
user.roleId = BUILTIN_ROLE_IDS.ADMIN
} else if (!user.roleId) {
- user.roleId = BUILTIN_ROLE_IDS.PUBLIC
+ user.roleId = BUILTIN_ROLE_IDS.BASIC
}
delete user.roles
return user
diff --git a/packages/standard-components/manifest.json b/packages/standard-components/manifest.json
index 28b82fe67d..1fb470dfc3 100644
--- a/packages/standard-components/manifest.json
+++ b/packages/standard-components/manifest.json
@@ -73,13 +73,13 @@
{
"label": "Column",
"value": "column",
- "barIcon": "ViewRow",
+ "barIcon": "ViewColumn",
"barTitle": "Column layout"
},
{
"label": "Row",
"value": "row",
- "barIcon": "ViewColumn",
+ "barIcon": "ViewRow",
"barTitle": "Row layout"
}
],
diff --git a/packages/standard-components/src/lucene.js b/packages/standard-components/src/lucene.js
index 8cbd1ea0fe..91c69dfda2 100644
--- a/packages/standard-components/src/lucene.js
+++ b/packages/standard-components/src/lucene.js
@@ -13,9 +13,12 @@ export const buildLuceneQuery = filter => {
notEmpty: {},
}
if (Array.isArray(filter)) {
- // Build up proper range filters
filter.forEach(expression => {
- const { operator, field, type, value } = expression
+ let { operator, field, type, value } = expression
+ // Ensure date fields are transformed into ISO strings
+ if (type === "datetime" && value) {
+ value = new Date(value).toISOString()
+ }
if (operator.startsWith("range")) {
if (!query.range[field]) {
query.range[field] = {