From afffd4d234d551a50d1c1b41977bf8db87242a3d Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Thu, 2 Nov 2023 15:37:18 +0000
Subject: [PATCH 001/338] Add initial version of new date picker without time
support
---
packages/bbui/package.json | 1 +
.../bbui/src/Form/Core/BBDatePicker.svelte | 313 ++++++++++++++++++
packages/bbui/src/index.js | 1 +
.../pages/builder/portal/apps/index.svelte | 31 ++
yarn.lock | 5 +
5 files changed, 351 insertions(+)
create mode 100644 packages/bbui/src/Form/Core/BBDatePicker.svelte
diff --git a/packages/bbui/package.json b/packages/bbui/package.json
index 78eed2b608..ccc59646b4 100644
--- a/packages/bbui/package.json
+++ b/packages/bbui/package.json
@@ -44,6 +44,7 @@
"@spectrum-css/avatar": "3.0.2",
"@spectrum-css/button": "3.0.1",
"@spectrum-css/buttongroup": "3.0.2",
+ "@spectrum-css/calendar": "3.0.2",
"@spectrum-css/checkbox": "3.0.2",
"@spectrum-css/dialog": "3.0.1",
"@spectrum-css/divider": "1.0.3",
diff --git a/packages/bbui/src/Form/Core/BBDatePicker.svelte b/packages/bbui/src/Form/Core/BBDatePicker.svelte
new file mode 100644
index 0000000000..d159b3dcd6
--- /dev/null
+++ b/packages/bbui/src/Form/Core/BBDatePicker.svelte
@@ -0,0 +1,313 @@
+
+
+
+
+
+
+
+
+
+
+
+ {#each DaysOfWeek as day}
+
+
+ {day[0]}
+
+
+ {/each}
+
+
+
+ {#each mondays as monday}
+
+ {#each [0, 1, 2, 3, 4, 5, 6] as dayOffset}
+ {@const date = monday.add(dayOffset, "days")}
+ {@const outsideMonth = date.month() !== activeMonth.month()}
+
+
+ {date.date()}
+
+
+ {/each}
+
+ {/each}
+
+
+
+
+
+
+
diff --git a/packages/bbui/src/index.js b/packages/bbui/src/index.js
index cda6b5acbf..407a8c2cec 100644
--- a/packages/bbui/src/index.js
+++ b/packages/bbui/src/index.js
@@ -54,6 +54,7 @@ export { default as Notification } from "./Notification/Notification.svelte"
export { default as SideNavigation } from "./SideNavigation/Navigation.svelte"
export { default as SideNavigationItem } from "./SideNavigation/Item.svelte"
export { default as DatePicker } from "./Form/DatePicker.svelte"
+export { default as BBDatePicker } from "./Form/Core/BBDatePicker.svelte"
export { default as Multiselect } from "./Form/Multiselect.svelte"
export { default as Context } from "./context"
export { default as Table } from "./Table/Table.svelte"
diff --git a/packages/builder/src/pages/builder/portal/apps/index.svelte b/packages/builder/src/pages/builder/portal/apps/index.svelte
index 59267f37e7..828e620dba 100644
--- a/packages/builder/src/pages/builder/portal/apps/index.svelte
+++ b/packages/builder/src/pages/builder/portal/apps/index.svelte
@@ -10,6 +10,8 @@
Notification,
Body,
Search,
+ DatePicker,
+ BBDatePicker,
} from "@budibase/bbui"
import Spinner from "components/common/Spinner.svelte"
import CreateAppModal from "components/start/CreateAppModal.svelte"
@@ -202,9 +204,38 @@
notifications.error("Error getting init info")
}
})
+
+ let foo = "2023-11-14T15:00:00"
+
+
Date only
+
+ (foo = e.detail)}
+ />
+
+
Date time
+
+ (foo = e.detail)} />
+
+
Date time no timezone
+
+ (foo = e.detail)}
+ />
+
+
+
+
+
+
{#each Object.keys(automationErrors || {}) as appId}
Date: Thu, 2 Nov 2023 17:20:36 +0000
Subject: [PATCH 002/338] Add editable month and year picker to calendar
---
packages/bbui/src/Actions/click_outside.js | 15 +--
.../bbui/src/Form/Core/BBDatePicker.svelte | 101 ++++++++++++++++--
.../pages/builder/portal/apps/index.svelte | 1 +
3 files changed, 100 insertions(+), 17 deletions(-)
diff --git a/packages/bbui/src/Actions/click_outside.js b/packages/bbui/src/Actions/click_outside.js
index 1961dca47c..e2cf38953c 100644
--- a/packages/bbui/src/Actions/click_outside.js
+++ b/packages/bbui/src/Actions/click_outside.js
@@ -1,8 +1,4 @@
-const ignoredClasses = [
- ".flatpickr-calendar",
- ".spectrum-Popover",
- ".download-js-link",
-]
+const ignoredClasses = [".flatpickr-calendar", ".download-js-link"]
let clickHandlers = []
/**
@@ -25,7 +21,14 @@ const handleClick = event => {
return
}
- // Ignore clicks for modals, unless the handler is registered from a modal
+ // Ignore clicks for popovers, unless the handler is registered from one
+ const sourceInPopover = handler.anchor.closest(".spectrum-Popover") != null
+ const clickInPopover = event.target.closest(".spectrum-Popover") != null
+ if (clickInPopover && !sourceInPopover) {
+ return
+ }
+
+ // Ignore clicks for modals, unless the handler is registered from one
const sourceInModal = handler.anchor.closest(".spectrum-Underlay") != null
const clickInModal = event.target.closest(".spectrum-Underlay") != null
if (clickInModal && !sourceInModal) {
diff --git a/packages/bbui/src/Form/Core/BBDatePicker.svelte b/packages/bbui/src/Form/Core/BBDatePicker.svelte
index d159b3dcd6..7163314246 100644
--- a/packages/bbui/src/Form/Core/BBDatePicker.svelte
+++ b/packages/bbui/src/Form/Core/BBDatePicker.svelte
@@ -6,6 +6,7 @@
import Popover from "../../Popover/Popover.svelte"
import dayjs from "dayjs"
import { createEventDispatcher } from "svelte"
+ import Select from "../Select.svelte"
export let id = null
export let disabled = false
@@ -30,22 +31,40 @@
"Saturday",
"Sunday",
]
+ const MonthsOfYear = [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December",
+ ]
let isOpen = false
let anchor
let calendar
let today = dayjs()
- let activeMonth
+ let calendarDate
$: parsedValue = parseValue(value)
$: displayValue = getDisplayValue(parsedValue)
- $: activeMonth = dayjs(parsedValue || today).startOf("month")
- $: mondays = getMondays(activeMonth)
+ $: calendarDate = dayjs(parsedValue || today).startOf("month")
+ $: mondays = getMondays(calendarDate)
const clearDateOnBackspace = event => {
+ // Ignore if we're typing a value
+ if (document.activeElement?.tagName.toLowerCase() === "input") {
+ return
+ }
if (["Backspace", "Clear", "Delete"].includes(event.key)) {
handleChange(null)
- calendar.hide()
+ calendar?.hide()
}
}
@@ -129,6 +148,17 @@
dispatch("change", newValue)
}
+
+ const handleYearChange = e => {
+ let year = parseInt(e.target.value)
+ if (isNaN(year)) {
+ year = calendarDate.year()
+ } else {
+ year = Math.max(0, Math.min(9999, year))
+ }
+ e.target.value = year
+ calendarDate = calendarDate.year(year)
+ }
- {activeMonth.format("MMMM YYYY")}
+
+ ({ label: m, value: idx }))}
+ value={calendarDate.month()}
+ on:change={e => (calendarDate = calendarDate.month(e.detail))}
+ autoWidth
+ />
+
+
(activeMonth = activeMonth.subtract(1, "month"))}
+ on:click={() => (calendarDate = calendarDate.subtract(1, "month"))}
>
(activeMonth = activeMonth.add(1, "month"))}
+ on:click={() => (calendarDate = calendarDate.add(1, "month"))}
>
{#each [0, 1, 2, 3, 4, 5, 6] as dayOffset}
{@const date = monday.add(dayOffset, "days")}
- {@const outsideMonth = date.month() !== activeMonth.month()}
+ {@const outsideMonth = date.month() !== calendarDate.month()}
diff --git a/packages/builder/src/pages/builder/portal/apps/index.svelte b/packages/builder/src/pages/builder/portal/apps/index.svelte
index 828e620dba..5fca9fb189 100644
--- a/packages/builder/src/pages/builder/portal/apps/index.svelte
+++ b/packages/builder/src/pages/builder/portal/apps/index.svelte
@@ -234,6 +234,7 @@
+
From 1b5bb8dd044f19bbc15ac765265fd4a9f2b30271 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Thu, 2 Nov 2023 18:35:58 +0000
Subject: [PATCH 003/338] Add style improvements to new date picker
---
packages/bbui/src/Form/Core/BBDatePicker.svelte | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/packages/bbui/src/Form/Core/BBDatePicker.svelte b/packages/bbui/src/Form/Core/BBDatePicker.svelte
index 7163314246..8c99caea36 100644
--- a/packages/bbui/src/Form/Core/BBDatePicker.svelte
+++ b/packages/bbui/src/Form/Core/BBDatePicker.svelte
@@ -69,6 +69,7 @@
}
const onOpen = () => {
+ calendarDate = dayjs(parsedValue || today).startOf("month")
isOpen = true
if (useKeyboardShortcuts) {
document.addEventListener("keyup", clearDateOnBackspace)
@@ -307,13 +308,13 @@
aria-selected="false"
aria-invalid="false"
title={date.format("dddd, MMMM D, YYYY")}
- on:click={outsideMonth ? null : handleChange(date)}
+ on:click={() => handleChange(date)}
>
{date.date()}
@@ -350,9 +351,6 @@
.spectrum-Calendar {
padding: 8px;
}
- .is-outsideMonth {
- pointer-events: none;
- }
.spectrum-Calendar-title {
display: flex;
justify-content: center;
@@ -362,6 +360,10 @@
.spectrum-Calendar-header button {
border-radius: 4px;
}
+ .spectrum-Calendar-date.is-outsideMonth {
+ visibility: visible;
+ color: var(--spectrum-global-color-gray-400);
+ }
.month-selector :global(.spectrum-Picker),
.year-selector {
@@ -377,6 +379,7 @@
font-family: var(--font-sans);
}
.month-selector :global(.spectrum-Picker:hover),
+ .month-selector :global(.spectrum-Picker.is-open),
.year-selector:hover {
background: var(--spectrum-global-color-gray-200);
}
From f078039aa4d7c962d67948ddaace7b7cf2bb17b9 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Thu, 2 Nov 2023 20:34:40 +0000
Subject: [PATCH 004/338] Add time field and sanitise all typeable fields to
prevent errors and improve experience
---
.../bbui/src/Form/Core/BBDatePicker.svelte | 144 +++++++++++++++---
1 file changed, 121 insertions(+), 23 deletions(-)
diff --git a/packages/bbui/src/Form/Core/BBDatePicker.svelte b/packages/bbui/src/Form/Core/BBDatePicker.svelte
index 8c99caea36..92dbb7f18e 100644
--- a/packages/bbui/src/Form/Core/BBDatePicker.svelte
+++ b/packages/bbui/src/Form/Core/BBDatePicker.svelte
@@ -7,6 +7,8 @@
import dayjs from "dayjs"
import { createEventDispatcher } from "svelte"
import Select from "../Select.svelte"
+ import Icon from "../../Icon/Icon.svelte"
+ import ActionButton from "../../ActionButton/ActionButton.svelte"
export let id = null
export let disabled = false
@@ -150,16 +152,50 @@
dispatch("change", newValue)
}
- const handleYearChange = e => {
- let year = parseInt(e.target.value)
- if (isNaN(year)) {
- year = calendarDate.year()
- } else {
- year = Math.max(0, Math.min(9999, year))
- }
- e.target.value = year
- calendarDate = calendarDate.year(year)
+ const handleMinuteChange = e => {
+ handleChange(parsedValue.minute(parseInt(e.target.value)))
}
+
+ const handleHourChange = e => {
+ handleChange(parsedValue.hour(parseInt(e.target.value)))
+ }
+
+ const handleDateChange = date => {
+ // Select this date at midnight if no current date
+ if (!parsedValue) {
+ handleChange(date)
+ }
+ // Otherwise persist selected time
+ else {
+ handleChange(
+ parsedValue.year(date.year()).month(date.month()).date(date.date())
+ )
+ }
+ }
+
+ const handleCalendarYearChange = e => {
+ calendarDate = calendarDate.year(parseInt(e.target.value))
+ }
+
+ const cleanNumber = ({ max, pad, fallback }) => {
+ return e => {
+ if (e.target.value) {
+ const value = parseInt(e.target.value)
+ if (isNaN(value)) {
+ e.target.value = fallback
+ } else {
+ e.target.value = Math.min(max, value).toString().padStart(pad, "0")
+ }
+ } else {
+ e.target.value = fallback
+ }
+ }
+ }
+
+ // Sanitization utils
+ const cleanYear = cleanNumber({ max: 9999, pad: 0, fallback: today.year() })
+ const cleanHour = cleanNumber({ max: 23, pad: 2, fallback: "00" })
+ const cleanMinute = cleanNumber({ max: 59, pad: 2, fallback: "00" })
handleChange(date)}
+ on:click={() => handleDateChange(date)}
>
+ {#if parsedValue && enableTime}
+
+
+ :
+
+
+ {/if}
From de0f87fa324e4bf80c94cac5026f4dbd64f4a470 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Fri, 3 Nov 2023 09:34:54 +0000
Subject: [PATCH 005/338] Break new datetime picker into granular components
---
.../bbui/src/Form/Core/BBDatePicker.svelte | 493 ------------------
.../Form/Core/DateTimePicker/Calendar.svelte | 206 ++++++++
.../Core/DateTimePicker/DateTimeField.svelte | 98 ++++
.../Core/DateTimePicker/DateTimePicker.svelte | 170 ++++++
.../Core/DateTimePicker/TimePicker.svelte | 65 +++
.../src/Form/Core/DateTimePicker/utils.js | 14 +
packages/bbui/src/index.js | 2 +-
.../pages/builder/portal/apps/index.svelte | 16 +-
8 files changed, 566 insertions(+), 498 deletions(-)
delete mode 100644 packages/bbui/src/Form/Core/BBDatePicker.svelte
create mode 100644 packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte
create mode 100644 packages/bbui/src/Form/Core/DateTimePicker/DateTimeField.svelte
create mode 100644 packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte
create mode 100644 packages/bbui/src/Form/Core/DateTimePicker/TimePicker.svelte
create mode 100644 packages/bbui/src/Form/Core/DateTimePicker/utils.js
diff --git a/packages/bbui/src/Form/Core/BBDatePicker.svelte b/packages/bbui/src/Form/Core/BBDatePicker.svelte
deleted file mode 100644
index 92dbb7f18e..0000000000
--- a/packages/bbui/src/Form/Core/BBDatePicker.svelte
+++ /dev/null
@@ -1,493 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- {#each DaysOfWeek as day}
-
-
- {day[0]}
-
-
- {/each}
-
-
-
- {#each mondays as monday}
-
- {#each [0, 1, 2, 3, 4, 5, 6] as dayOffset}
- {@const date = monday.add(dayOffset, "days")}
- {@const outsideMonth = date.month() !== calendarDate.month()}
- handleDateChange(date)}
- >
-
- {date.date()}
-
-
- {/each}
-
- {/each}
-
-
-
-
- {#if parsedValue && enableTime}
-
-
- :
-
-
- {/if}
-
-
-
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte b/packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte
new file mode 100644
index 0000000000..f943a63254
--- /dev/null
+++ b/packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte
@@ -0,0 +1,206 @@
+
+
+
+
+
+
+
+
+ {#each DaysOfWeek as day}
+
+
+ {day[0]}
+
+
+ {/each}
+
+
+
+ {#each mondays as monday}
+
+ {#each [0, 1, 2, 3, 4, 5, 6] as dayOffset}
+ {@const date = monday.add(dayOffset, "days")}
+ {@const outsideMonth = date.month() !== calendarDate.month()}
+ handleDateChange(date)}
+ >
+
+ {date.date()}
+
+
+ {/each}
+
+ {/each}
+
+
+
+
+
+
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/DateTimeField.svelte b/packages/bbui/src/Form/Core/DateTimePicker/DateTimeField.svelte
new file mode 100644
index 0000000000..cf7d141109
--- /dev/null
+++ b/packages/bbui/src/Form/Core/DateTimePicker/DateTimeField.svelte
@@ -0,0 +1,98 @@
+
+
+
+
+
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte b/packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte
new file mode 100644
index 0000000000..a0e51a38fb
--- /dev/null
+++ b/packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte
@@ -0,0 +1,170 @@
+
+
+
+
+
+ {#if isOpen}
+
+ {#if showCalendar}
+
+ {/if}
+ {#if showCalendar && showTime}
+
+ {/if}
+ {#if showTime}
+
+ {/if}
+
+ {/if}
+
+
+
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/TimePicker.svelte b/packages/bbui/src/Form/Core/DateTimePicker/TimePicker.svelte
new file mode 100644
index 0000000000..60e1f313dc
--- /dev/null
+++ b/packages/bbui/src/Form/Core/DateTimePicker/TimePicker.svelte
@@ -0,0 +1,65 @@
+
+
+
+
+ :
+
+
+
+
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/utils.js b/packages/bbui/src/Form/Core/DateTimePicker/utils.js
new file mode 100644
index 0000000000..953c3eb6c0
--- /dev/null
+++ b/packages/bbui/src/Form/Core/DateTimePicker/utils.js
@@ -0,0 +1,14 @@
+export const cleanInput = ({ max, pad, fallback }) => {
+ return e => {
+ if (e.target.value) {
+ const value = parseInt(e.target.value)
+ if (isNaN(value)) {
+ e.target.value = fallback
+ } else {
+ e.target.value = Math.min(max, value).toString().padStart(pad, "0")
+ }
+ } else {
+ e.target.value = fallback
+ }
+ }
+}
diff --git a/packages/bbui/src/index.js b/packages/bbui/src/index.js
index 407a8c2cec..689cde05d7 100644
--- a/packages/bbui/src/index.js
+++ b/packages/bbui/src/index.js
@@ -54,7 +54,7 @@ export { default as Notification } from "./Notification/Notification.svelte"
export { default as SideNavigation } from "./SideNavigation/Navigation.svelte"
export { default as SideNavigationItem } from "./SideNavigation/Item.svelte"
export { default as DatePicker } from "./Form/DatePicker.svelte"
-export { default as BBDatePicker } from "./Form/Core/BBDatePicker.svelte"
+export { default as DateTimePicker } from "./Form/Core/DateTimePicker/DateTimePicker.svelte"
export { default as Multiselect } from "./Form/Multiselect.svelte"
export { default as Context } from "./context"
export { default as Table } from "./Table/Table.svelte"
diff --git a/packages/builder/src/pages/builder/portal/apps/index.svelte b/packages/builder/src/pages/builder/portal/apps/index.svelte
index 5fca9fb189..dbe4b9626d 100644
--- a/packages/builder/src/pages/builder/portal/apps/index.svelte
+++ b/packages/builder/src/pages/builder/portal/apps/index.svelte
@@ -11,7 +11,7 @@
Body,
Search,
DatePicker,
- BBDatePicker,
+ DateTimePicker,
} from "@budibase/bbui"
import Spinner from "components/common/Spinner.svelte"
import CreateAppModal from "components/start/CreateAppModal.svelte"
@@ -212,7 +212,7 @@
Date only
- (foo = e.detail)}
@@ -220,17 +220,25 @@
Date time
- (foo = e.detail)} />
+ (foo = e.detail)}
+ />
Date time no timezone
- (foo = e.detail)}
/>
+
Time only
+
+ (foo = e.detail)} />
+
From a6bda4fce7aacf66a8f0d0712964615ce56d1cfd Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Fri, 3 Nov 2023 09:35:24 +0000
Subject: [PATCH 006/338] Remove unused props
---
.../bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte | 2 --
1 file changed, 2 deletions(-)
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte b/packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte
index a0e51a38fb..87fbe9f70b 100644
--- a/packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte
+++ b/packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte
@@ -14,8 +14,6 @@
export let placeholder = null
export let timeOnly = false
export let ignoreTimezones = false
- export let range = false
- export let flatpickr
export let useKeyboardShortcuts = true
const dispatch = createEventDispatcher()
From 819df964bc59db0ae2ec026a44153a6efd917e08 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Fri, 3 Nov 2023 11:18:03 +0000
Subject: [PATCH 007/338] Fix style issues in client apps and remove extraneous
dependencies
---
packages/bbui/package.json | 2 +-
.../Form/Core/DateTimePicker/Calendar.svelte | 16 ++++++-
.../Core/DateTimePicker/DateTimeField.svelte | 12 +++---
.../Core/DateTimePicker/DateTimePicker.svelte | 3 ++
packages/client/package.json | 9 +---
yarn.lock | 42 +++----------------
6 files changed, 30 insertions(+), 54 deletions(-)
diff --git a/packages/bbui/package.json b/packages/bbui/package.json
index ccc59646b4..3601af74e9 100644
--- a/packages/bbui/package.json
+++ b/packages/bbui/package.json
@@ -44,7 +44,7 @@
"@spectrum-css/avatar": "3.0.2",
"@spectrum-css/button": "3.0.1",
"@spectrum-css/buttongroup": "3.0.2",
- "@spectrum-css/calendar": "3.0.2",
+ "@spectrum-css/calendar": "3.2.7",
"@spectrum-css/checkbox": "3.0.2",
"@spectrum-css/dialog": "3.0.1",
"@spectrum-css/divider": "1.0.3",
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte b/packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte
index f943a63254..f8386fe6da 100644
--- a/packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte
+++ b/packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte
@@ -1,5 +1,4 @@
+
+
+
+
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/TimePicker.svelte b/packages/bbui/src/Form/Core/DateTimePicker/TimePicker.svelte
index 60e1f313dc..a062c4ce0b 100644
--- a/packages/bbui/src/Form/Core/DateTimePicker/TimePicker.svelte
+++ b/packages/bbui/src/Form/Core/DateTimePicker/TimePicker.svelte
@@ -1,6 +1,7 @@
-
:
-
@@ -45,7 +46,6 @@
diff --git a/packages/client/src/components/app/forms/NewDateTimeField.svelte b/packages/client/src/components/app/forms/NewDateTimeField.svelte
new file mode 100644
index 0000000000..6bcd20d250
--- /dev/null
+++ b/packages/client/src/components/app/forms/NewDateTimeField.svelte
@@ -0,0 +1,53 @@
+
+
+
+ {#if fieldState}
+
+ {/if}
+
From 96bc6fc789521b536e6a128da7a1f1acdb81452f Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Fri, 3 Nov 2023 17:05:27 +0000
Subject: [PATCH 009/338] Add better support for time only date strings
---
.../Form/Core/DateTimePicker/DateTimePicker.svelte | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte b/packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte
index b04ae3cdb6..3ae2571166 100644
--- a/packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte
+++ b/packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte
@@ -56,8 +56,17 @@
}
const parseValue = value => {
- // Sanity check that we have a valid value
- const parsedDate = dayjs(value)
+ let parsedDate
+
+ // Attempt to parse as a time-only string if required
+ if (typeof value === "string" && timeOnly) {
+ parsedDate = dayjs(`0-${value}`)
+ }
+
+ // Attempt to parse as normal if required
+ if (!parsedDate?.isValid()) {
+ parsedDate = dayjs(value)
+ }
if (!parsedDate?.isValid()) {
return null
}
From 93630b36e2a57b83d197c65d816d42eed849f2e1 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Fri, 3 Nov 2023 17:07:31 +0000
Subject: [PATCH 010/338] Fix small inconsistency when picker is open for
multiple minutes
---
packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte | 2 +-
packages/bbui/src/Form/Core/DateTimePicker/TimePicker.svelte | 4 +---
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte b/packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte
index d48afa1f35..ae32190e2e 100644
--- a/packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte
+++ b/packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte
@@ -34,7 +34,7 @@
const now = dayjs()
let calendarDate
- $: calendarDate = dayjs(value || now).startOf("month")
+ $: calendarDate = dayjs(value || dayjs()).startOf("month")
$: mondays = getMondays(calendarDate)
const getMondays = monthStart => {
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/TimePicker.svelte b/packages/bbui/src/Form/Core/DateTimePicker/TimePicker.svelte
index a062c4ce0b..adf2a5e87a 100644
--- a/packages/bbui/src/Form/Core/DateTimePicker/TimePicker.svelte
+++ b/packages/bbui/src/Form/Core/DateTimePicker/TimePicker.svelte
@@ -6,9 +6,7 @@
export let value
export let onChange
- const now = dayjs()
-
- $: displayValue = value || now
+ $: displayValue = value || dayjs()
const handleHourChange = e => {
onChange(displayValue.hour(parseInt(e.target.value)))
From e758582822f474aa7e2c3112fd34a8f022952b00 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Fri, 3 Nov 2023 19:26:43 +0000
Subject: [PATCH 011/338] Add cross browser style improvements for datepicker
---
.../Form/Core/DateTimePicker/Calendar.svelte | 13 ++++++++++---
.../Core/DateTimePicker/DateTimePicker.svelte | 18 ++++++++++++++----
.../Core/DateTimePicker/NumberInput.svelte | 1 +
3 files changed, 25 insertions(+), 7 deletions(-)
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte b/packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte
index ae32190e2e..6491ba94ab 100644
--- a/packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte
+++ b/packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte
@@ -61,6 +61,10 @@
onChange(base.year(date.year()).month(date.month()).date(date.date()))
}
+ export const setDate = date => {
+ calendarDate = date
+ }
+
const cleanYear = cleanInput({ max: 9999, pad: 0, fallback: now.year() })
@@ -85,6 +89,7 @@
value={calendarDate.year()}
min={0}
max={9999}
+ width={64}
on:change={handleCalendarYearChange}
on:input={cleanYear}
/>
@@ -173,6 +178,9 @@
.spectrum-Calendar {
width: auto;
}
+ .spectrum-Calendar-header {
+ width: auto;
+ }
.spectrum-Calendar-title {
display: flex;
justify-content: flex-start;
@@ -196,12 +204,11 @@
background: var(--spectrum-global-color-blue-400);
}
.spectrum-Calendar tr {
+ box-sizing: content-box;
height: 40px;
}
.spectrum-Calendar-tableCell {
- height: 32px;
- width: 32px;
- padding: 4px;
+ box-sizing: content-box;
}
.spectrum-Calendar-nextMonth,
.spectrum-Calendar-prevMonth {
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte b/packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte
index 3ae2571166..cccfea612c 100644
--- a/packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte
+++ b/packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte
@@ -25,6 +25,7 @@
let isOpen = false
let anchor
let popover
+ let calendar
$: parsedValue = parseValue(value)
$: showCalendar = !timeOnly
@@ -107,6 +108,12 @@
dispatch("change", newValue)
}
+
+ const setToNow = () => {
+ const now = dayjs()
+ calendar?.setDate(now)
+ handleChange(now)
+ }
{#if showCalendar}
-
+
{/if}
@@ -164,7 +175,7 @@
display: flex;
justify-content: space-between;
align-items: center;
- gap: 32px;
+ gap: 60px;
}
.footer.spaced {
padding-top: 14px;
@@ -175,6 +186,5 @@
display: flex;
justify-content: flex-end;
gap: 6px;
- margin-right: 4px;
}
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/NumberInput.svelte b/packages/bbui/src/Form/Core/DateTimePicker/NumberInput.svelte
index 5b7c6dccce..8d7c72e94e 100644
--- a/packages/bbui/src/Form/Core/DateTimePicker/NumberInput.svelte
+++ b/packages/bbui/src/Form/Core/DateTimePicker/NumberInput.svelte
@@ -34,6 +34,7 @@
font-weight: bold;
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
+ box-sizing: content-box;
}
input:focus,
input:hover {
From 78af50bafe9956793ace3f78c3aa77a9f367879b Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Fri, 3 Nov 2023 19:34:12 +0000
Subject: [PATCH 012/338] Use new datepicker except when a range is needed
---
.../Calendar.svelte | 0
.../DateInput.svelte} | 0
.../Form/Core/DatePicker/DatePicker.svelte | 10 ++++
.../FlatpickrDatePicker.svelte} | 2 +-
.../NumberInput.svelte | 0
.../SpectrumDatePicker.svelte} | 2 +-
.../TimePicker.svelte | 0
.../{DateTimePicker => DatePicker}/utils.js | 0
packages/bbui/src/Form/Core/index.js | 2 +-
packages/bbui/src/Form/DatePicker.svelte | 2 +-
packages/bbui/src/index.js | 2 +-
.../new/_components/componentStructure.json | 1 +
.../pages/builder/portal/apps/index.svelte | 40 --------------
.../app/forms/NewDateTimeField.svelte | 53 -------------------
14 files changed, 16 insertions(+), 98 deletions(-)
rename packages/bbui/src/Form/Core/{DateTimePicker => DatePicker}/Calendar.svelte (100%)
rename packages/bbui/src/Form/Core/{DateTimePicker/DateTimeField.svelte => DatePicker/DateInput.svelte} (100%)
create mode 100644 packages/bbui/src/Form/Core/DatePicker/DatePicker.svelte
rename packages/bbui/src/Form/Core/{DatePicker.svelte => DatePicker/FlatpickrDatePicker.svelte} (99%)
rename packages/bbui/src/Form/Core/{DateTimePicker => DatePicker}/NumberInput.svelte (100%)
rename packages/bbui/src/Form/Core/{DateTimePicker/DateTimePicker.svelte => DatePicker/SpectrumDatePicker.svelte} (98%)
rename packages/bbui/src/Form/Core/{DateTimePicker => DatePicker}/TimePicker.svelte (100%)
rename packages/bbui/src/Form/Core/{DateTimePicker => DatePicker}/utils.js (100%)
delete mode 100644 packages/client/src/components/app/forms/NewDateTimeField.svelte
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte b/packages/bbui/src/Form/Core/DatePicker/Calendar.svelte
similarity index 100%
rename from packages/bbui/src/Form/Core/DateTimePicker/Calendar.svelte
rename to packages/bbui/src/Form/Core/DatePicker/Calendar.svelte
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/DateTimeField.svelte b/packages/bbui/src/Form/Core/DatePicker/DateInput.svelte
similarity index 100%
rename from packages/bbui/src/Form/Core/DateTimePicker/DateTimeField.svelte
rename to packages/bbui/src/Form/Core/DatePicker/DateInput.svelte
diff --git a/packages/bbui/src/Form/Core/DatePicker/DatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker/DatePicker.svelte
new file mode 100644
index 0000000000..ec1514718c
--- /dev/null
+++ b/packages/bbui/src/Form/Core/DatePicker/DatePicker.svelte
@@ -0,0 +1,10 @@
+
+
+
diff --git a/packages/bbui/src/Form/Core/DatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker/FlatpickrDatePicker.svelte
similarity index 99%
rename from packages/bbui/src/Form/Core/DatePicker.svelte
rename to packages/bbui/src/Form/Core/DatePicker/FlatpickrDatePicker.svelte
index 7ce15292be..8d92f7633d 100644
--- a/packages/bbui/src/Form/Core/DatePicker.svelte
+++ b/packages/bbui/src/Form/Core/DatePicker/FlatpickrDatePicker.svelte
@@ -5,7 +5,7 @@
import "@spectrum-css/textfield/dist/index-vars.css"
import "@spectrum-css/picker/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
- import { uuid } from "../../helpers"
+ import { uuid } from "../../../helpers"
export let id = null
export let disabled = false
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/NumberInput.svelte b/packages/bbui/src/Form/Core/DatePicker/NumberInput.svelte
similarity index 100%
rename from packages/bbui/src/Form/Core/DateTimePicker/NumberInput.svelte
rename to packages/bbui/src/Form/Core/DatePicker/NumberInput.svelte
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte b/packages/bbui/src/Form/Core/DatePicker/SpectrumDatePicker.svelte
similarity index 98%
rename from packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte
rename to packages/bbui/src/Form/Core/DatePicker/SpectrumDatePicker.svelte
index cccfea612c..d01097d357 100644
--- a/packages/bbui/src/Form/Core/DateTimePicker/DateTimePicker.svelte
+++ b/packages/bbui/src/Form/Core/DatePicker/SpectrumDatePicker.svelte
@@ -7,7 +7,7 @@
import { createEventDispatcher } from "svelte"
import TimePicker from "./TimePicker.svelte"
import Calendar from "./Calendar.svelte"
- import DateTimeInput from "./DateTimeField.svelte"
+ import DateTimeInput from "./DateInput.svelte"
import ActionButton from "../../../ActionButton/ActionButton.svelte"
export let id = null
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/TimePicker.svelte b/packages/bbui/src/Form/Core/DatePicker/TimePicker.svelte
similarity index 100%
rename from packages/bbui/src/Form/Core/DateTimePicker/TimePicker.svelte
rename to packages/bbui/src/Form/Core/DatePicker/TimePicker.svelte
diff --git a/packages/bbui/src/Form/Core/DateTimePicker/utils.js b/packages/bbui/src/Form/Core/DatePicker/utils.js
similarity index 100%
rename from packages/bbui/src/Form/Core/DateTimePicker/utils.js
rename to packages/bbui/src/Form/Core/DatePicker/utils.js
diff --git a/packages/bbui/src/Form/Core/index.js b/packages/bbui/src/Form/Core/index.js
index b0edf52748..4af2507147 100644
--- a/packages/bbui/src/Form/Core/index.js
+++ b/packages/bbui/src/Form/Core/index.js
@@ -8,7 +8,7 @@ export { default as CoreTextArea } from "./TextArea.svelte"
export { default as CoreCombobox } from "./Combobox.svelte"
export { default as CoreSwitch } from "./Switch.svelte"
export { default as CoreSearch } from "./Search.svelte"
-export { default as CoreDatePicker } from "./DatePicker.svelte"
+export { default as CoreDatePicker } from "./DatePicker/DatePicker.svelte"
export { default as CoreDropzone } from "./Dropzone.svelte"
export { default as CoreStepper } from "./Stepper.svelte"
export { default as CoreRichTextField } from "./RichTextField.svelte"
diff --git a/packages/bbui/src/Form/DatePicker.svelte b/packages/bbui/src/Form/DatePicker.svelte
index 04ce8b5467..49a66003fc 100644
--- a/packages/bbui/src/Form/DatePicker.svelte
+++ b/packages/bbui/src/Form/DatePicker.svelte
@@ -1,6 +1,6 @@
-
-
Date only
-
- (foo = e.detail)}
- />
-
-
Date time
-
- (foo = e.detail)}
- />
-
-
Date time no timezone
-
- (foo = e.detail)}
- />
-
-
Time only
-
- (foo = e.detail)} />
-
-
-
-
-
-
-
{#each Object.keys(automationErrors || {}) as appId}
- import { CoreDatePicker } from "@budibase/bbui"
- import Field from "./Field.svelte"
-
- export let field
- export let label
- export let placeholder
- export let disabled = false
- export let enableTime = true
- export let timeOnly = false
- export let time24hr = false
- export let ignoreTimezones = false
- export let validation
- export let defaultValue
- export let onChange
-
- let fieldState
- let fieldApi
-
- const handleChange = e => {
- const changed = fieldApi.setValue(e.detail)
- if (onChange && changed) {
- onChange({ value: e.detail })
- }
- }
-
-
-
- {#if fieldState}
-
- {/if}
-
From b2352157cdf1cec5b136c766660539649e0a25fe Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Fri, 3 Nov 2023 20:27:30 +0000
Subject: [PATCH 013/338] Improve grid integration with new datepicker
---
packages/bbui/src/Actions/position_dropdown.js | 6 ++++--
.../src/Form/Core/DatePicker/DatePicker.svelte | 5 +++++
.../Core/DatePicker/SpectrumDatePicker.svelte | 17 ++++++++++++++++-
.../src/components/grid/cells/DateCell.svelte | 15 +++++----------
4 files changed, 30 insertions(+), 13 deletions(-)
diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js
index f2018272f6..7c4f7e8a2a 100644
--- a/packages/bbui/src/Actions/position_dropdown.js
+++ b/packages/bbui/src/Actions/position_dropdown.js
@@ -39,11 +39,13 @@ export default function positionDropdown(element, opts) {
styles = customUpdate(anchorBounds, elementBounds, styles)
} else {
// Determine vertical styles
+ const topSpace = anchorBounds.top
+ const bottomSpace = window.innerHeight - anchorBounds.bottom
if (align === "right-outside") {
styles.top = anchorBounds.top
} else if (
- window.innerHeight - anchorBounds.bottom <
- (maxHeight || 100)
+ window.innerHeight - anchorBounds.bottom < (maxHeight || 100) &&
+ topSpace - bottomSpace > 100
) {
styles.top = anchorBounds.top - elementBounds.height - offset
styles.maxHeight = maxHeight || 240
diff --git a/packages/bbui/src/Form/Core/DatePicker/DatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker/DatePicker.svelte
index ec1514718c..a03be24753 100644
--- a/packages/bbui/src/Form/Core/DatePicker/DatePicker.svelte
+++ b/packages/bbui/src/Form/Core/DatePicker/DatePicker.svelte
@@ -1,10 +1,15 @@
diff --git a/packages/bbui/src/Form/Core/DatePicker/SpectrumDatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker/SpectrumDatePicker.svelte
index d01097d357..97774da51b 100644
--- a/packages/bbui/src/Form/Core/DatePicker/SpectrumDatePicker.svelte
+++ b/packages/bbui/src/Form/Core/DatePicker/SpectrumDatePicker.svelte
@@ -4,7 +4,7 @@
import "@spectrum-css/textfield/dist/index-vars.css"
import Popover from "../../../Popover/Popover.svelte"
import dayjs from "dayjs"
- import { createEventDispatcher } from "svelte"
+ import { createEventDispatcher, onMount } from "svelte"
import TimePicker from "./TimePicker.svelte"
import Calendar from "./Calendar.svelte"
import DateTimeInput from "./DateInput.svelte"
@@ -19,6 +19,9 @@
export let timeOnly = false
export let ignoreTimezones = false
export let useKeyboardShortcuts = true
+ export let appendTo = null
+ export let api = null
+ export let align = "left"
const dispatch = createEventDispatcher()
@@ -114,6 +117,13 @@
calendar?.setDate(now)
handleChange(now)
}
+
+ onMount(() => {
+ api = {
+ open: () => popover?.show(),
+ close: () => popover?.hide(),
+ }
+ })
{#if isOpen}
diff --git a/packages/frontend-core/src/components/grid/cells/DateCell.svelte b/packages/frontend-core/src/components/grid/cells/DateCell.svelte
index 53b159ee30..fae75277b4 100644
--- a/packages/frontend-core/src/components/grid/cells/DateCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/DateCell.svelte
@@ -10,7 +10,7 @@
export let readonly = false
export let api
- let flatpickr
+ let datePickerAPI
let isOpen
// Adding the 0- will turn a string like 00:00:00 into a valid ISO
@@ -41,7 +41,7 @@
// Ensure we close flatpickr when unselected
$: {
if (!focused) {
- flatpickr?.close()
+ datePickerAPI?.close()
}
}
@@ -52,8 +52,8 @@
onMount(() => {
api = {
onKeyDown,
- focus: () => flatpickr?.open(),
- blur: () => flatpickr?.close(),
+ focus: () => datePickerAPI?.open(),
+ blur: () => datePickerAPI?.close(),
isActive: () => isOpen,
}
})
@@ -75,12 +75,10 @@
onChange(e.detail)}
- appendTo={document.documentElement}
enableTime={!dateOnly}
{timeOnly}
- time24hr
ignoreTimezones={schema.ignoreTimezones}
- bind:flatpickr
+ bind:api={datePickerAPI}
on:open={() => (isOpen = true)}
on:close={() => (isOpen = false)}
useKeyboardShortcuts={false}
@@ -110,9 +108,6 @@
position: absolute;
opacity: 0;
}
- .picker :global(.flatpickr) {
- min-width: 0;
- }
.picker :global(.spectrum-Textfield-input) {
width: 100%;
}
From f5369e7cddda61fde78e643309d367f24880382e Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Tue, 2 Apr 2024 10:50:37 +0100
Subject: [PATCH 014/338] Update pro
---
packages/pro | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/pro b/packages/pro
index 3820c0c93a..6b62505be0 160000
--- a/packages/pro
+++ b/packages/pro
@@ -1 +1 @@
-Subproject commit 3820c0c93a3e448e10a60a9feb5396844b537ca8
+Subproject commit 6b62505be0c0b50a57b4f4980d86541ebdc86428
From 2e4e3eac60f7ed722df649c48d6de0105e91ec01 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Tue, 2 Apr 2024 10:59:30 +0100
Subject: [PATCH 015/338] Update styles of selected day
---
packages/bbui/src/Form/Core/DatePicker/Calendar.svelte | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/packages/bbui/src/Form/Core/DatePicker/Calendar.svelte b/packages/bbui/src/Form/Core/DatePicker/Calendar.svelte
index 6491ba94ab..75e8e193e6 100644
--- a/packages/bbui/src/Form/Core/DatePicker/Calendar.svelte
+++ b/packages/bbui/src/Form/Core/DatePicker/Calendar.svelte
@@ -125,7 +125,6 @@
@@ -194,11 +193,13 @@
visibility: visible;
color: var(--spectrum-global-color-gray-400);
}
- .spectrum-Calendar-date.is-today:before {
+ .spectrum-Calendar-date.is-today,
+ .spectrum-Calendar-date.is-today::before {
border-color: var(--spectrum-global-color-gray-400);
}
- .spectrum-Calendar-date.is-today {
- border-color: var(--spectrum-global-color-gray-400);
+ .spectrum-Calendar-date.is-today.is-selected,
+ .spectrum-Calendar-date.is-today.is-selected::before {
+ border-color: var(--spectrum-global-color-blue-700);
}
.spectrum-Calendar-date.is-selected:not(.is-range-selection) {
background: var(--spectrum-global-color-blue-400);
From d71766b9a4624cf7cd81bfb641281bfff35bac1f Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Tue, 2 Apr 2024 11:01:50 +0100
Subject: [PATCH 016/338] Revert clickoutside
---
packages/bbui/src/Actions/click_outside.js | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/packages/bbui/src/Actions/click_outside.js b/packages/bbui/src/Actions/click_outside.js
index 305ae65ac8..7873b7fd6c 100644
--- a/packages/bbui/src/Actions/click_outside.js
+++ b/packages/bbui/src/Actions/click_outside.js
@@ -1,4 +1,8 @@
-const ignoredClasses = [".flatpickr-calendar", ".download-js-link"]
+const ignoredClasses = [
+ ".flatpickr-calendar",
+ ".spectrum-Popover",
+ ".download-js-link",
+]
let clickHandlers = []
/**
@@ -21,13 +25,6 @@ const handleClick = event => {
return
}
- // Ignore clicks for popovers, unless the handler is registered from one
- const sourceInPopover = handler.anchor.closest(".spectrum-Popover") != null
- const clickInPopover = event.target.closest(".spectrum-Popover") != null
- if (clickInPopover && !sourceInPopover) {
- return
- }
-
// Ignore clicks for modals, unless the handler is registered from one
const sourceInModal = handler.anchor.closest(".spectrum-Underlay") != null
const clickInModal = event.target.closest(".spectrum-Underlay") != null
From 24f67617669d90442da2c4f8b2a019c8f4d59cc5 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Tue, 2 Apr 2024 11:02:10 +0100
Subject: [PATCH 017/338] Revert clickoutside
---
packages/bbui/src/Actions/click_outside.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/bbui/src/Actions/click_outside.js b/packages/bbui/src/Actions/click_outside.js
index 7873b7fd6c..eafca657f3 100644
--- a/packages/bbui/src/Actions/click_outside.js
+++ b/packages/bbui/src/Actions/click_outside.js
@@ -25,7 +25,7 @@ const handleClick = event => {
return
}
- // Ignore clicks for modals, unless the handler is registered from one
+ // Ignore clicks for modals, unless the handler is registered from a modal
const sourceInModal = handler.anchor.closest(".spectrum-Underlay") != null
const clickInModal = event.target.closest(".spectrum-Underlay") != null
if (clickInModal && !sourceInModal) {
From 4673fbeaaac1efd9b63d97b8e35001862c7e9d02 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Tue, 2 Apr 2024 11:08:37 +0100
Subject: [PATCH 018/338] Fix overflow
---
packages/bbui/src/Form/Core/DatePicker/SpectrumDatePicker.svelte | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/bbui/src/Form/Core/DatePicker/SpectrumDatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker/SpectrumDatePicker.svelte
index 97774da51b..3a96044df4 100644
--- a/packages/bbui/src/Form/Core/DatePicker/SpectrumDatePicker.svelte
+++ b/packages/bbui/src/Form/Core/DatePicker/SpectrumDatePicker.svelte
@@ -185,6 +185,7 @@
diff --git a/packages/bbui/src/Form/Core/DatePicker/FlatpickrDatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker/FlatpickrDatePicker.svelte
deleted file mode 100644
index ac163de3bb..0000000000
--- a/packages/bbui/src/Form/Core/DatePicker/FlatpickrDatePicker.svelte
+++ /dev/null
@@ -1,268 +0,0 @@
-
-
-{#key redrawOptions}
-
-
-
-{/key}
-{#if open}
-
-
-{/if}
-
-
diff --git a/packages/bbui/src/Form/Core/DatePicker/SpectrumDatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker/SpectrumDatePicker.svelte
deleted file mode 100644
index bd9fb8269e..0000000000
--- a/packages/bbui/src/Form/Core/DatePicker/SpectrumDatePicker.svelte
+++ /dev/null
@@ -1,183 +0,0 @@
-
-
-
-
-
- {#if isOpen}
-
- {#if showCalendar}
-
- {/if}
-
-
- {/if}
-
-
-
diff --git a/packages/bbui/src/Form/Core/DateRangePicker.svelte b/packages/bbui/src/Form/Core/DateRangePicker.svelte
new file mode 100644
index 0000000000..9084942ba7
--- /dev/null
+++ b/packages/bbui/src/Form/Core/DateRangePicker.svelte
@@ -0,0 +1,69 @@
+
+
+
+
(fromDate = e.detail)}
+ enableTime={false}
+ />
+
+
+
+ (toDate = e.detail)}
+ enableTime={false}
+ />
+
+
+
diff --git a/packages/bbui/src/Form/Core/index.js b/packages/bbui/src/Form/Core/index.js
index 4af2507147..533a1956c5 100644
--- a/packages/bbui/src/Form/Core/index.js
+++ b/packages/bbui/src/Form/Core/index.js
@@ -9,6 +9,7 @@ export { default as CoreCombobox } from "./Combobox.svelte"
export { default as CoreSwitch } from "./Switch.svelte"
export { default as CoreSearch } from "./Search.svelte"
export { default as CoreDatePicker } from "./DatePicker/DatePicker.svelte"
+export { default as CoreDateRangePicker } from "./DateRangePicker.svelte"
export { default as CoreDropzone } from "./Dropzone.svelte"
export { default as CoreStepper } from "./Stepper.svelte"
export { default as CoreRichTextField } from "./RichTextField.svelte"
diff --git a/packages/bbui/src/Form/DatePicker.svelte b/packages/bbui/src/Form/DatePicker.svelte
index 5e84947f0a..cf249e5bb8 100644
--- a/packages/bbui/src/Form/DatePicker.svelte
+++ b/packages/bbui/src/Form/DatePicker.svelte
@@ -15,18 +15,12 @@
export let placeholder = null
export let appendTo = undefined
export let ignoreTimezones = false
- export let range = false
export let helpText = null
+
const dispatch = createEventDispatcher()
const onChange = e => {
- if (range) {
- // Flatpickr cant take two dates and work out what to display, needs to be provided a string.
- // Like - "Date1 to Date2". Hence passing in that specifically from the array
- value = e?.detail[1]
- } else {
- value = e.detail
- }
+ value = e.detail
dispatch("change", e.detail)
}
@@ -43,7 +37,6 @@
{time24hr}
{appendTo}
{ignoreTimezones}
- {range}
on:change={onChange}
/>
diff --git a/packages/bbui/src/Form/DateRangePicker.svelte b/packages/bbui/src/Form/DateRangePicker.svelte
new file mode 100644
index 0000000000..39c2acb96a
--- /dev/null
+++ b/packages/bbui/src/Form/DateRangePicker.svelte
@@ -0,0 +1,34 @@
+
+
+
+
+
diff --git a/packages/bbui/src/index.js b/packages/bbui/src/index.js
index 222e558675..f28e185305 100644
--- a/packages/bbui/src/index.js
+++ b/packages/bbui/src/index.js
@@ -3,13 +3,34 @@ import "./bbui.css"
// Spectrum icons
import "@spectrum-css/icon/dist/index-vars.css"
-// Components
+// Form components
export { default as Input } from "./Form/Input.svelte"
export { default as Stepper } from "./Form/Stepper.svelte"
export { default as TextArea } from "./Form/TextArea.svelte"
export { default as Select } from "./Form/Select.svelte"
export { default as Combobox } from "./Form/Combobox.svelte"
export { default as Dropzone } from "./Form/Dropzone.svelte"
+export { default as DatePicker } from "./Form/DatePicker.svelte"
+export { default as DateRangePicker } from "./Form/DateRangePicker.svelte"
+export { default as Toggle } from "./Form/Toggle.svelte"
+export { default as RadioGroup } from "./Form/RadioGroup.svelte"
+export { default as Checkbox } from "./Form/Checkbox.svelte"
+export { default as InputDropdown } from "./Form/InputDropdown.svelte"
+export { default as PickerDropdown } from "./Form/PickerDropdown.svelte"
+export { default as EnvDropdown } from "./Form/EnvDropdown.svelte"
+export { default as Multiselect } from "./Form/Multiselect.svelte"
+export { default as Search } from "./Form/Search.svelte"
+export { default as RichTextField } from "./Form/RichTextField.svelte"
+export { default as Slider } from "./Form/Slider.svelte"
+export { default as File } from "./Form/File.svelte"
+
+// Core form components to be used elsewhere (standard components)
+export * from "./Form/Core"
+
+// Fancy form components
+export * from "./FancyForm"
+
+// Components
export { default as Drawer } from "./Drawer/Drawer.svelte"
export { default as DrawerContent } from "./Drawer/DrawerContent.svelte"
export { default as Avatar } from "./Avatar/Avatar.svelte"
@@ -21,12 +42,6 @@ export { default as ButtonGroup } from "./ButtonGroup/ButtonGroup.svelte"
export { default as ClearButton } from "./ClearButton/ClearButton.svelte"
export { default as Icon } from "./Icon/Icon.svelte"
export { default as IconAvatar } from "./Icon/IconAvatar.svelte"
-export { default as Toggle } from "./Form/Toggle.svelte"
-export { default as RadioGroup } from "./Form/RadioGroup.svelte"
-export { default as Checkbox } from "./Form/Checkbox.svelte"
-export { default as InputDropdown } from "./Form/InputDropdown.svelte"
-export { default as PickerDropdown } from "./Form/PickerDropdown.svelte"
-export { default as EnvDropdown } from "./Form/EnvDropdown.svelte"
export { default as DetailSummary } from "./DetailSummary/DetailSummary.svelte"
export { default as Popover } from "./Popover/Popover.svelte"
export { default as ProgressBar } from "./ProgressBar/ProgressBar.svelte"
@@ -37,11 +52,6 @@ export { default as Page } from "./Layout/Page.svelte"
export { default as Link } from "./Link/Link.svelte"
export { default as Tooltip } from "./Tooltip/Tooltip.svelte"
export { default as TempTooltip } from "./Tooltip/TempTooltip.svelte"
-export {
- default as AbsTooltip,
- TooltipPosition,
- TooltipType,
-} from "./Tooltip/AbsTooltip.svelte"
export { default as TooltipWrapper } from "./Tooltip/TooltipWrapper.svelte"
export { default as Menu } from "./Menu/Menu.svelte"
export { default as MenuSection } from "./Menu/Section.svelte"
@@ -53,9 +63,6 @@ export { default as NotificationDisplay } from "./Notification/NotificationDispl
export { default as Notification } from "./Notification/Notification.svelte"
export { default as SideNavigation } from "./SideNavigation/Navigation.svelte"
export { default as SideNavigationItem } from "./SideNavigation/Item.svelte"
-export { default as DatePicker } from "./Form/DatePicker.svelte"
-export { default as DateTimePicker } from "./Form/Core/DatePicker/SpectrumDatePicker.svelte"
-export { default as Multiselect } from "./Form/Multiselect.svelte"
export { default as Context } from "./context"
export { default as Table } from "./Table/Table.svelte"
export { default as Tabs } from "./Tabs/Tabs.svelte"
@@ -65,7 +72,6 @@ export { default as Tag } from "./Tags/Tag.svelte"
export { default as TreeView } from "./TreeView/Tree.svelte"
export { default as TreeItem } from "./TreeView/Item.svelte"
export { default as Divider } from "./Divider/Divider.svelte"
-export { default as Search } from "./Form/Search.svelte"
export { default as Pagination } from "./Pagination/Pagination.svelte"
export { default as Badge } from "./Badge/Badge.svelte"
export { default as StatusLight } from "./StatusLight/StatusLight.svelte"
@@ -77,15 +83,15 @@ export { default as CopyInput } from "./Input/CopyInput.svelte"
export { default as BannerDisplay } from "./Banner/BannerDisplay.svelte"
export { default as MarkdownEditor } from "./Markdown/MarkdownEditor.svelte"
export { default as MarkdownViewer } from "./Markdown/MarkdownViewer.svelte"
-export { default as RichTextField } from "./Form/RichTextField.svelte"
export { default as List } from "./List/List.svelte"
export { default as ListItem } from "./List/ListItem.svelte"
export { default as IconSideNav } from "./IconSideNav/IconSideNav.svelte"
export { default as IconSideNavItem } from "./IconSideNav/IconSideNavItem.svelte"
-export { default as Slider } from "./Form/Slider.svelte"
export { default as Accordion } from "./Accordion/Accordion.svelte"
-export { default as File } from "./Form/File.svelte"
export { default as OptionSelectDnD } from "./OptionSelectDnD/OptionSelectDnD.svelte"
+export { default as AbsTooltip } from "./Tooltip/AbsTooltip.svelte"
+export { TooltipPosition, TooltipType } from "./Tooltip/AbsTooltip.svelte"
+
// Renderers
export { default as BoldRenderer } from "./Table/BoldRenderer.svelte"
export { default as CodeRenderer } from "./Table/CodeRenderer.svelte"
@@ -97,9 +103,6 @@ export { default as Heading } from "./Typography/Heading.svelte"
export { default as Detail } from "./Typography/Detail.svelte"
export { default as Code } from "./Typography/Code.svelte"
-// Core form components to be used elsewhere (standard components)
-export * from "./Form/Core"
-
// Actions
export { default as autoResizeTextArea } from "./Actions/autoresize_textarea"
export { default as positionDropdown } from "./Actions/position_dropdown"
@@ -111,6 +114,3 @@ export { banner, BANNER_TYPES } from "./Stores/banner"
// Helpers
export * as Helpers from "./helpers"
-
-// Fancy form components
-export * from "./FancyForm"
diff --git a/packages/builder/src/pages/builder/app/[application]/settings/backups/index.svelte b/packages/builder/src/pages/builder/app/[application]/settings/backups/index.svelte
index bbf28b2fe8..212243aa76 100644
--- a/packages/builder/src/pages/builder/app/[application]/settings/backups/index.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/settings/backups/index.svelte
@@ -1,7 +1,7 @@
@@ -207,7 +207,7 @@
View plans
- {:else if !backupData?.length && !loading && !filterOpt && !startDate}
+ {:else if !backupData?.length && !loading && !filterOpt && !date}
@@ -236,19 +236,19 @@
bind:value={filterOpt}
/>
- {
- startDate = e.detail?.[0]
- endDate = e.detail?.[1]
+ date = e.detail
}}
+ enableTime={false}
/>
- Create new backup
+
+ Create new backup
+
diff --git a/packages/client/src/components/app/DateRangePicker.svelte b/packages/client/src/components/app/DateRangePicker.svelte
deleted file mode 100644
index 5c710ad766..0000000000
--- a/packages/client/src/components/app/DateRangePicker.svelte
+++ /dev/null
@@ -1,79 +0,0 @@
-
-
-
- (value = e.detail)}
- />
-
diff --git a/packages/client/src/components/app/index.js b/packages/client/src/components/app/index.js
index e23e19704c..1c5722eb40 100644
--- a/packages/client/src/components/app/index.js
+++ b/packages/client/src/components/app/index.js
@@ -29,7 +29,6 @@ export { default as image } from "./Image.svelte"
export { default as embed } from "./Embed.svelte"
export { default as icon } from "./Icon.svelte"
export { default as backgroundimage } from "./BackgroundImage.svelte"
-export { default as daterangepicker } from "./DateRangePicker.svelte"
export { default as cardstat } from "./CardStat.svelte"
export { default as spectrumcard } from "./SpectrumCard.svelte"
export { default as tag } from "./Tag.svelte"
From 0a4dfef8cfe4d487610ef845f5541ea5bfed927f Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Wed, 10 Apr 2024 18:46:41 +0100
Subject: [PATCH 031/338] Replace usages of date range picker with 2 date
pickers
---
packages/bbui/src/helpers.js | 2 +-
.../settings/backups/index.svelte | 35 +++++++++------
.../portal/account/auditLogs/index.svelte | 44 +++++++++++--------
packages/frontend-core/src/api/backups.js | 3 --
4 files changed, 48 insertions(+), 36 deletions(-)
diff --git a/packages/bbui/src/helpers.js b/packages/bbui/src/helpers.js
index 80a11f9151..7beaf4e878 100644
--- a/packages/bbui/src/helpers.js
+++ b/packages/bbui/src/helpers.js
@@ -124,7 +124,7 @@ export const parseDate = (value, { timeOnly, dateOnly } = {}) => {
}
// Certain string values need transformed
- if (typeof value !== "string") {
+ if (typeof value === "string") {
if (timeOnly || !isNaN(new Date(`0-${value}`))) {
value = `0-${value}`
}
diff --git a/packages/builder/src/pages/builder/app/[application]/settings/backups/index.svelte b/packages/builder/src/pages/builder/app/[application]/settings/backups/index.svelte
index 31b41e73aa..18d76480f4 100644
--- a/packages/builder/src/pages/builder/app/[application]/settings/backups/index.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/settings/backups/index.svelte
@@ -31,7 +31,8 @@
let backupData = null
let pageInfo = createPaginationStore()
let filterOpt = null
- let date = null
+ let startDate = null
+ let endDate = null
let filters = [
{
label: "Manual backup",
@@ -52,9 +53,9 @@
]
$: page = $pageInfo.page
- $: fetchBackups(filterOpt, page, date)
+ $: fetchBackups(filterOpt, page, startDate, endDate)
- const schema = {
+ let schema = {
type: {
displayName: "Type",
width: "auto",
@@ -99,13 +100,13 @@
})
}
- async function fetchBackups(filters, page, date) {
+ async function fetchBackups(filters, page, startDate, endDate) {
const response = await backups.searchBackups({
appId: $appStore.appId,
...filters,
page,
- startDate: date ? dayjs(date).startOf("day") : null,
- endDate: date ? dayjs(date).endOf("day") : null,
+ startDate: startDate ? dayjs(startDate).startOf("day") : null,
+ endDate: endDate ? dayjs(endDate).endOf("day") : null,
})
pageInfo.fetched(response.hasNextPage, response.nextPage)
@@ -165,7 +166,7 @@
}
onMount(async () => {
- await fetchBackups(filterOpt, page, date)
+ await fetchBackups(filterOpt, page, startDate, endDate)
loading = false
})
@@ -207,7 +208,7 @@
View plans
- {:else if !backupData?.length && !loading && !filterOpt && !date}
+ {:else if !backupData?.length && !loading && !filterOpt && !startDate}
@@ -237,12 +238,20 @@
/>
{
- date = e.detail
- }}
+ value={startDate}
+ label="From"
enableTime={false}
+ on:change={e => {
+ startDate = e.detail
+ }}
+ />
+ {
+ endDate = e.detail
+ }}
/>
diff --git a/packages/builder/src/pages/builder/portal/account/auditLogs/index.svelte b/packages/builder/src/pages/builder/portal/account/auditLogs/index.svelte
index 7f12c9ac36..70bcc9809c 100644
--- a/packages/builder/src/pages/builder/portal/account/auditLogs/index.svelte
+++ b/packages/builder/src/pages/builder/portal/account/auditLogs/index.svelte
@@ -27,6 +27,7 @@
import TimeRenderer from "./_components/TimeRenderer.svelte"
import AppColumnRenderer from "./_components/AppColumnRenderer.svelte"
import { cloneDeep } from "lodash"
+ import dayjs from "dayjs"
const schema = {
date: { width: "0.8fr" },
@@ -69,9 +70,8 @@
let sidePanelVisible = false
let wideSidePanel = false
let timer
- let startDate = new Date()
- startDate.setDate(startDate.getDate() - 30)
- let endDate = new Date()
+ let endDate = dayjs()
+ let startDate = endDate.subtract(30, "days")
$: fetchUsers(userPage, userSearchTerm)
$: fetchLogs({
@@ -155,8 +155,8 @@
logsPageInfo.loading()
await auditLogs.search({
bookmark: logsPage,
- startDate,
- endDate,
+ startDate: startDate ? dayjs(startDate).startOf("day") : null,
+ endDate: endDate ? dayjs(endDate).endOf("day") : null,
fullSearch: logSearchTerm,
userIds: selectedUsers,
appIds: selectedApps,
@@ -303,20 +303,19 @@
{
- if (e.detail[0]?.length === 1) {
- startDate = e.detail[0][0].toISOString()
- endDate = ""
- } else if (e.detail[0]?.length > 1) {
- startDate = e.detail[0][0].toISOString()
- endDate = e.detail[0][1].toISOString()
- } else {
- startDate = ""
- endDate = ""
- }
+ startDate = e.detail
+ }}
+ />
+ {
+ endDate = e.detail
}}
/>
@@ -488,7 +487,7 @@
flex-direction: row;
gap: var(--spacing-l);
flex-wrap: wrap;
- align-items: center;
+ align-items: flex-end;
}
.side-panel-icons {
@@ -505,6 +504,13 @@
.date-picker {
flex-basis: calc(70% - 32px);
min-width: 100px;
+ display: flex;
+ flex-direction: row;
+ gap: var(--spacing-l);
+ }
+ .date-picker :global(> *) {
+ flex: 1 1 auto;
+ width: 0;
}
.freeSearch {
diff --git a/packages/frontend-core/src/api/backups.js b/packages/frontend-core/src/api/backups.js
index 7663ae09af..40546b6f66 100644
--- a/packages/frontend-core/src/api/backups.js
+++ b/packages/frontend-core/src/api/backups.js
@@ -1,7 +1,4 @@
export const buildBackupsEndpoints = API => ({
- /**
- * Gets a list of users in the current tenant.
- */
searchBackups: async ({ appId, trigger, type, page, startDate, endDate }) => {
const opts = {}
if (page) {
From b1bd8993a65a22d6713f8e0d573348301c44b902 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Thu, 11 Apr 2024 08:57:34 +0100
Subject: [PATCH 032/338] Add date range picker and update backups and audit
logs to use it
---
.../components/common/DateRangePicker.svelte | 89 +++++++++++++++++++
.../settings/backups/index.svelte | 36 +++-----
.../portal/account/auditLogs/index.svelte | 40 +++------
3 files changed, 113 insertions(+), 52 deletions(-)
create mode 100644 packages/builder/src/components/common/DateRangePicker.svelte
diff --git a/packages/builder/src/components/common/DateRangePicker.svelte b/packages/builder/src/components/common/DateRangePicker.svelte
new file mode 100644
index 0000000000..d7323bbdb1
--- /dev/null
+++ b/packages/builder/src/components/common/DateRangePicker.svelte
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
diff --git a/packages/builder/src/pages/builder/app/[application]/settings/backups/index.svelte b/packages/builder/src/pages/builder/app/[application]/settings/backups/index.svelte
index 18d76480f4..39d61ffc31 100644
--- a/packages/builder/src/pages/builder/app/[application]/settings/backups/index.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/settings/backups/index.svelte
@@ -1,7 +1,6 @@
@@ -208,7 +206,7 @@
View plans
- {:else if !backupData?.length && !loading && !filterOpt && !startDate}
+ {:else if !backupData?.length && !loading && !filterOpt && !dateRange?.length}
@@ -237,21 +235,9 @@
bind:value={filterOpt}
/>
- {
- startDate = e.detail
- }}
- />
- {
- endDate = e.detail
- }}
+ (dateRange = e.detail)}
/>
diff --git a/packages/builder/src/pages/builder/portal/account/auditLogs/index.svelte b/packages/builder/src/pages/builder/portal/account/auditLogs/index.svelte
index 70bcc9809c..bb829b94e4 100644
--- a/packages/builder/src/pages/builder/portal/account/auditLogs/index.svelte
+++ b/packages/builder/src/pages/builder/portal/account/auditLogs/index.svelte
@@ -27,6 +27,7 @@
import TimeRenderer from "./_components/TimeRenderer.svelte"
import AppColumnRenderer from "./_components/AppColumnRenderer.svelte"
import { cloneDeep } from "lodash"
+ import DateRangePicker from "components/common/DateRangePicker.svelte"
import dayjs from "dayjs"
const schema = {
@@ -70,15 +71,13 @@
let sidePanelVisible = false
let wideSidePanel = false
let timer
- let endDate = dayjs()
- let startDate = endDate.subtract(30, "days")
+ let dateRange = [dayjs().subtract(30, "days"), dayjs()]
$: fetchUsers(userPage, userSearchTerm)
$: fetchLogs({
logsPage,
logSearchTerm,
- startDate,
- endDate,
+ dateRange,
selectedUsers,
selectedApps,
selectedEvents,
@@ -136,8 +135,7 @@
const fetchLogs = async ({
logsPage,
logSearchTerm,
- startDate,
- endDate,
+ dateRange,
selectedUsers,
selectedApps,
selectedEvents,
@@ -155,8 +153,8 @@
logsPageInfo.loading()
await auditLogs.search({
bookmark: logsPage,
- startDate: startDate ? dayjs(startDate).startOf("day") : null,
- endDate: endDate ? dayjs(endDate).endOf("day") : null,
+ startDate: dateRange[0],
+ endDate: dateRange[1],
fullSearch: logSearchTerm,
userIds: selectedUsers,
appIds: selectedApps,
@@ -214,8 +212,8 @@
const downloadLogs = async () => {
try {
window.location = auditLogs.getDownloadUrl({
- startDate,
- endDate,
+ startDate: dateRange[0],
+ endDate: dateRange[1],
fullSearch: logSearchTerm,
userIds: selectedUsers,
appIds: selectedApps,
@@ -302,21 +300,9 @@
- {
- startDate = e.detail
- }}
- />
- {
- endDate = e.detail
- }}
+ (dateRange = e.detail)}
/>
@@ -506,9 +492,9 @@
min-width: 100px;
display: flex;
flex-direction: row;
- gap: var(--spacing-l);
}
- .date-picker :global(> *) {
+ .date-picker :global(.date-range-picker),
+ .date-picker :global(.spectrum-Form-item) {
flex: 1 1 auto;
width: 0;
}
From 2c27ef82940d1253b84e9e116628d4c58b87a729 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Thu, 11 Apr 2024 11:20:30 +0100
Subject: [PATCH 033/338] Improve position dropdown and update click handlers
to ignore new datepicker
---
packages/bbui/src/Actions/click_outside.js | 1 +
packages/bbui/src/Actions/position_dropdown.js | 8 +++++---
packages/bbui/src/Form/Core/DatePicker/DateInput.svelte | 8 +++++---
packages/bbui/src/Form/Core/DatePicker/DatePicker.svelte | 5 ++++-
packages/bbui/src/Form/Core/Picker.svelte | 1 +
.../src/components/grid/overlays/KeyboardManager.svelte | 1 +
6 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/packages/bbui/src/Actions/click_outside.js b/packages/bbui/src/Actions/click_outside.js
index eafca657f3..36af1398b1 100644
--- a/packages/bbui/src/Actions/click_outside.js
+++ b/packages/bbui/src/Actions/click_outside.js
@@ -1,5 +1,6 @@
const ignoredClasses = [
".flatpickr-calendar",
+ ".spectrum-Calendar",
".spectrum-Popover",
".download-js-link",
]
diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js
index 35ff4933d7..4929a9beaf 100644
--- a/packages/bbui/src/Actions/position_dropdown.js
+++ b/packages/bbui/src/Actions/position_dropdown.js
@@ -41,6 +41,8 @@ export default function positionDropdown(element, opts) {
offset: opts.offset,
})
} else {
+ const renderHeight = maxHeight || elementBounds.height
+
// Determine vertical styles
const topSpace = anchorBounds.top
const bottomSpace = window.innerHeight - anchorBounds.bottom
@@ -52,11 +54,11 @@ export default function positionDropdown(element, opts) {
styles.top = window.innerHeight - elementBounds.height
}
} else if (
- window.innerHeight - anchorBounds.bottom < (maxHeight || 100) &&
+ window.innerHeight - anchorBounds.bottom < renderHeight + offset &&
topSpace - bottomSpace > 100
) {
- styles.top = anchorBounds.top - elementBounds.height - offset
- styles.maxHeight = maxHeight || 240
+ styles.top = anchorBounds.top - renderHeight - offset
+ styles.maxHeight = maxHeight
} else {
styles.top = anchorBounds.bottom + offset
styles.maxHeight =
diff --git a/packages/bbui/src/Form/Core/DatePicker/DateInput.svelte b/packages/bbui/src/Form/Core/DatePicker/DateInput.svelte
index 653574c3e7..d3bb04a30b 100644
--- a/packages/bbui/src/Form/Core/DatePicker/DateInput.svelte
+++ b/packages/bbui/src/Form/Core/DatePicker/DateInput.svelte
@@ -3,12 +3,13 @@
export let anchor
export let disabled
+ export let readonly
export let error
export let focused
export let placeholder
export let id
export let value
- export let icon = "Calendar"
+ export let icon
export let enableTime
export let timeOnly
@@ -32,11 +33,11 @@
{#if isOpen}
diff --git a/packages/bbui/src/Form/Core/Picker.svelte b/packages/bbui/src/Form/Core/Picker.svelte
index f6bffbbf10..eb7a5db655 100644
--- a/packages/bbui/src/Form/Core/Picker.svelte
+++ b/packages/bbui/src/Form/Core/Picker.svelte
@@ -155,6 +155,7 @@
useAnchorWidth={!autoWidth}
maxWidth={autoWidth ? 400 : null}
customHeight={customPopoverHeight}
+ maxHeight={240}
>
Date: Thu, 11 Apr 2024 11:23:50 +0100
Subject: [PATCH 034/338] Lint
---
packages/bbui/src/Form/Core/DatePicker/Calendar.svelte | 1 -
.../src/pages/builder/portal/account/auditLogs/index.svelte | 1 -
2 files changed, 2 deletions(-)
diff --git a/packages/bbui/src/Form/Core/DatePicker/Calendar.svelte b/packages/bbui/src/Form/Core/DatePicker/Calendar.svelte
index 75e8e193e6..b073ac78f9 100644
--- a/packages/bbui/src/Form/Core/DatePicker/Calendar.svelte
+++ b/packages/bbui/src/Form/Core/DatePicker/Calendar.svelte
@@ -72,7 +72,6 @@
From 68e679bb55683a7c3bac915874b3fde82b50e170 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Fri, 12 Apr 2024 14:26:41 +0100
Subject: [PATCH 037/338] Update click_outside to be more robust
---
packages/bbui/src/Actions/click_outside.js | 40 ++++++++++++----------
1 file changed, 21 insertions(+), 19 deletions(-)
diff --git a/packages/bbui/src/Actions/click_outside.js b/packages/bbui/src/Actions/click_outside.js
index 36af1398b1..76d015bee6 100644
--- a/packages/bbui/src/Actions/click_outside.js
+++ b/packages/bbui/src/Actions/click_outside.js
@@ -1,8 +1,13 @@
const ignoredClasses = [
- ".flatpickr-calendar",
- ".spectrum-Calendar",
- ".spectrum-Popover",
".download-js-link",
+ ".flatpickr-calendar",
+ ".spectrum-Menu",
+ ".date-time-popover",
+]
+const conditionallyIgnoredClasses = [
+ ".spectrum-Underlay",
+ ".drawer-wrapper",
+ ".spectrum-Popover",
]
let clickHandlers = []
@@ -22,26 +27,23 @@ const handleClick = event => {
// Process handlers
clickHandlers.forEach(handler => {
+ // Check that we're the right kind of click event
+ if (handler.allowedType && event.type !== handler.allowedType) {
+ return
+ }
+
+ // Check that the click isn't inside the target
if (handler.element.contains(event.target)) {
return
}
- // Ignore clicks for modals, unless the handler is registered from a modal
- const sourceInModal = handler.anchor.closest(".spectrum-Underlay") != null
- const clickInModal = event.target.closest(".spectrum-Underlay") != null
- if (clickInModal && !sourceInModal) {
- return
- }
-
- // Ignore clicks for drawers, unless the handler is registered from a drawer
- const sourceInDrawer = handler.anchor.closest(".drawer-wrapper") != null
- const clickInDrawer = event.target.closest(".drawer-wrapper") != null
- if (clickInDrawer && !sourceInDrawer) {
- return
- }
-
- if (handler.allowedType && event.type !== handler.allowedType) {
- return
+ // Ignore clicks for certain classes unless we're nested inside them
+ for (let className of conditionallyIgnoredClasses) {
+ const sourceInside = handler.anchor.closest(className) != null
+ const clickInside = event.target.closest(className) != null
+ if (clickInside && !sourceInside) {
+ return
+ }
}
handler.callback?.(event)
From f5ed62bd486f33dd0a55b04f82f28abd9ecf0173 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Fri, 12 Apr 2024 14:28:32 +0100
Subject: [PATCH 038/338] Remove placeholder from edit column modal to fix
crash when selecting placeholder
---
.../components/backend/DataTable/modals/CreateEditColumn.svelte | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index 92501bec3b..774478f7cc 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -519,6 +519,7 @@
/>
{/if}
Date: Mon, 15 Apr 2024 13:46:49 +0100
Subject: [PATCH 039/338] Revert "Revert "adds sidepanel open and close
actions, and gives the user the option to disable click-outside closure of
sidepanel""
---
packages/bbui/src/Layout/Page.svelte | 10 +++++-----
packages/client/manifest.json | 16 +++++++++++++++-
packages/client/src/components/app/Layout.svelte | 5 ++++-
.../client/src/components/app/SidePanel.svelte | 14 ++++++++++++++
packages/client/src/stores/sidePanel.js | 9 +++++++++
5 files changed, 47 insertions(+), 7 deletions(-)
diff --git a/packages/bbui/src/Layout/Page.svelte b/packages/bbui/src/Layout/Page.svelte
index 2169a12459..62dd9cc909 100644
--- a/packages/bbui/src/Layout/Page.svelte
+++ b/packages/bbui/src/Layout/Page.svelte
@@ -7,11 +7,11 @@
export let narrower = false
export let noPadding = false
- let sidePanelVisble = false
+ let sidePanelVisible = false
setContext("side-panel", {
- open: () => (sidePanelVisble = true),
- close: () => (sidePanelVisble = false),
+ open: () => (sidePanelVisible = true),
+ close: () => (sidePanelVisible = false),
})
@@ -24,9 +24,9 @@
{
- sidePanelVisble = false
+ sidePanelVisible = false
}}
>
diff --git a/packages/client/manifest.json b/packages/client/manifest.json
index 40abc7a9a0..c9e28e202b 100644
--- a/packages/client/manifest.json
+++ b/packages/client/manifest.json
@@ -6723,7 +6723,21 @@
"illegalChildren": ["section", "sidepanel"],
"showEmptyState": false,
"draggable": false,
- "info": "Side panels are hidden by default. They will only be revealed when triggered by the 'Open Side Panel' action."
+ "info": "Side panels are hidden by default. They will only be revealed when triggered by the 'Open Side Panel' action.",
+ "sendEvents": true,
+ "settings": [
+ {
+ "type": "boolean",
+ "key": "clickOutsideToClose",
+ "label": "Click outside to close",
+ "defaultValue": true
+ },
+ {
+ "type": "event",
+ "key": "onSidePanelClose",
+ "label": "On side panel close"
+ }
+ ]
},
"rowexplorer": {
"block": true,
diff --git a/packages/client/src/components/app/Layout.svelte b/packages/client/src/components/app/Layout.svelte
index 8508e943ff..bae2bd0faf 100644
--- a/packages/client/src/components/app/Layout.svelte
+++ b/packages/client/src/components/app/Layout.svelte
@@ -73,7 +73,10 @@
$context.device.width,
$context.device.height
)
- $: autoCloseSidePanel = !$builderStore.inBuilder && $sidePanelStore.open
+ $: autoCloseSidePanel =
+ !$builderStore.inBuilder &&
+ $sidePanelStore.open &&
+ $sidePanelStore.clickOutsideToClose
$: screenId = $builderStore.inBuilder
? `${$builderStore.screen?._id}-screen`
: "screen"
diff --git a/packages/client/src/components/app/SidePanel.svelte b/packages/client/src/components/app/SidePanel.svelte
index 825b401bb8..624617ad69 100644
--- a/packages/client/src/components/app/SidePanel.svelte
+++ b/packages/client/src/components/app/SidePanel.svelte
@@ -5,6 +5,9 @@
const { styleable, sidePanelStore, builderStore, dndIsDragging } =
getContext("sdk")
+ export let sidePanelClose
+ export let clickOutsideToClose
+
// Automatically show and hide the side panel when inside the builder.
// For some unknown reason, svelte reactivity breaks if we reference the
// reactive variable "open" inside the following expression, or if we define
@@ -26,6 +29,10 @@
}
}
+ $: {
+ sidePanelStore.actions.setSidepanelState(clickOutsideToClose)
+ }
+
// Derive visibility
$: open = $sidePanelStore.contentId === $component.id
@@ -40,6 +47,12 @@
}
}
+ const handleSidePanelClose = async () => {
+ if (sidePanelClose) {
+ await sidePanelClose()
+ }
+ }
+
const showInSidePanel = (el, visible) => {
const update = visible => {
const target = document.getElementById("side-panel-container")
@@ -51,6 +64,7 @@
} else {
if (target.contains(node)) {
target.removeChild(node)
+ handleSidePanelClose()
}
}
}
diff --git a/packages/client/src/stores/sidePanel.js b/packages/client/src/stores/sidePanel.js
index 3b3b9f5f4d..df66eca01c 100644
--- a/packages/client/src/stores/sidePanel.js
+++ b/packages/client/src/stores/sidePanel.js
@@ -3,6 +3,7 @@ import { writable, derived } from "svelte/store"
export const createSidePanelStore = () => {
const initialState = {
contentId: null,
+ clickOutsideToClose: true,
}
const store = writable(initialState)
const derivedStore = derived(store, $store => {
@@ -32,11 +33,19 @@ export const createSidePanelStore = () => {
}, 50)
}
+ const setSidepanelState = bool => {
+ clearTimeout(timeout)
+ store.update(state => {
+ state.clickOutsideToClose = bool
+ return state
+ })
+ }
return {
subscribe: derivedStore.subscribe,
actions: {
open,
close,
+ setSidepanelState,
},
}
}
From ea3cd0cd9b456af44579474e2df6393e134308dc Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Wed, 17 Apr 2024 11:27:27 +0100
Subject: [PATCH 040/338] Support reordering on mobile
---
.../src/components/grid/cells/HeaderCell.svelte | 14 +++++++++-----
.../src/components/grid/stores/reorder.js | 17 ++++++++++++++---
2 files changed, 23 insertions(+), 8 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
index 657f618759..f0ba29b499 100644
--- a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
@@ -18,7 +18,6 @@
export let column
export let idx
- export let orderable = true
const {
reorder,
@@ -66,6 +65,7 @@
$: resetSearchValue(column.name)
$: searching = searchValue != null
$: debouncedUpdateFilter(searchValue)
+ $: orderable = !column.primaryDisplay
const getSortingLabels = type => {
switch (type) {
@@ -112,7 +112,7 @@
}
const onMouseDown = e => {
- if (e.button === 0 && orderable) {
+ if ((e.touches?.length || e.button === 0) && orderable) {
timeout = setTimeout(() => {
reorder.actions.startReordering(column.name, e)
}, 200)
@@ -120,7 +120,7 @@
}
const onMouseUp = e => {
- if (e.button === 0 && orderable) {
+ if ((e.touches?.length || e.button === 0) && orderable) {
clearTimeout(timeout)
}
}
@@ -258,6 +258,9 @@
Use as display column
@@ -378,7 +382,7 @@
Move right
diff --git a/packages/frontend-core/src/components/grid/stores/reorder.js b/packages/frontend-core/src/components/grid/stores/reorder.js
index f820593174..7bf0423b23 100644
--- a/packages/frontend-core/src/components/grid/stores/reorder.js
+++ b/packages/frontend-core/src/components/grid/stores/reorder.js
@@ -40,6 +40,7 @@ export const createActions = context => {
// Callback when dragging on a colum header and starting reordering
const startReordering = (column, e) => {
+ console.log("start", column)
const $visibleColumns = get(visibleColumns)
const $bounds = get(bounds)
const $stickyColumn = get(stickyColumn)
@@ -55,6 +56,11 @@ export const createActions = context => {
x: 0,
column: $stickyColumn.name,
})
+ } else if (!$visibleColumns[0].primaryDisplay) {
+ breakpoints.unshift({
+ x: 0,
+ column: null,
+ })
}
// Update state
@@ -69,6 +75,9 @@ export const createActions = context => {
// Add listeners to handle mouse movement
document.addEventListener("mousemove", onReorderMouseMove)
document.addEventListener("mouseup", stopReordering)
+ document.addEventListener("touchmove", onReorderMouseMove)
+ document.addEventListener("touchend", stopReordering)
+ document.addEventListener("touchcancel", stopReordering)
// Trigger a move event immediately so ensure a candidate column is chosen
onReorderMouseMove(e)
@@ -77,7 +86,7 @@ export const createActions = context => {
// Callback when moving the mouse when reordering columns
const onReorderMouseMove = e => {
// Immediately handle the current position
- const x = e.clientX
+ const x = e.clientX ?? e.touches?.[0]?.clientX
reorder.update(state => ({
...state,
latestX: x,
@@ -168,6 +177,9 @@ export const createActions = context => {
// Remove event handlers
document.removeEventListener("mousemove", onReorderMouseMove)
document.removeEventListener("mouseup", stopReordering)
+ document.removeEventListener("touchmove", onReorderMouseMove)
+ document.removeEventListener("touchend", stopReordering)
+ document.removeEventListener("touchcancel", stopReordering)
// Save column changes
await columns.actions.saveChanges()
@@ -185,8 +197,7 @@ export const createActions = context => {
if (--targetIdx < sourceIdx) {
targetIdx++
}
- state.splice(targetIdx, 0, removed[0])
- return state.slice()
+ return state.toSpliced(targetIdx, 0, removed[0])
})
}
From f8087f0fb3cc78f9846040d0a78dfbcce07930a0 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Wed, 17 Apr 2024 11:45:54 +0100
Subject: [PATCH 041/338] Automatically unpin the grid sticky column when
available space is restricted
---
.../src/components/grid/cells/GridCell.svelte | 3 +
.../grid/controls/HideColumnsButton.svelte | 1 +
.../grid/overlays/ResizeOverlay.svelte | 1 +
.../src/components/grid/stores/columns.js | 64 +++++++++++++------
.../src/components/grid/stores/reorder.js | 19 +++---
.../src/components/grid/stores/ui.js | 2 +-
6 files changed, 59 insertions(+), 31 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/cells/GridCell.svelte b/packages/frontend-core/src/components/grid/cells/GridCell.svelte
index 74d98ec130..32a9dea83b 100644
--- a/packages/frontend-core/src/components/grid/cells/GridCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/GridCell.svelte
@@ -43,6 +43,9 @@
on:mouseup
on:click
on:contextmenu
+ on:touchstart
+ on:touchend
+ on:touchcancel
{style}
>
{#if error}
diff --git a/packages/frontend-core/src/components/grid/controls/HideColumnsButton.svelte b/packages/frontend-core/src/components/grid/controls/HideColumnsButton.svelte
index 01c9dc648b..4e19e64297 100644
--- a/packages/frontend-core/src/components/grid/controls/HideColumnsButton.svelte
+++ b/packages/frontend-core/src/components/grid/controls/HideColumnsButton.svelte
@@ -81,6 +81,7 @@
size="S"
value={column.visible}
on:change={e => toggleVisibility(column, e.detail)}
+ disabled={column.primaryDisplay}
/>
{/each}
diff --git a/packages/frontend-core/src/components/grid/overlays/ResizeOverlay.svelte b/packages/frontend-core/src/components/grid/overlays/ResizeOverlay.svelte
index 1140962583..e96517a434 100644
--- a/packages/frontend-core/src/components/grid/overlays/ResizeOverlay.svelte
+++ b/packages/frontend-core/src/components/grid/overlays/ResizeOverlay.svelte
@@ -21,6 +21,7 @@
class="resize-slider"
class:visible={activeColumn === $stickyColumn.name}
on:mousedown={e => resize.actions.startResizing($stickyColumn, e)}
+ on:touchstart={e => resize.actions.startResizing($stickyColumn, e)}
on:dblclick={() => resize.actions.resetSize($stickyColumn)}
style="left:{GutterWidth + $stickyColumn.width}px;"
>
diff --git a/packages/frontend-core/src/components/grid/stores/columns.js b/packages/frontend-core/src/components/grid/stores/columns.js
index 31638e7c79..8ceaae105f 100644
--- a/packages/frontend-core/src/components/grid/stores/columns.js
+++ b/packages/frontend-core/src/components/grid/stores/columns.js
@@ -48,22 +48,28 @@ export const createStores = () => {
export const deriveStores = context => {
const { columns, stickyColumn } = context
- // Derive if we have any normal columns
- const hasNonAutoColumn = derived(
+ // Quick access to all columns
+ const allColumns = derived(
[columns, stickyColumn],
([$columns, $stickyColumn]) => {
let allCols = $columns || []
if ($stickyColumn) {
allCols = [...allCols, $stickyColumn]
}
- const normalCols = allCols.filter(column => {
- return !column.schema?.autocolumn
- })
- return normalCols.length > 0
+ return allCols
}
)
+ // Derive if we have any normal columns
+ const hasNonAutoColumn = derived(allColumns, $allColumns => {
+ const normalCols = $allColumns.filter(column => {
+ return !column.schema?.autocolumn
+ })
+ return normalCols.length > 0
+ })
+
return {
+ allColumns,
hasNonAutoColumn,
}
}
@@ -142,24 +148,26 @@ export const createActions = context => {
}
export const initialise = context => {
- const { definition, columns, stickyColumn, enrichedSchema } = context
+ const {
+ definition,
+ columns,
+ stickyColumn,
+ allColumns,
+ enrichedSchema,
+ compact,
+ } = context
// Merge new schema fields with existing schema in order to preserve widths
- enrichedSchema.subscribe($enrichedSchema => {
+ const processColumns = $enrichedSchema => {
if (!$enrichedSchema) {
columns.set([])
stickyColumn.set(null)
return
}
const $definition = get(definition)
- const $columns = get(columns)
+ const $allColumns = get(allColumns)
const $stickyColumn = get(stickyColumn)
-
- // Generate array of all columns to easily find pre-existing columns
- let allColumns = $columns || []
- if ($stickyColumn) {
- allColumns.push($stickyColumn)
- }
+ const $compact = get(compact)
// Find primary display
let primaryDisplay
@@ -171,7 +179,7 @@ export const initialise = context => {
// Get field list
let fields = []
Object.keys($enrichedSchema).forEach(field => {
- if (field !== primaryDisplay) {
+ if ($compact || field !== primaryDisplay) {
fields.push(field)
}
})
@@ -181,7 +189,7 @@ export const initialise = context => {
fields
.map(field => {
const fieldSchema = $enrichedSchema[field]
- const oldColumn = allColumns?.find(x => x.name === field)
+ const oldColumn = $allColumns?.find(x => x.name === field)
return {
name: field,
label: fieldSchema.displayName || field,
@@ -189,9 +197,18 @@ export const initialise = context => {
width: fieldSchema.width || oldColumn?.width || DefaultColumnWidth,
visible: fieldSchema.visible ?? true,
order: fieldSchema.order ?? oldColumn?.order,
+ primaryDisplay: field === primaryDisplay,
}
})
.sort((a, b) => {
+ // If we don't have a pinned column then primary display will be in
+ // the normal columns list, and should be first
+ if (a.name === primaryDisplay) {
+ return -1
+ } else if (b.name === primaryDisplay) {
+ return 1
+ }
+
// Sort by order first
const orderA = a.order
const orderB = b.order
@@ -214,12 +231,12 @@ export const initialise = context => {
)
// Update sticky column
- if (!primaryDisplay) {
+ if ($compact || !primaryDisplay) {
stickyColumn.set(null)
return
}
const stickySchema = $enrichedSchema[primaryDisplay]
- const oldStickyColumn = allColumns?.find(x => x.name === primaryDisplay)
+ const oldStickyColumn = $allColumns?.find(x => x.name === primaryDisplay)
stickyColumn.set({
name: primaryDisplay,
label: stickySchema.displayName || primaryDisplay,
@@ -228,6 +245,13 @@ export const initialise = context => {
visible: true,
order: 0,
left: GutterWidth,
+ primaryDisplay: true,
})
- })
+ }
+
+ // Process columns when schema changes
+ enrichedSchema.subscribe(processColumns)
+
+ // Process columns when compact flag changes
+ compact.subscribe(() => processColumns(get(enrichedSchema)))
}
diff --git a/packages/frontend-core/src/components/grid/stores/reorder.js b/packages/frontend-core/src/components/grid/stores/reorder.js
index 7bf0423b23..30a5c144de 100644
--- a/packages/frontend-core/src/components/grid/stores/reorder.js
+++ b/packages/frontend-core/src/components/grid/stores/reorder.js
@@ -40,7 +40,6 @@ export const createActions = context => {
// Callback when dragging on a colum header and starting reordering
const startReordering = (column, e) => {
- console.log("start", column)
const $visibleColumns = get(visibleColumns)
const $bounds = get(bounds)
const $stickyColumn = get(stickyColumn)
@@ -167,13 +166,6 @@ export const createActions = context => {
// Ensure auto-scrolling is stopped
stopAutoScroll()
- // Swap position of columns
- let { sourceColumn, targetColumn } = get(reorder)
- moveColumn(sourceColumn, targetColumn)
-
- // Reset state
- reorder.set(reorderInitialState)
-
// Remove event handlers
document.removeEventListener("mousemove", onReorderMouseMove)
document.removeEventListener("mouseup", stopReordering)
@@ -181,8 +173,15 @@ export const createActions = context => {
document.removeEventListener("touchend", stopReordering)
document.removeEventListener("touchcancel", stopReordering)
- // Save column changes
- await columns.actions.saveChanges()
+ // Ensure there's actually a change
+ let { sourceColumn, targetColumn } = get(reorder)
+ if (sourceColumn !== targetColumn) {
+ moveColumn(sourceColumn, targetColumn)
+ await columns.actions.saveChanges()
+ }
+
+ // Reset state
+ reorder.set(reorderInitialState)
}
// Moves a column after another columns.
diff --git a/packages/frontend-core/src/components/grid/stores/ui.js b/packages/frontend-core/src/components/grid/stores/ui.js
index da0558bb5b..928f93f3e1 100644
--- a/packages/frontend-core/src/components/grid/stores/ui.js
+++ b/packages/frontend-core/src/components/grid/stores/ui.js
@@ -98,7 +98,7 @@ export const deriveStores = context => {
// Derive whether we should use the compact UI, depending on width
const compact = derived([stickyColumn, width], ([$stickyColumn, $width]) => {
- return ($stickyColumn?.width || 0) + $width + GutterWidth < 1100
+ return ($stickyColumn?.width || 0) + $width + GutterWidth < 800
})
return {
From c11139914fadeb898fde3f07fc769fc281facaea Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Wed, 17 Apr 2024 11:55:44 +0100
Subject: [PATCH 042/338] Add support for resizing columns on mobile
---
.../src/components/grid/lib/utils.js | 7 +++++++
.../grid/overlays/ResizeOverlay.svelte | 1 +
.../grid/overlays/ScrollOverlay.svelte | 16 +++++-----------
.../src/components/grid/stores/reorder.js | 3 ++-
.../src/components/grid/stores/resize.js | 14 ++++++++++++--
5 files changed, 27 insertions(+), 14 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/lib/utils.js b/packages/frontend-core/src/components/grid/lib/utils.js
index c7c618e6f8..02e6d66e0e 100644
--- a/packages/frontend-core/src/components/grid/lib/utils.js
+++ b/packages/frontend-core/src/components/grid/lib/utils.js
@@ -20,3 +20,10 @@ export const getColumnIcon = column => {
return result || "Text"
}
+
+export const parseEventLocation = e => {
+ return {
+ x: e.clientX ?? e.touches?.[0]?.clientX,
+ y: e.clientY ?? e.touches?.[0]?.clientY,
+ }
+}
diff --git a/packages/frontend-core/src/components/grid/overlays/ResizeOverlay.svelte b/packages/frontend-core/src/components/grid/overlays/ResizeOverlay.svelte
index e96517a434..e564108430 100644
--- a/packages/frontend-core/src/components/grid/overlays/ResizeOverlay.svelte
+++ b/packages/frontend-core/src/components/grid/overlays/ResizeOverlay.svelte
@@ -33,6 +33,7 @@
class="resize-slider"
class:visible={activeColumn === column.name}
on:mousedown={e => resize.actions.startResizing(column, e)}
+ on:touchstart={e => resize.actions.startResizing(column, e)}
on:dblclick={() => resize.actions.resetSize(column)}
style={getStyle(column, offset, $scrollLeft)}
>
diff --git a/packages/frontend-core/src/components/grid/overlays/ScrollOverlay.svelte b/packages/frontend-core/src/components/grid/overlays/ScrollOverlay.svelte
index 43a64f3fbd..e0ead3727c 100644
--- a/packages/frontend-core/src/components/grid/overlays/ScrollOverlay.svelte
+++ b/packages/frontend-core/src/components/grid/overlays/ScrollOverlay.svelte
@@ -2,6 +2,7 @@
import { getContext } from "svelte"
import { domDebounce } from "../../../utils/utils"
import { DefaultRowHeight, ScrollBarSize } from "../lib/constants"
+ import { parseEventLocation } from "../lib/utils"
const {
scroll,
@@ -53,17 +54,10 @@
}
}
- const getLocation = e => {
- return {
- y: e.touches?.[0]?.clientY ?? e.clientY,
- x: e.touches?.[0]?.clientX ?? e.clientX,
- }
- }
-
// V scrollbar drag handlers
const startVDragging = e => {
e.preventDefault()
- initialMouse = getLocation(e).y
+ initialMouse = parseEventLocation(e).y
initialScroll = $scrollTop
document.addEventListener("mousemove", moveVDragging)
document.addEventListener("touchmove", moveVDragging)
@@ -73,7 +67,7 @@
closeMenu()
}
const moveVDragging = domDebounce(e => {
- const delta = getLocation(e).y - initialMouse
+ const delta = parseEventLocation(e).y - initialMouse
const weight = delta / availHeight
const newScrollTop = initialScroll + weight * $maxScrollTop
scroll.update(state => ({
@@ -92,7 +86,7 @@
// H scrollbar drag handlers
const startHDragging = e => {
e.preventDefault()
- initialMouse = getLocation(e).x
+ initialMouse = parseEventLocation(e).x
initialScroll = $scrollLeft
document.addEventListener("mousemove", moveHDragging)
document.addEventListener("touchmove", moveHDragging)
@@ -102,7 +96,7 @@
closeMenu()
}
const moveHDragging = domDebounce(e => {
- const delta = getLocation(e).x - initialMouse
+ const delta = parseEventLocation(e).x - initialMouse
const weight = delta / availWidth
const newScrollLeft = initialScroll + weight * $maxScrollLeft
scroll.update(state => ({
diff --git a/packages/frontend-core/src/components/grid/stores/reorder.js b/packages/frontend-core/src/components/grid/stores/reorder.js
index 30a5c144de..84b297c461 100644
--- a/packages/frontend-core/src/components/grid/stores/reorder.js
+++ b/packages/frontend-core/src/components/grid/stores/reorder.js
@@ -1,4 +1,5 @@
import { get, writable, derived } from "svelte/store"
+import { parseEventLocation } from "../lib/utils"
const reorderInitialState = {
sourceColumn: null,
@@ -85,7 +86,7 @@ export const createActions = context => {
// Callback when moving the mouse when reordering columns
const onReorderMouseMove = e => {
// Immediately handle the current position
- const x = e.clientX ?? e.touches?.[0]?.clientX
+ const { x } = parseEventLocation(e)
reorder.update(state => ({
...state,
latestX: x,
diff --git a/packages/frontend-core/src/components/grid/stores/resize.js b/packages/frontend-core/src/components/grid/stores/resize.js
index 2dc9e0784c..87b3912848 100644
--- a/packages/frontend-core/src/components/grid/stores/resize.js
+++ b/packages/frontend-core/src/components/grid/stores/resize.js
@@ -1,5 +1,6 @@
import { writable, get, derived } from "svelte/store"
import { MinColumnWidth, DefaultColumnWidth } from "../lib/constants"
+import { parseEventLocation } from "../lib/utils"
const initialState = {
initialMouseX: null,
@@ -24,6 +25,8 @@ export const createActions = context => {
// Starts resizing a certain column
const startResizing = (column, e) => {
+ const { x } = parseEventLocation(e)
+
// Prevent propagation to stop reordering triggering
e.stopPropagation()
ui.actions.blur()
@@ -39,7 +42,7 @@ export const createActions = context => {
width: column.width,
left: column.left,
initialWidth: column.width,
- initialMouseX: e.clientX,
+ initialMouseX: x,
column: column.name,
columnIdx,
})
@@ -47,12 +50,16 @@ export const createActions = context => {
// Add mouse event listeners to handle resizing
document.addEventListener("mousemove", onResizeMouseMove)
document.addEventListener("mouseup", stopResizing)
+ document.addEventListener("touchmove", onResizeMouseMove)
+ document.addEventListener("touchend", stopResizing)
+ document.addEventListener("touchcancel", stopResizing)
}
// Handler for moving the mouse to resize columns
const onResizeMouseMove = e => {
const { initialMouseX, initialWidth, width, columnIdx } = get(resize)
- const dx = e.clientX - initialMouseX
+ const { x } = parseEventLocation(e)
+ const dx = x - initialMouseX
const newWidth = Math.round(Math.max(MinColumnWidth, initialWidth + dx))
// Ignore small changes
@@ -87,6 +94,9 @@ export const createActions = context => {
resize.set(initialState)
document.removeEventListener("mousemove", onResizeMouseMove)
document.removeEventListener("mouseup", stopResizing)
+ document.removeEventListener("touchmove", onResizeMouseMove)
+ document.removeEventListener("touchend", stopResizing)
+ document.removeEventListener("touchcancel", stopResizing)
// Persist width if it changed
if ($resize.width !== $resize.initialWidth) {
From 27f7b1cc15b90ce8e1e03dce291708e1957a2aea Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Wed, 17 Apr 2024 14:32:57 +0100
Subject: [PATCH 043/338] Add multiple improvements for touch events to grids
---
.../src/components/grid/cells/HeaderCell.svelte | 5 +++--
packages/frontend-core/src/components/grid/stores/reorder.js | 3 ++-
packages/frontend-core/src/components/grid/stores/resize.js | 1 +
3 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
index f0ba29b499..8a10556da9 100644
--- a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
@@ -119,9 +119,10 @@
}
}
- const onMouseUp = e => {
- if ((e.touches?.length || e.button === 0) && orderable) {
+ const onMouseUp = () => {
+ if (timeout) {
clearTimeout(timeout)
+ timeout = null
}
}
diff --git a/packages/frontend-core/src/components/grid/stores/reorder.js b/packages/frontend-core/src/components/grid/stores/reorder.js
index 84b297c461..c068f82cba 100644
--- a/packages/frontend-core/src/components/grid/stores/reorder.js
+++ b/packages/frontend-core/src/components/grid/stores/reorder.js
@@ -34,6 +34,7 @@ export const createActions = context => {
stickyColumn,
ui,
maxScrollLeft,
+ width,
} = context
let autoScrollInterval
@@ -95,7 +96,7 @@ export const createActions = context => {
// Check if we need to start auto-scrolling
const $reorder = get(reorder)
- const proximityCutoff = 140
+ const proximityCutoff = Math.min(140, get(width) / 6)
const speedFactor = 8
const rightProximity = Math.max(0, $reorder.gridLeft + $reorder.width - x)
const leftProximity = Math.max(0, x - $reorder.gridLeft)
diff --git a/packages/frontend-core/src/components/grid/stores/resize.js b/packages/frontend-core/src/components/grid/stores/resize.js
index 87b3912848..157465e838 100644
--- a/packages/frontend-core/src/components/grid/stores/resize.js
+++ b/packages/frontend-core/src/components/grid/stores/resize.js
@@ -29,6 +29,7 @@ export const createActions = context => {
// Prevent propagation to stop reordering triggering
e.stopPropagation()
+ e.preventDefault()
ui.actions.blur()
// Find and cache index
From 58b23a736fc080c997bba37fedb0e6365f060179 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Wed, 17 Apr 2024 14:44:52 +0100
Subject: [PATCH 044/338] Remove add new row tooltip when caused by inline
filters
---
.../frontend-core/src/components/grid/layout/NewRow.svelte | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/packages/frontend-core/src/components/grid/layout/NewRow.svelte b/packages/frontend-core/src/components/grid/layout/NewRow.svelte
index 66c42e5303..f5de870f7e 100644
--- a/packages/frontend-core/src/components/grid/layout/NewRow.svelte
+++ b/packages/frontend-core/src/components/grid/layout/NewRow.svelte
@@ -30,6 +30,7 @@
refreshing,
config,
filter,
+ inlineFilters,
columnRenderMap,
} = getContext("grid")
@@ -157,7 +158,11 @@
{#if !visible && !selectedRowCount && $config.canAddRows}
From 0f7e576f19981c3f93b3e856f9953b3849c8478d Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Wed, 17 Apr 2024 14:52:23 +0100
Subject: [PATCH 045/338] Revert unnecessary changes
---
.../src/components/grid/stores/columns.js | 18 ++++++------------
1 file changed, 6 insertions(+), 12 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/stores/columns.js b/packages/frontend-core/src/components/grid/stores/columns.js
index 8ceaae105f..551eeb364f 100644
--- a/packages/frontend-core/src/components/grid/stores/columns.js
+++ b/packages/frontend-core/src/components/grid/stores/columns.js
@@ -48,28 +48,22 @@ export const createStores = () => {
export const deriveStores = context => {
const { columns, stickyColumn } = context
- // Quick access to all columns
- const allColumns = derived(
+ // Derive if we have any normal columns
+ const hasNonAutoColumn = derived(
[columns, stickyColumn],
([$columns, $stickyColumn]) => {
let allCols = $columns || []
if ($stickyColumn) {
allCols = [...allCols, $stickyColumn]
}
- return allCols
+ const normalCols = allCols.filter(column => {
+ return !column.schema?.autocolumn
+ })
+ return normalCols.length > 0
}
)
- // Derive if we have any normal columns
- const hasNonAutoColumn = derived(allColumns, $allColumns => {
- const normalCols = $allColumns.filter(column => {
- return !column.schema?.autocolumn
- })
- return normalCols.length > 0
- })
-
return {
- allColumns,
hasNonAutoColumn,
}
}
From 5df34310d4f2018569be040beb9f5f29359d07d2 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Wed, 17 Apr 2024 14:53:27 +0100
Subject: [PATCH 046/338] Redo changes
---
.../src/components/grid/stores/columns.js | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/stores/columns.js b/packages/frontend-core/src/components/grid/stores/columns.js
index 551eeb364f..8ceaae105f 100644
--- a/packages/frontend-core/src/components/grid/stores/columns.js
+++ b/packages/frontend-core/src/components/grid/stores/columns.js
@@ -48,22 +48,28 @@ export const createStores = () => {
export const deriveStores = context => {
const { columns, stickyColumn } = context
- // Derive if we have any normal columns
- const hasNonAutoColumn = derived(
+ // Quick access to all columns
+ const allColumns = derived(
[columns, stickyColumn],
([$columns, $stickyColumn]) => {
let allCols = $columns || []
if ($stickyColumn) {
allCols = [...allCols, $stickyColumn]
}
- const normalCols = allCols.filter(column => {
- return !column.schema?.autocolumn
- })
- return normalCols.length > 0
+ return allCols
}
)
+ // Derive if we have any normal columns
+ const hasNonAutoColumn = derived(allColumns, $allColumns => {
+ const normalCols = $allColumns.filter(column => {
+ return !column.schema?.autocolumn
+ })
+ return normalCols.length > 0
+ })
+
return {
+ allColumns,
hasNonAutoColumn,
}
}
From ad10679115b22f07e055a21e96d4ff2ee4aa0a75 Mon Sep 17 00:00:00 2001
From: mikesealey
Date: Wed, 17 Apr 2024 16:27:23 +0100
Subject: [PATCH 047/338] saving progress based on review reccomendations
---
packages/client/manifest.json | 11 +++++------
packages/client/src/components/app/Layout.svelte | 2 +-
packages/client/src/components/app/SidePanel.svelte | 4 ++--
packages/client/src/stores/sidePanel.js | 9 ++++-----
4 files changed, 12 insertions(+), 14 deletions(-)
diff --git a/packages/client/manifest.json b/packages/client/manifest.json
index c9e28e202b..cb85768adc 100644
--- a/packages/client/manifest.json
+++ b/packages/client/manifest.json
@@ -6724,18 +6724,17 @@
"showEmptyState": false,
"draggable": false,
"info": "Side panels are hidden by default. They will only be revealed when triggered by the 'Open Side Panel' action.",
- "sendEvents": true,
"settings": [
{
"type": "boolean",
- "key": "clickOutsideToClose",
- "label": "Click outside to close",
- "defaultValue": true
+ "key": "ignoreClicksOutside",
+ "label": "Ignore clicks outside",
+ "defaultValue": false
},
{
"type": "event",
- "key": "onSidePanelClose",
- "label": "On side panel close"
+ "key": "onClose",
+ "label": "On close"
}
]
},
diff --git a/packages/client/src/components/app/Layout.svelte b/packages/client/src/components/app/Layout.svelte
index bae2bd0faf..617658c754 100644
--- a/packages/client/src/components/app/Layout.svelte
+++ b/packages/client/src/components/app/Layout.svelte
@@ -76,7 +76,7 @@
$: autoCloseSidePanel =
!$builderStore.inBuilder &&
$sidePanelStore.open &&
- $sidePanelStore.clickOutsideToClose
+ $sidePanelStore.ignoreClicksOutside
$: screenId = $builderStore.inBuilder
? `${$builderStore.screen?._id}-screen`
: "screen"
diff --git a/packages/client/src/components/app/SidePanel.svelte b/packages/client/src/components/app/SidePanel.svelte
index 624617ad69..8b0b395649 100644
--- a/packages/client/src/components/app/SidePanel.svelte
+++ b/packages/client/src/components/app/SidePanel.svelte
@@ -6,7 +6,7 @@
getContext("sdk")
export let sidePanelClose
- export let clickOutsideToClose
+ export let ignoreClicksOutside
// Automatically show and hide the side panel when inside the builder.
// For some unknown reason, svelte reactivity breaks if we reference the
@@ -30,7 +30,7 @@
}
$: {
- sidePanelStore.actions.setSidepanelState(clickOutsideToClose)
+ sidePanelStore.actions.setIgnoreClicksOutside(ignoreClicksOutside)
}
// Derive visibility
diff --git a/packages/client/src/stores/sidePanel.js b/packages/client/src/stores/sidePanel.js
index df66eca01c..b25914c484 100644
--- a/packages/client/src/stores/sidePanel.js
+++ b/packages/client/src/stores/sidePanel.js
@@ -3,7 +3,7 @@ import { writable, derived } from "svelte/store"
export const createSidePanelStore = () => {
const initialState = {
contentId: null,
- clickOutsideToClose: true,
+ ignoreClicksOutside: true,
}
const store = writable(initialState)
const derivedStore = derived(store, $store => {
@@ -33,10 +33,9 @@ export const createSidePanelStore = () => {
}, 50)
}
- const setSidepanelState = bool => {
- clearTimeout(timeout)
+ const setIgnoreClicksOutside = bool => {
store.update(state => {
- state.clickOutsideToClose = bool
+ state.ignoreClicksOutside = bool
return state
})
}
@@ -45,7 +44,7 @@ export const createSidePanelStore = () => {
actions: {
open,
close,
- setSidepanelState,
+ setIgnoreClicksOutside,
},
}
}
From 6c38d32549a79f04ea00f10e21c84f2c540796ff Mon Sep 17 00:00:00 2001
From: mikesealey
Date: Thu, 18 Apr 2024 10:08:48 +0100
Subject: [PATCH 048/338] reinstates actions running when sidepanel closes
---
packages/client/src/components/app/SidePanel.svelte | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/client/src/components/app/SidePanel.svelte b/packages/client/src/components/app/SidePanel.svelte
index 8b0b395649..827ed2ab95 100644
--- a/packages/client/src/components/app/SidePanel.svelte
+++ b/packages/client/src/components/app/SidePanel.svelte
@@ -5,7 +5,7 @@
const { styleable, sidePanelStore, builderStore, dndIsDragging } =
getContext("sdk")
- export let sidePanelClose
+ export let onClose
export let ignoreClicksOutside
// Automatically show and hide the side panel when inside the builder.
@@ -48,8 +48,8 @@
}
const handleSidePanelClose = async () => {
- if (sidePanelClose) {
- await sidePanelClose()
+ if (onClose) {
+ await onClose()
}
}
From 088c210de85e260c946d2b105687cd155c16d298 Mon Sep 17 00:00:00 2001
From: mikesealey
Date: Thu, 18 Apr 2024 12:11:45 +0100
Subject: [PATCH 049/338] sets ignoreClickOutside to each side panel
---
packages/client/src/components/app/Layout.svelte | 2 +-
packages/client/src/components/app/SidePanel.svelte | 7 ++++---
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/packages/client/src/components/app/Layout.svelte b/packages/client/src/components/app/Layout.svelte
index 617658c754..bfbac8f4f1 100644
--- a/packages/client/src/components/app/Layout.svelte
+++ b/packages/client/src/components/app/Layout.svelte
@@ -76,7 +76,7 @@
$: autoCloseSidePanel =
!$builderStore.inBuilder &&
$sidePanelStore.open &&
- $sidePanelStore.ignoreClicksOutside
+ !$sidePanelStore.ignoreClicksOutside
$: screenId = $builderStore.inBuilder
? `${$builderStore.screen?._id}-screen`
: "screen"
diff --git a/packages/client/src/components/app/SidePanel.svelte b/packages/client/src/components/app/SidePanel.svelte
index 827ed2ab95..bff5a78837 100644
--- a/packages/client/src/components/app/SidePanel.svelte
+++ b/packages/client/src/components/app/SidePanel.svelte
@@ -29,9 +29,9 @@
}
}
- $: {
- sidePanelStore.actions.setIgnoreClicksOutside(ignoreClicksOutside)
- }
+ // $: {
+
+ // }
// Derive visibility
$: open = $sidePanelStore.contentId === $component.id
@@ -43,6 +43,7 @@
let renderKey = null
$: {
if (open) {
+ sidePanelStore.actions.setIgnoreClicksOutside(ignoreClicksOutside)
renderKey = Math.random()
}
}
From 6bbdf0e4744e2a42283eeeb36d2c4cd0e9d0f2bc Mon Sep 17 00:00:00 2001
From: Dean
Date: Thu, 18 Apr 2024 17:04:26 +0100
Subject: [PATCH 050/338] Bindings support for views and table row searches
---
.../buttons/TableFilterButton.svelte | 71 ++++++++++++-------
.../controls/FilterEditor/FilterDrawer.svelte | 1 +
.../controls/FilterEditor/FilterUsers.svelte | 21 +++---
.../server/src/api/controllers/row/index.ts | 15 +++-
.../src/api/controllers/row/utils/utils.ts | 41 ++++++++++-
.../server/src/api/controllers/row/views.ts | 14 +++-
6 files changed, 122 insertions(+), 41 deletions(-)
diff --git a/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte
index 91456da655..26b6624160 100644
--- a/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte
+++ b/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte
@@ -1,7 +1,9 @@
-
+
{text}
-
- dispatch("change", tempValue)}
- >
-
- (tempValue = e.detail)}
- />
-
-
-
-
+
+ {
+ dispatch("change", tempValue)
+ drawer.hide()
+ }}
+ >
+ Save
+
+ (tempValue = e.detail)}
+ {bindings}
+ />
+
diff --git a/packages/builder/src/components/design/settings/controls/FilterEditor/FilterDrawer.svelte b/packages/builder/src/components/design/settings/controls/FilterEditor/FilterDrawer.svelte
index 7f1ee8010d..74c081cd5b 100644
--- a/packages/builder/src/components/design/settings/controls/FilterEditor/FilterDrawer.svelte
+++ b/packages/builder/src/components/design/settings/controls/FilterEditor/FilterDrawer.svelte
@@ -304,6 +304,7 @@
OperatorOptions.ContainsAny.value,
].includes(filter.operator)}
disabled={filter.noValue}
+ type={filter.valueType}
/>
{:else}
diff --git a/packages/builder/src/components/design/settings/controls/FilterEditor/FilterUsers.svelte b/packages/builder/src/components/design/settings/controls/FilterEditor/FilterUsers.svelte
index 88383ba170..4613b8c40f 100644
--- a/packages/builder/src/components/design/settings/controls/FilterEditor/FilterUsers.svelte
+++ b/packages/builder/src/components/design/settings/controls/FilterEditor/FilterUsers.svelte
@@ -1,7 +1,6 @@
- option.email}
- getOptionValue={option => option._id}
- {disabled}
-/>
+
+ option.email}
+ getOptionValue={option => option._id}
+ {disabled}
+ />
+
diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts
index c3d1f2cb47..7f99105ea4 100644
--- a/packages/server/src/api/controllers/row/index.ts
+++ b/packages/server/src/api/controllers/row/index.ts
@@ -2,7 +2,7 @@ import stream from "stream"
import archiver from "archiver"
import { quotas } from "@budibase/pro"
-import { objectStore } from "@budibase/backend-core"
+import { objectStore, context } from "@budibase/backend-core"
import * as internal from "./internal"
import * as external from "./external"
import { isExternalTableID } from "../../../integrations/utils"
@@ -198,8 +198,21 @@ export async function destroy(ctx: UserCtx) {
export async function search(ctx: Ctx) {
const tableId = utils.getTableId(ctx)
+ // Current user context for bindable search
+ const { _id, _rev, firstName, lastName, email, status, roleId } = ctx.user
+
+ await context.ensureSnippetContext()
+
+ const enrichedQuery = await utils.enrichSearchContext(
+ { ...ctx.request.body.query },
+ {
+ user: { _id, _rev, firstName, lastName, email, status, roleId },
+ }
+ )
+
const searchParams: RowSearchParams = {
...ctx.request.body,
+ query: enrichedQuery,
tableId,
}
diff --git a/packages/server/src/api/controllers/row/utils/utils.ts b/packages/server/src/api/controllers/row/utils/utils.ts
index f387a468cf..503f139783 100644
--- a/packages/server/src/api/controllers/row/utils/utils.ts
+++ b/packages/server/src/api/controllers/row/utils/utils.ts
@@ -7,6 +7,8 @@ import {
FieldType,
RelationshipsJson,
Row,
+ SearchRowRequest,
+ SearchRowResponse,
Table,
UserCtx,
} from "@budibase/types"
@@ -22,7 +24,7 @@ import {
getInternalRowId,
} from "./basic"
import sdk from "../../../../sdk"
-
+import { processStringSync } from "@budibase/string-templates"
import validateJs from "validate.js"
validateJs.extend(validateJs.validators.datetime, {
@@ -187,3 +189,40 @@ export async function sqlOutputProcessing(
export function isUserMetadataTable(tableId: string) {
return tableId === InternalTables.USER_METADATA
}
+
+export async function enrichSearchContext(
+ fields: Record,
+ inputs = {},
+ helpers = true
+): Promise> {
+ const enrichedQuery: Record = {}
+ if (!fields || !inputs) {
+ return enrichedQuery
+ }
+ const parameters = { ...inputs }
+ // enrich the fields with dynamic parameters
+ for (let key of Object.keys(fields)) {
+ if (fields[key] == null) {
+ continue
+ }
+ if (typeof fields[key] === "object") {
+ // enrich nested fields object
+ enrichedQuery[key] = await enrichSearchContext(
+ fields[key],
+ parameters,
+ helpers
+ )
+ } else if (typeof fields[key] === "string") {
+ // enrich string value as normal
+ enrichedQuery[key] = processStringSync(fields[key], parameters, {
+ noEscaping: true,
+ noHelpers: !helpers,
+ escapeNewlines: true,
+ })
+ } else {
+ enrichedQuery[key] = fields[key]
+ }
+ }
+
+ return enrichedQuery
+}
diff --git a/packages/server/src/api/controllers/row/views.ts b/packages/server/src/api/controllers/row/views.ts
index 2644446d82..18953ebe88 100644
--- a/packages/server/src/api/controllers/row/views.ts
+++ b/packages/server/src/api/controllers/row/views.ts
@@ -9,7 +9,8 @@ import {
} from "@budibase/types"
import { dataFilters } from "@budibase/shared-core"
import sdk from "../../../sdk"
-import { db } from "@budibase/backend-core"
+import { db, context } from "@budibase/backend-core"
+import { enrichSearchContext, userSearchFromContext } from "./utils"
export async function searchView(
ctx: UserCtx
@@ -56,10 +57,19 @@ export async function searchView(
})
}
+ // Current user search context.
+ const { _id, _rev, firstName, lastName, email, status, roleId } = ctx.user
+
+ await context.ensureSnippetContext()
+
+ const enrichedQuery = await enrichSearchContext(query, {
+ user: { _id, _rev, firstName, lastName, email, status, roleId },
+ })
+
const searchOptions: RequiredKeys &
RequiredKeys> = {
tableId: view.tableId,
- query,
+ query: enrichedQuery,
fields: viewFields,
...getSortOptions(body, view),
limit: body.limit,
From 66381ded2f6aa7cd8667043c409c69d6aa79248d Mon Sep 17 00:00:00 2001
From: theodelaporte
Date: Fri, 19 Apr 2024 10:37:51 +0200
Subject: [PATCH 051/338] :memo: Add portuguese version of readme
---
i18n/README.por.md | 88 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 88 insertions(+)
create mode 100644 i18n/README.por.md
diff --git a/i18n/README.por.md b/i18n/README.por.md
new file mode 100644
index 0000000000..b23988cac5
--- /dev/null
+++ b/i18n/README.por.md
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+ Budibase
+
+
+
+ A plataforma low-code que você vai adorar usar
+
+
+ Budibase é uma plataforma low-code open source e é a maneira mais fácil de criar ferramentas internas que aumentam a produtividade.
+
+
+
+ 🤖 🎨 🚀
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## ✨ Funcionalidades
+
+### Construa e implante um verdadeiro software
+Ao contrário de outras plataformas, com Budibase você constrói e implanta aplicativos de página única. Os aplicativos Budibase são altamente performáticos e podem ser designados de forma responsiva, proporcionando assim aos seus usuários uma experiência excepcional.
+
+
+### Fonte livre e extensível
+Budibase é software livre - sob licença GPL v3. Isso deve lhe dar tranquilidade de que Budibase estará sempre por aqui. Você também pode codificar no Budibase ou bifurcá-lo e fazer alterações como quiser, tornando-o uma experiência amigável para desenvolvedores.
+
+
+### Importar dados ou começar do zero
+Budibase pode extrair dados de várias fontes, incluindo MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB ou uma API REST. E ao contrário de outras plataformas, com Budibase, você pode começar do zero e criar aplicativos de negócios sem nenhuma fonte de dados. [Solicitar uma nova fonte de dados](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
+
+
+
+
+
+
+### Design e criação de aplicativos usando componentes pré-definidos.
+
+Budibase vem com componentes belamente projetados e poderosos que você pode usar como blocos de construção para construir sua interface do usuário. Também expomos muitas de suas opções de estilo CSS favoritas para que você possa ser mais criativo. [Solicitar um novo componente](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
+
+
+
+
+
+
+### Automatize processos, integre outras ferramentas e se conecte a webhooks
+Economize tempo automatizando processos manuais e fluxos de trabalho. Seja conectando-se a webhooks ou automatizando e-mails, basta dizer ao Budibase o que fazer e deixá-lo trabalhar para você. Você pode facilmente [criar uma nova automação para o Budibase aqui](https://github.com/Budibase/automations) ou [Solicitar uma nova automação](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
+
+
+
+
From a56fd679112f051cdd01ea275848ef575c963233 Mon Sep 17 00:00:00 2001
From: theodelaporte
Date: Fri, 19 Apr 2024 10:38:52 +0200
Subject: [PATCH 052/338] :memo: add italian version of readme
---
i18n/README.it.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 74 insertions(+)
create mode 100644 i18n/README.it.md
diff --git a/i18n/README.it.md b/i18n/README.it.md
new file mode 100644
index 0000000000..b9cca5a61d
--- /dev/null
+++ b/i18n/README.it.md
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+ Budibase
+
+
+
+ La piattaforma low-code che amerai usare
+
+
+ Budibase è una piattaforma low-code open source ed è il modo più facile per creare strumenti interni che aumentano la produttività.
+
+
+
+ 🤖 🎨 🚀
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## ✨ Funzionalità
+
+### Costruisci e distribuisci un vero software
+A differenza di altre piattaforme, con Budibase si costruiscono e distribuiscono applicazioni single-page. Le applicazioni Budibase sono altamente performanti e possono essere progettate in modo responsivo, offrendo agli utenti un'esperienza eccezionale.
+
+
+### Sorgente libero ed estensibile
+Budibase è software libero - sotto licenza GPL v3. Questo dovrebbe darti la tranquillità che Budibase sarà sempre qui. Puoi anche codificare in Budibase o fare un fork e apportare modifiche come desideri, rendendolo un'esperienza amichevole per gli sviluppatori.
+
+
+### Importare dati o partire da zero
+Budibase può estrarre dati da varie fonti, tra cui MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB o un'API REST. E a differenza di altre piattaforme, con Budibase puoi partire da zero e creare applicazioni di business senza alcuna fonte dati. [Richiedi una nuova fonte di dati](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
+
+
+
+
+
+
+### Progettare e creare applicazioni utilizzando component
From 7c9c3b719675e1eb6cac64a804b0e71b9ba23e04 Mon Sep 17 00:00:00 2001
From: theodelaporte
Date: Fri, 19 Apr 2024 10:39:44 +0200
Subject: [PATCH 053/338] :memo: add russian version of readme
---
i18n/README.ru.md | 83 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 83 insertions(+)
create mode 100644 i18n/README.ru.md
diff --git a/i18n/README.ru.md b/i18n/README.ru.md
new file mode 100644
index 0000000000..61e5a00550
--- /dev/null
+++ b/i18n/README.ru.md
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+ Budibase
+
+
+
+ Платформа low-code, которую вы будете любить использовать
+
+
+ Budibase - это open source платформа low-code, которая является самым простым способом создания внутренних инструментов, повышающих производительность.
+
+
+
+ 🤖 🎨 🚀
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## ✨ Функциональности
+
+### Создание и развертывание настоящего программного обеспечения
+В отличие от других платформ, с Budibase вы создаете и развертываете одностраничные приложения. Приложения Budibase обладают высокой производительностью и могут быть разработаны с адаптивным дизайном, обеспечивая пользователям исключительный опыт.
+
+
+### Свободный и расширяемый исходный код
+Budibase - это свободное программное обеспечение под лицензией GPL v3. Это должно дать вам уверенность в том, что Budibase всегда будет доступен. Вы также можете кодировать в Budibase или создать его форк и вносить изменения по своему усмотрению, что делает его дружественным для разработчиков.
+
+
+### Импорт данных или начало с нуля
+Budibase может извлекать данные из различных источников, включая MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB или REST API. И в отличие от других платформ, с Budibase вы можете начать с нуля и создавать деловые приложения без каких-либо источников данных. [Запросить новый источник данных](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
+
+
+
+
+
+
+### Проектирование и создание приложений с помощью предварительно определенных компонентов
+Budibase поставляется с красиво спроектированными и мощными компонентами, которые можно использовать как строительные блоки для создания вашего пользовательского интерфейса. Мы также предоставляем множество ваших любимых опций стилей CSS, чтобы вы могли проявить свою творческую мысль. [Запросить новый компонент](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
+
+
+
+
+
+
+### Автоматизация процессов, интеграция с другими инструментами и подключение веб-хуков
+Экономьте время, автоматизируя ручные процессы и рабочие пот
From 03d3151a65f20b68316fc76a5de1563f199b99d1 Mon Sep 17 00:00:00 2001
From: theodelaporte
Date: Fri, 19 Apr 2024 10:41:54 +0200
Subject: [PATCH 054/338] :memo: match readme to each other
---
i18n/README.it.md | 169 +++++++++++++++++++++++++++++++++++++++++----
i18n/README.por.md | 161 +++++++++++++++++++++++++++++++++++++-----
i18n/README.ru.md | 160 +++++++++++++++++++++++++++++++++++++-----
3 files changed, 438 insertions(+), 52 deletions(-)
diff --git a/i18n/README.it.md b/i18n/README.it.md
index b9cca5a61d..8972fcdb18 100644
--- a/i18n/README.it.md
+++ b/i18n/README.it.md
@@ -8,10 +8,10 @@
- La piattaforma low-code che amerai usare
+ La piattaforma low-code che amerai utilizzare
- Budibase è una piattaforma low-code open source ed è il modo più facile per creare strumenti interni che aumentano la produttività.
+ Budibase è una piattaforma low-code open source ed è il modo più semplice per creare strumenti interni che migliorano la produttività.
@@ -25,10 +25,10 @@
-
+
-
+
@@ -40,35 +40,174 @@
-
## ✨ Funzionalità
-### Costruisci e distribuisci un vero software
-A differenza di altre piattaforme, con Budibase si costruiscono e distribuiscono applicazioni single-page. Le applicazioni Budibase sono altamente performanti e possono essere progettate in modo responsivo, offrendo agli utenti un'esperienza eccezionale.
+### Costruisci e distribuisci software reale
+A differenza di altre piattaforme, con Budibase puoi costruire e distribuire applicazioni one-page. Le applicazioni Budibase sono altamente performanti e possono essere progettate in modo responsive, offrendo ai tuoi utenti un'esperienza eccezionale.
-### Sorgente libero ed estensibile
-Budibase è software libero - sotto licenza GPL v3. Questo dovrebbe darti la tranquillità che Budibase sarà sempre qui. Puoi anche codificare in Budibase o fare un fork e apportare modifiche come desideri, rendendolo un'esperienza amichevole per gli sviluppatori.
+### Sorgente aperto ed estensibile
+Budibase è software open source - sotto licenza GPL v3. Questo dovrebbe rassicurarti sul fatto che Budibase sarà sempre lì. Puoi anche codificare in Budibase o fare fork e apportare modifiche a tuo piacimento, rendendolo un'esperienza amichevole per gli sviluppatori.
-### Importare dati o partire da zero
-Budibase può estrarre dati da varie fonti, tra cui MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB o un'API REST. E a differenza di altre piattaforme, con Budibase puoi partire da zero e creare applicazioni di business senza alcuna fonte dati. [Richiedi una nuova fonte di dati](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
+### Importa dati o inizia da zero
+Budibase può estrarre i suoi dati da diverse fonti, tra cui MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB o un'API REST. E a differenza di altre piattaforme, con Budibase puoi partire da zero e creare applicazioni aziendali senza alcuna fonte di dati. [Richiedi una nuova fonte di dati](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
-
+
-### Progettare e creare applicazioni utilizzando component
+### Progetta e crea applicazioni utilizzando componenti predefiniti.
+
+Budibase è dotato di componenti predefiniti belli e potenti che puoi utilizzare come mattoni per costruire la tua interfaccia utente. Esporremo anche molte delle tue opzioni di stile CSS preferite in modo che tu possa esprimere una creatività maggiore. [Richiedi un nuovo componente](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
+
+
+
+
+
+
+### Automatizza processi, integra altri strumenti e collegati a webhook
+Risparmia tempo automatizzando processi manuali e flussi di lavoro. Che si tratti di connettersi a webhook o automatizzare email, basta dire a Budibase cosa fare e lasciarlo lavorare per te. Puoi facilmente [creare una nuova automazione per Budibase qui](https://github.com/Budibase/automations) o [Richiedere una nuova automazione](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
+
+
+
+
+
+
+### Integrazione con i tuoi strumenti preferiti
+Budibase si integra con vari strumenti popolari, consentendoti di creare applicazioni che si adattano perfettamente alla tua stack tecnologica.
+
+
+
+
+
+
+### Paradiso degli amministratori
+Budibase è progettato per crescere. Con Budibase, puoi auto-ospitarti sulla tua infrastruttura e gestire globalmente utenti, home, SMTP, applicazioni, gruppi, aspetto e altro ancora. Puoi anche fornire agli utenti/gruppi un portale delle applicazioni e affidare la gestione degli utenti al responsabile del gruppo.
+
+- Guarda il video promozionale: https://youtu.be/xoljVpty_Kw
+
+
+
+## 🏁 Inizio
+
+
+
+Implementa Budibase self-hosted nella tua infrastruttura esistente, utilizzando Docker, Kubernetes e Digital Ocean.
+Oppure utilizza Budibase Cloud se non hai bisogno di auto-ospitare e desideri iniziare rapidamente.
+
+### [Inizia con Budibase](https://budibase.com)
+
+
+
+
+## 🎓 Imparare Budibase
+
+La documentazione Budibase [è qui](https://docs.budibase.com).
+
+
+
+
+
+## 💬 Comunità
+
+Se hai domande o vuoi discutere con altri utenti di Budibase e unirti alla nostra comunità, vai su: [Discussioni Github](https://github.com/Budibase/budibase/discussions)
+
+
+
+
+## ❗ Codice di condotta
+
+Budibase si impegna a offrire a tutti un'esperienza accogliente, diversificata e priva di molestie. Ci aspettiamo che tutti i membri della comunità Budibase rispettino i principi del nostro [**Codice di condotta**](https://github.com/Budibase/budibase/blob/HEAD/.github/CODE_OF_CONDUCT.md). Grazie per la tua attenzione.
+
+
+
+
+
+
+## 🙌 Contribuire a Budibase
+
+Che tu stia aprendo un rapporto di bug o creando una Pull request, ogni contributo è apprezzato e benvenuto. Se stai pensando di implementare una nuova funzionalità o modificare l'API, crea prima un Issue. In questo modo possiamo assicurarci che il tuo lavoro non sia inutile.
+
+### Non sai da dove cominciare ?
+Un buon punto di partenza per contribuire è qui: [Progetti in corso](https://github.com/Budibase/budibase/projects/22).
+
+### Come è organizzato il repo ?
+Budibase è un monorepo gestito da lerna. Lerna gestisce la costruzione e la pubblicazione dei pacchetti di Budibase. Ecco, a grandi linee, i pacchetti che compongono Budibase.
+
+- [packages/builder](https://github.com/Budibase/budibase/tree/HEAD/packages/builder) - contiene il codice per l'applicazione svelte lato client di budibase builder.
+
+- [packages/client](https://github.com/Budibase/budibase/tree/HEAD/packages/client) - Un modulo che viene eseguito nel browser e che è responsabile della lettura delle definizioni JSON e della creazione di applicazioni web viventi da esse.
+
+- [packages/server](https://github.com/Budibase/budibase/tree/HEAD/packages/server) - Il server budibase. Questa applicazione Koa è responsabile del servizio del JS per le applicazioni builder e budibase, oltre a fornire l'API per l'interazione con il database e il filesystem.
+
+Per ulteriori informazioni, vedere [CONTRIBUTING.md](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md)
+
+
+
+
+## 📝 Licenza
+
+Budibase è open source, con licenza [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html). Le librerie client e dei componenti sono con licenza [MPL](https://directory.fsf.org/wiki/License:MPL-2.0) - quindi le applicazioni che crei possono essere utilizzate con licenza come desideri.
+
+
+
+## ⭐ Stargazers nel tempo
+
+[![Stargazers nel tempo](https://starchart.cc/Budibase/budibase.svg)](https://starchart.cc/Budibase/budibase)
+
+Se riscontri problemi tra gli aggiornamenti del builder, utilizza la seguente guida [qui](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md#troubleshooting) per pulire il tuo ambiente.
+
+
+
+## Contributeurs ✨
+
+Grazie a queste meravigliose persone ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
+
+
+
+
+
+
+
+
+
+
+Questo progetto segue il [convenant del contribuente](https://github.com/Budibase/budibase/blob/master/CODE_OF_CONDUCT.md). Ogni contributo è il benvenuto!
+
+
+
+
+
diff --git a/i18n/README.por.md b/i18n/README.por.md
index b23988cac5..b8954dbe22 100644
--- a/i18n/README.por.md
+++ b/i18n/README.por.md
@@ -11,7 +11,7 @@
A plataforma low-code que você vai adorar usar
- Budibase é uma plataforma low-code open source e é a maneira mais fácil de criar ferramentas internas que aumentam a produtividade.
+ Budibase é uma plataforma low-code de código aberto e é a maneira mais fácil de criar ferramentas internas que melhoram a produtividade.
@@ -25,15 +25,15 @@
-
+
-
+
-
+
@@ -44,7 +44,7 @@
·
Documentação
·
- Solicitações de melhoria
+ Solicitar melhorias
·
Reportar um bug
·
@@ -52,37 +52,160 @@
+## ✨ Recursos
-## ✨ Funcionalidades
-
-### Construa e implante um verdadeiro software
-Ao contrário de outras plataformas, com Budibase você constrói e implanta aplicativos de página única. Os aplicativos Budibase são altamente performáticos e podem ser designados de forma responsiva, proporcionando assim aos seus usuários uma experiência excepcional.
+### Construa e implante um software real
+Ao contrário de outras plataformas, com o Budibase você constrói e implanta aplicativos de uma página. Os aplicativos Budibase são altamente performáticos e podem ser designados de forma responsiva, proporcionando uma experiência excepcional aos seus usuários.
-### Fonte livre e extensível
-Budibase é software livre - sob licença GPL v3. Isso deve lhe dar tranquilidade de que Budibase estará sempre por aqui. Você também pode codificar no Budibase ou bifurcá-lo e fazer alterações como quiser, tornando-o uma experiência amigável para desenvolvedores.
+### Código-fonte livre e extensível
+Budibase é software livre - sob a licença GPL v3. Isso deve lhe dar confiança de que o Budibase estará sempre disponível. Você também pode codificar no Budibase ou bifurcá-lo e fazer alterações conforme desejar, tornando-o amigável para desenvolvedores.
### Importar dados ou começar do zero
-Budibase pode extrair dados de várias fontes, incluindo MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB ou uma API REST. E ao contrário de outras plataformas, com Budibase, você pode começar do zero e criar aplicativos de negócios sem nenhuma fonte de dados. [Solicitar uma nova fonte de dados](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
+Budibase pode extrair dados de várias fontes, incluindo MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB ou uma API REST. E ao contrário de outras plataformas, com o Budibase você pode começar do zero e criar aplicativos de negócios sem nenhuma fonte de dados. [Solicitar uma nova fonte de dados](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
-
+
-### Design e criação de aplicativos usando componentes pré-definidos.
-
-Budibase vem com componentes belamente projetados e poderosos que você pode usar como blocos de construção para construir sua interface do usuário. Também expomos muitas de suas opções de estilo CSS favoritas para que você possa ser mais criativo. [Solicitar um novo componente](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
+### Projetar e criar aplicativos usando componentes pré-definidos
+O Budibase vem com componentes lindamente projetados e poderosos que você pode usar como blocos de construção para criar sua interface do usuário. Também oferecemos muitas das suas opções de estilo CSS favoritas para que você possa mostrar sua criatividade. [Solicitar um novo componente](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
-
+
-### Automatize processos, integre outras ferramentas e se conecte a webhooks
+### Automatizar processos, integrar outras ferramentas e conectar webhooks
Economize tempo automatizando processos manuais e fluxos de trabalho. Seja conectando-se a webhooks ou automatizando e-mails, basta dizer ao Budibase o que fazer e deixá-lo trabalhar para você. Você pode facilmente [criar uma nova automação para o Budibase aqui](https://github.com/Budibase/automations) ou [Solicitar uma nova automação](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
-
+
+
+
+
+### Integração com suas ferramentas favoritas
+O Budibase se integra a várias ferramentas populares, permitindo que você crie aplicativos que se encaixam perfeitamente em sua pilha tecnológica.
+
+
+
+
+
+
+### Paraíso dos administradores
+O Budibase é projetado para escalar. Com o Budibase, você pode se auto-hospedar em sua própria infraestrutura e gerenciar globalmente usuários, home, SMTP, aplicativos, grupos, aparência e muito mais. Você também pode fornecer aos usuários/grupos um portal de aplicativos e delegar o gerenciamento de usuários ao líder do grupo.
+
+- Assista ao vídeo promocional: https://youtu.be/xoljVpty_Kw
+
+
+
+## 🏁 Começar
+
+
+
+Implante o Budibase em auto-hospedagem em sua infraestrutura existente, usando Docker, Kubernetes e Digital Ocean.
+Ou use o Budibase Cloud se você não precisar se auto-hospedar e quiser começar rapidamente.
+
+### [Começar com o Budibase](https://budibase.com)
+
+
+
+
+## 🎓 Aprenda Budibase
+
+A documentação Budibase [está aqui](https://docs.budibase.com).
+
+
+
+
+
+## 💬 Comunidade
+
+Se você tiver alguma dúvida ou quiser conversar com outros usuários do Budibase e se juntar à nossa comunidade, visite [Discussões do Github](https://github.com/Budibase/budibase/discussions)
+
+
+
+
+## ❗ Código de Conduta
+
+O Budibase está comprometido em oferecer a todos uma experiência acolhedora, diversificada e livre de assédio. Esperamos que todos os membros da comunidade Budibase sigam os princípios do nosso [**Código de Conduta**](https://github.com/Budibase/budibase/blob/HEAD/.github/CODE_OF_CONDUCT.md). Obrigado por ler.
+
+
+
+
+
+
+## 🙌 Contribuindo para o Budibase
+
+Seja abrindo uma issue ou criando um pull request, toda contribuição é apreciada e bem-vinda. Se você está pensando em implementar uma nova funcionalidade ou alterar a API, por favor, crie primeiro uma Issue. Assim, podemos garantir que seu trabalho não seja em vão.
+
+### Não sabe por onde começar?
+Um bom lugar para começar a contribuir é aqui: [Projetos em andamento](https://github.com/Budibase/budibase/projects/22).
+
+### Como o repositório está organizado?
+O Budibase é um monorepo gerenciado pelo lerna. O Lerna cuida da construção e publicação dos pacotes do Budibase. Aqui estão, em alto nível, os pacotes que compõem o Budibase.
+
+- [packages/builder](https://github.com/Budibase/budibase/tree/HEAD/packages/builder) - contém o código para o aplicativo svelte do lado do cliente do budibase builder.
+
+- [packages/client](https://github.com/Budibase/budibase/tree/HEAD/packages/client) - Um módulo que roda no navegador e é responsável por ler definições JSON e criar aplicativos web dinâmicos a partir delas.
+
+- [packages/server](https://github.com/Budibase/budibase/tree/HEAD/packages/server) - O servidor budibase. Este aplicativo Koa é responsável por servir o JS para os aplicativos builder e budibase, bem como fornecer a API para interagir com o banco de dados e o sistema de arquivos.
+
+Para mais informações, veja [CONTRIBUTING.md](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md)
+
+
+
+
+## 📝 Licença
+
+O Budibase é open source, sob a licença [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html). As bibliotecas do cliente e dos componentes estão licenciadas sob [MPL](https://directory.fsf.org/wiki/License:MPL-2.0) - para que os aplicativos que você cria possam ser usados sob licença como você desejar.
+
+
+
+## ⭐ Stargazers ao longo do tempo
+
+[![Stargazers ao longo do tempo](https://starchart.cc/Budibase/budibase.svg)](https://starchart.cc/Budibase/budibase)
+
+Se você tiver problemas entre as atualizações do builder, por favor, use o guia a seguir [aqui](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md#troubleshooting) para limpar seu ambiente.
+
+
+
+## Contribuidores ✨
+
+Agradecimentos a estas pessoas maravilhosas ([chave de emoji](https://allcontributors.org/docs/fr/emoji-key)):
+
+
+
+
+
+
+
+
+
+
+## Licença
+
+Distribuído sob a licença GPL v3.0. Veja `LICENSE` para mais informações.
+
diff --git a/i18n/README.ru.md b/i18n/README.ru.md
index 61e5a00550..b235828a40 100644
--- a/i18n/README.ru.md
+++ b/i18n/README.ru.md
@@ -8,10 +8,10 @@
- Платформа low-code, которую вы будете любить использовать
+ Низкокодовая платформа, которую вы полюбите использовать
- Budibase - это open source платформа low-code, которая является самым простым способом создания внутренних инструментов, повышающих производительность.
+ Budibase - это открытая низкокодовая платформа, которая представляет собой самый простой способ создания внутренних инструментов, повышающих производительность.
@@ -25,10 +25,10 @@
-
+
-
+
@@ -44,7 +44,7 @@
·
Документация
·
- Запросы на улучшение
+ Запросы на улучшения
·
Сообщить об ошибке
·
@@ -52,32 +52,156 @@
+## ✨ Функциональные возможности
-## ✨ Функциональности
-
-### Создание и развертывание настоящего программного обеспечения
-В отличие от других платформ, с Budibase вы создаете и развертываете одностраничные приложения. Приложения Budibase обладают высокой производительностью и могут быть разработаны с адаптивным дизайном, обеспечивая пользователям исключительный опыт.
+### Строим и развертываем настоящее программное обеспечение
+В отличие от других платформ, с помощью Budibase вы создаете и развертываете одностраничные приложения. Приложения Budibase имеют высокую производительность и могут быть адаптированы для разных устройств, обеспечивая вашим пользователям удивительный опыт.
-### Свободный и расширяемый исходный код
-Budibase - это свободное программное обеспечение под лицензией GPL v3. Это должно дать вам уверенность в том, что Budibase всегда будет доступен. Вы также можете кодировать в Budibase или создать его форк и вносить изменения по своему усмотрению, что делает его дружественным для разработчиков.
+### Открытый и расширяемый исходный код
+Budibase - это свободное программное обеспечение под лицензией GPL v3. Это должно вас уверить в том, что Budibase всегда будет здесь. Вы также можете писать код в Budibase или форкнуть его и вносить изменения по своему усмотрению, что сделает его дружелюбным для разработчиков.
### Импорт данных или начало с нуля
-Budibase может извлекать данные из различных источников, включая MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB или REST API. И в отличие от других платформ, с Budibase вы можете начать с нуля и создавать деловые приложения без каких-либо источников данных. [Запросить новый источник данных](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
+Budibase может получать данные из различных источников, включая MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB или REST API. И в отличие от других платформ, с помощью Budibase вы можете начать с нуля и создавать бизнес-приложения без каких-либо источников данных. [Запросить новый источник данных](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
-
+
-### Проектирование и создание приложений с помощью предварительно определенных компонентов
-Budibase поставляется с красиво спроектированными и мощными компонентами, которые можно использовать как строительные блоки для создания вашего пользовательского интерфейса. Мы также предоставляем множество ваших любимых опций стилей CSS, чтобы вы могли проявить свою творческую мысль. [Запросить новый компонент](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
+### Проектирование и создание приложений с использованием предварительно определенных компонентов.
+
+Budibase поставляется с красиво оформленными и мощными компонентами, которые вы можете использовать как строительные блоки для создания вашего пользовательского интерфейса. Мы также предоставляем множество ваших любимых опций стилей CSS, чтобы вы могли проявить больше креативности. [Запросить новый компонент](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
-
+
-### Автоматизация процессов, интеграция с другими инструментами и подключение веб-хуков
-Экономьте время, автоматизируя ручные процессы и рабочие пот
+### Автоматизация процессов, интеграция с другими инструментами и подключение к вебхукам
+Экономьте время, автоматизируя ручные процессы и рабочие потоки. Будь то подключение к вебхукам или автоматизация отправки электронных писем, просто скажите Budibase, что он должен делать, и позвольте ему работать за вас. Вы можете легко [создать новую автоматизацию для Budibase здесь](https://github.com/Budibase/automations) или [Запросить новую автоматизацию](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
+
+
+
+
+
+
+### Интеграция с вашими любимыми инструментами
+Budibase интегрируется с рядом популярных инструментов, что позволяет вам создавать приложения, которые идеально вписываются в вашу технологическую стопку.
+
+
+
+
+
+
+### Рай для админов
+Budibase разработан для масштабирования. С Budibase вы можете самостоятельно размещать его на своей собственной инфраструктуре и глобально управлять пользователями, доменами, SMTP, приложениями, группами, внешним видом и многим другим. Вы также можете предоставить пользователям/группам портал приложений и поручить управление пользователями руководителю группы.
+
+- Смотрите промо-видео: https://youtu.be/xoljVpty_Kw
+
+
+
+## 🏁 Начало работы
+
+
+
+Разверните Budibase на своей собственной инфраструктуре с использованием Docker, Kubernetes и Digital Ocean.
+Или используйте Budibase Cloud, если вам не нужно самостоятельно размещаться, и вы хотите быстро начать.
+
+### [Начать работу с Budibase](https://budibase.com)
+
+
+
+
+## 🎓 Изучение Budibase
+
+Документация Budibase [здесь](https://docs.budibase.com).
+
+
+
+
+
+## 💬 Сообщество
+
+Если у вас есть вопросы или вы хотите обсудить что-то с другими пользователями Budibase и присоединиться к нашему сообществу, пожалуйста, перейдите по следующей ссылке: [Обсуждения на GitHub](https://github.com/Budibase/budibase/discussions)
+
+
+
+
+## ❗ Кодекс поведения
+
+Budibase обязуется обеспечить каждому дружелюбный, разнообразный и безопасный опыт. Мы ожидаем, что все члены сообщества Budibase будут следовать принципам нашего [**Кодекса поведения**](https://github.com/Budibase/budibase/blob/HEAD/.github/CODE_OF_CONDUCT.md). Спасибо за внимание.
+
+
+
+
+
+
+## 🙌 Вклад в Budibase
+
+Будь то открытие ошибки или создание запроса на включение изменений, любой вклад приветствуется и приветствуется. Если вы планируете реализовать новую функциональность или изменить API, сначала создайте Issue. Так мы сможем убедиться, что ваша работа не напрасна.
+
+### Не знаете, с чего начать?
+Хорошее место для начала вклада - это здесь: [Текущие проекты](https://github.com/Budibase/budibase/projects/22).
+
+### Как организован репозиторий?
+Budibase - это монорепозиторий, управляемый с помощью lerna. Lerna управляет сборкой и публикацией пакетов Budibase. Вот, в общих чертах, пакеты, из которых состоит Budibase.
+
+- [packages/builder](https://github.com/Budibase/budibase/tree/HEAD/packages/builder) - содержит код клиентского приложения Svelte для Budibase builder.
+
+- [packages/client](https://github.com/Budibase/budibase/tree/HEAD/packages/client) - Модуль, который запускается в браузере и отвечает за чтение JSON-определений и создание веб-приложений из них.
+
+- [packages/server](https://github.com/Budibase/budibase/tree/HEAD/packages/server) - Сервер Budibase. Это приложение Koa отвечает за предоставление JS для строителей и приложений Budibase, а также предоставляет API для взаимодействия с базой данных и файловой системой.
+
+Для получения дополнительной информации см. [CONTRIBUTING.md](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md)
+
+
+
+
+## 📝 Лицензия
+
+Budibase является проектом с открытым исходным кодом, лицензированным по [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html). Клиентские библиотеки и компоненты лицензируются по [MPL](https://directory.fsf.org/wiki/License:MPL-2.0), так что приложения, которые вы создаете, могут использоваться под любой лицензией, как вам угодно.
+
+
+
+## ⭐ Старгейзеры во времени
+
+[![Stargazers во времени](https://starchart.cc/Budibase/budibase.svg)](https://starchart.cc/Budibase/budibase)
+
+Если у вас возникли проблемы между обновлениями билдера, пожалуйста, используйте следующее руководство [здесь](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md#troubleshooting), чтобы очистить ваше окружение.
+
+
+
+## Участники ✨
+
+Благодарим этих замечательных людей ([ключи эмодзи](https://allcontributors.org/docs/ru/emoji-key)):
+
+
+
+
+
+
+
+
+
+
From 80a6afd54f03a6dfb37a1b3f0cce763fcace349f Mon Sep 17 00:00:00 2001
From: mikesealey
Date: Fri, 19 Apr 2024 10:41:39 +0100
Subject: [PATCH 055/338] closes side panel when navigating away regardless of
ignoreClicksOutside
---
packages/client/src/components/app/Layout.svelte | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/client/src/components/app/Layout.svelte b/packages/client/src/components/app/Layout.svelte
index bfbac8f4f1..dccb061254 100644
--- a/packages/client/src/components/app/Layout.svelte
+++ b/packages/client/src/components/app/Layout.svelte
@@ -284,7 +284,9 @@
url={navItem.url}
subLinks={navItem.subLinks}
internalLink={navItem.internalLink}
- on:clickLink={() => (mobileOpen = false)}
+ on:clickLink={(() => (mobileOpen = false),
+ console.log("287"),
+ sidePanelStore.actions.close)}
leftNav={navigation === "Left"}
{mobile}
{navStateStore}
From 237bc707581900f3f9bbcfff597d88d90a344454 Mon Sep 17 00:00:00 2001
From: mikesealey
Date: Fri, 19 Apr 2024 10:58:25 +0100
Subject: [PATCH 056/338] removes console.log()
---
packages/client/src/components/app/Layout.svelte | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/client/src/components/app/Layout.svelte b/packages/client/src/components/app/Layout.svelte
index dccb061254..363a464143 100644
--- a/packages/client/src/components/app/Layout.svelte
+++ b/packages/client/src/components/app/Layout.svelte
@@ -285,7 +285,6 @@
subLinks={navItem.subLinks}
internalLink={navItem.internalLink}
on:clickLink={(() => (mobileOpen = false),
- console.log("287"),
sidePanelStore.actions.close)}
leftNav={navigation === "Left"}
{mobile}
From bdf15b21b1f62cea09a3c03d5e0b8c7fc9260d66 Mon Sep 17 00:00:00 2001
From: Dean
Date: Fri, 19 Apr 2024 11:49:20 +0100
Subject: [PATCH 057/338] Fixes for filter drawer padding
---
.../buttons/TableFilterButton.svelte | 19 ++++++++++---------
.../src/components/FilterBuilder.svelte | 1 -
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte
index 0fc3fd505e..140cac1533 100644
--- a/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte
+++ b/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte
@@ -1,6 +1,6 @@
@@ -284,8 +289,7 @@
url={navItem.url}
subLinks={navItem.subLinks}
internalLink={navItem.internalLink}
- on:clickLink={(() => (mobileOpen = false),
- sidePanelStore.actions.close)}
+ on:clickLink={handleClickLink}
leftNav={navigation === "Left"}
{mobile}
{navStateStore}
From 4c0d3ed5f38781a5db5e024993b3e7de7e6692ad Mon Sep 17 00:00:00 2001
From: mikesealey
Date: Fri, 19 Apr 2024 14:45:57 +0100
Subject: [PATCH 059/338] runs the closeSidePanel function when navigating away
using a button-action
---
packages/client/src/utils/buttonActions.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js
index d883ee1b55..4ab7490ae7 100644
--- a/packages/client/src/utils/buttonActions.js
+++ b/packages/client/src/utils/buttonActions.js
@@ -240,6 +240,7 @@ const triggerAutomationHandler = async action => {
const navigationHandler = action => {
const { url, peek, externalNewTab } = action.parameters
routeStore.actions.navigate(url, peek, externalNewTab)
+ closeSidePanelHandler()
}
const queryExecutionHandler = async action => {
From b2f81276cdf17ab43118595e0ae9c79c626a7b46 Mon Sep 17 00:00:00 2001
From: Budibase Staging Release Bot <>
Date: Mon, 22 Apr 2024 14:14:24 +0000
Subject: [PATCH 060/338] Bump version to 2.23.11
---
lerna.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lerna.json b/lerna.json
index e8bcd4429c..728cddc194 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "version": "2.23.10",
+ "version": "2.23.11",
"npmClient": "yarn",
"packages": [
"packages/*",
From a4c0328c53610fab0209be6e25af502609d9e38d Mon Sep 17 00:00:00 2001
From: Peter Clement
Date: Mon, 22 Apr 2024 16:30:57 +0100
Subject: [PATCH 061/338] REST file handling and SMTP automation block
attachments (#13403)
* handle files in rest connector
* fetch presigned url and return
* further updates to handle files in rest connector
* remove unused important and fix extension bug
* wrong expiry param
* tests
* add const for temp bucket
* handle ttl on bucket
* more bucket ttl work
* split out fileresponse and xmlresponse into utils
* lint
* remove log
* fix tests
* some pr comments
* update function naming and lint
* adding back needed response for frontend
* use fsp
* handle different content-disposition and potential path traversal
* add test container for s3 / minio
* add test case for filename* and ascii filenames
* move tests into separate describe
* remove log
* up timeout
* switch to minio image instead of localstack
* use minio image instead of s3 for testing
* stream file upload instead
* use streamUpload and update signatures
* update bucketcreate return
* throw real error
* tidy up
* pro
* pro ref fix?
* pro fix
* pro fix?
* move minio test provider to backend-core
* update email builder to allow attachments
* testing for sending files via smtp
* use backend-core minio test container in server
* handle different types of url
* fix minio test provider
* test with container host
* lint
* try different hostname?
* Revert "try different hostname?"
This reverts commit cfefdb8ded2b49462604053cf140e7292771c651.
* fix issue with fetching of signed url with test minio
* update autoamtion attachments to take filename and url
* fix tests
* pro ref
* fix parsing of url object
* pr comments and linting
* pro ref
* fix pro again
* fix pro
* account-portal
* fix null issue
* fix ref
* ref
* When sending a file attachment in email fetch it directly from our object store
* add more checks to ensure we're working with a signed url
* update test to account for direct object store read
* formatting
* fix time issues within test
* update bucket and path extraction to regex
* use const in regex
* pro
* Updating TTL handling in upload functions (#13539)
* Updating TTL handling in upload functions
* describe ttl type
* account for ttl creation in existing buckets and update types
* fix tests
* pro
* pro
---
packages/backend-core/src/environment.ts | 2 +
.../src/objectStore/objectStore.ts | 121 ++++++++++++++----
.../backend-core/src/objectStore/utils.ts | 26 ++++
.../tests/core/utilities/index.ts | 3 +
.../tests/core/utilities/minio.ts | 34 +++++
.../SetupPanel/AutomationBlockSetup.svelte | 56 +++++++-
.../integration/KeyValueBuilder.svelte | 29 +++--
packages/cli/src/backups/objectStore.ts | 4 +-
packages/pro | 2 +-
packages/server/package.json | 3 +
.../src/automations/steps/sendSmtpEmail.ts | 12 +-
.../automations/tests/sendSmtpEmail.spec.ts | 8 ++
packages/server/src/integrations/rest.ts | 68 +++++-----
.../src/integrations/tests/rest.spec.ts | 116 ++++++++++++++++-
.../server/src/integrations/utils/utils.ts | 78 ++++++++++-
.../src/utilities/fileSystem/clientLibrary.ts | 28 ++--
.../server/src/utilities/workerRequests.ts | 5 +-
.../types/src/documents/app/automation.ts | 8 ++
.../src/api/controllers/global/email.ts | 2 +
.../api/routes/global/tests/realEmail.spec.ts | 47 ++++++-
packages/worker/src/tests/api/email.ts | 4 +-
packages/worker/src/tests/jestEnv.ts | 4 +-
.../worker/src/tests/structures/configs.ts | 4 +-
packages/worker/src/utilities/email.ts | 40 +++++-
yarn.lock | 19 ++-
25 files changed, 619 insertions(+), 104 deletions(-)
create mode 100644 packages/backend-core/tests/core/utilities/minio.ts
diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts
index 8dbc904643..9ade81b9d7 100644
--- a/packages/backend-core/src/environment.ts
+++ b/packages/backend-core/src/environment.ts
@@ -29,6 +29,7 @@ const DefaultBucketName = {
TEMPLATES: "templates",
GLOBAL: "global",
PLUGINS: "plugins",
+ TEMP: "tmp-file-attachments",
}
const selfHosted = !!parseInt(process.env.SELF_HOSTED || "")
@@ -146,6 +147,7 @@ const environment = {
process.env.GLOBAL_BUCKET_NAME || DefaultBucketName.GLOBAL,
PLUGIN_BUCKET_NAME:
process.env.PLUGIN_BUCKET_NAME || DefaultBucketName.PLUGINS,
+ TEMP_BUCKET_NAME: process.env.TEMP_BUCKET_NAME || DefaultBucketName.TEMP,
USE_COUCH: process.env.USE_COUCH || true,
MOCK_REDIS: process.env.MOCK_REDIS,
DEFAULT_LICENSE: process.env.DEFAULT_LICENSE,
diff --git a/packages/backend-core/src/objectStore/objectStore.ts b/packages/backend-core/src/objectStore/objectStore.ts
index 8d18fb97fd..aa5365c5c3 100644
--- a/packages/backend-core/src/objectStore/objectStore.ts
+++ b/packages/backend-core/src/objectStore/objectStore.ts
@@ -7,31 +7,41 @@ import tar from "tar-fs"
import zlib from "zlib"
import { promisify } from "util"
import { join } from "path"
-import fs, { ReadStream } from "fs"
+import fs, { PathLike, ReadStream } from "fs"
import env from "../environment"
-import { budibaseTempDir } from "./utils"
+import { bucketTTLConfig, budibaseTempDir } from "./utils"
import { v4 } from "uuid"
import { APP_PREFIX, APP_DEV_PREFIX } from "../db"
+import fsp from "fs/promises"
const streamPipeline = promisify(stream.pipeline)
// use this as a temporary store of buckets that are being created
const STATE = {
bucketCreationPromises: {},
}
+const signedFilePrefix = "/files/signed"
type ListParams = {
ContinuationToken?: string
}
-type UploadParams = {
+type BaseUploadParams = {
bucket: string
filename: string
- path: string
type?: string | null
- // can be undefined, we will remove it
- metadata?: {
- [key: string]: string | undefined
- }
+ metadata?: { [key: string]: string | undefined }
+ body?: ReadableStream | Buffer
+ ttl?: number
+ addTTL?: boolean
+ extra?: any
+}
+
+type UploadParams = BaseUploadParams & {
+ path?: string | PathLike
+}
+
+type StreamUploadParams = BaseUploadParams & {
+ stream: ReadStream
}
const CONTENT_TYPE_MAP: any = {
@@ -41,6 +51,8 @@ const CONTENT_TYPE_MAP: any = {
js: "application/javascript",
json: "application/json",
gz: "application/gzip",
+ svg: "image/svg+xml",
+ form: "multipart/form-data",
}
const STRING_CONTENT_TYPES = [
@@ -105,7 +117,10 @@ export function ObjectStore(
* Given an object store and a bucket name this will make sure the bucket exists,
* if it does not exist then it will create it.
*/
-export async function makeSureBucketExists(client: any, bucketName: string) {
+export async function createBucketIfNotExists(
+ client: any,
+ bucketName: string
+): Promise<{ created: boolean; exists: boolean }> {
bucketName = sanitizeBucket(bucketName)
try {
await client
@@ -113,15 +128,16 @@ export async function makeSureBucketExists(client: any, bucketName: string) {
Bucket: bucketName,
})
.promise()
+ return { created: false, exists: true }
} catch (err: any) {
const promises: any = STATE.bucketCreationPromises
const doesntExist = err.statusCode === 404,
noAccess = err.statusCode === 403
if (promises[bucketName]) {
await promises[bucketName]
+ return { created: false, exists: true }
} else if (doesntExist || noAccess) {
if (doesntExist) {
- // bucket doesn't exist create it
promises[bucketName] = client
.createBucket({
Bucket: bucketName,
@@ -129,13 +145,15 @@ export async function makeSureBucketExists(client: any, bucketName: string) {
.promise()
await promises[bucketName]
delete promises[bucketName]
+ return { created: true, exists: false }
+ } else {
+ throw new Error("Access denied to object store bucket." + err)
}
} else {
throw new Error("Unable to write to object store bucket.")
}
}
}
-
/**
* Uploads the contents of a file given the required parameters, useful when
* temp files in use (for example file uploaded as an attachment).
@@ -146,12 +164,22 @@ export async function upload({
path,
type,
metadata,
+ body,
+ ttl,
}: UploadParams) {
const extension = filename.split(".").pop()
- const fileBytes = fs.readFileSync(path)
+
+ const fileBytes = path ? (await fsp.open(path)).createReadStream() : body
const objectStore = ObjectStore(bucketName)
- await makeSureBucketExists(objectStore, bucketName)
+ const bucketCreated = await createBucketIfNotExists(objectStore, bucketName)
+
+ if (ttl && (bucketCreated.created || bucketCreated.exists)) {
+ let ttlConfig = bucketTTLConfig(bucketName, ttl)
+ if (objectStore.putBucketLifecycleConfiguration) {
+ await objectStore.putBucketLifecycleConfiguration(ttlConfig).promise()
+ }
+ }
let contentType = type
if (!contentType) {
@@ -174,6 +202,7 @@ export async function upload({
}
config.Metadata = metadata
}
+
return objectStore.upload(config).promise()
}
@@ -181,14 +210,24 @@ export async function upload({
* Similar to the upload function but can be used to send a file stream
* through to the object store.
*/
-export async function streamUpload(
- bucketName: string,
- filename: string,
- stream: ReadStream | ReadableStream,
- extra = {}
-) {
+export async function streamUpload({
+ bucket: bucketName,
+ stream,
+ filename,
+ type,
+ extra,
+ ttl,
+}: StreamUploadParams) {
+ const extension = filename.split(".").pop()
const objectStore = ObjectStore(bucketName)
- await makeSureBucketExists(objectStore, bucketName)
+ const bucketCreated = await createBucketIfNotExists(objectStore, bucketName)
+
+ if (ttl && (bucketCreated.created || bucketCreated.exists)) {
+ let ttlConfig = bucketTTLConfig(bucketName, ttl)
+ if (objectStore.putBucketLifecycleConfiguration) {
+ await objectStore.putBucketLifecycleConfiguration(ttlConfig).promise()
+ }
+ }
// Set content type for certain known extensions
if (filename?.endsWith(".js")) {
@@ -203,10 +242,18 @@ export async function streamUpload(
}
}
+ let contentType = type
+ if (!contentType) {
+ contentType = extension
+ ? CONTENT_TYPE_MAP[extension.toLowerCase()]
+ : CONTENT_TYPE_MAP.txt
+ }
+
const params = {
Bucket: sanitizeBucket(bucketName),
Key: sanitizeKey(filename),
Body: stream,
+ ContentType: contentType,
...extra,
}
return objectStore.upload(params).promise()
@@ -286,7 +333,7 @@ export function getPresignedUrl(
const signedUrl = new URL(url)
const path = signedUrl.pathname
const query = signedUrl.search
- return `/files/signed${path}${query}`
+ return `${signedFilePrefix}${path}${query}`
}
}
@@ -341,7 +388,7 @@ export async function retrieveDirectory(bucketName: string, path: string) {
*/
export async function deleteFile(bucketName: string, filepath: string) {
const objectStore = ObjectStore(bucketName)
- await makeSureBucketExists(objectStore, bucketName)
+ await createBucketIfNotExists(objectStore, bucketName)
const params = {
Bucket: bucketName,
Key: sanitizeKey(filepath),
@@ -351,7 +398,7 @@ export async function deleteFile(bucketName: string, filepath: string) {
export async function deleteFiles(bucketName: string, filepaths: string[]) {
const objectStore = ObjectStore(bucketName)
- await makeSureBucketExists(objectStore, bucketName)
+ await createBucketIfNotExists(objectStore, bucketName)
const params = {
Bucket: bucketName,
Delete: {
@@ -412,7 +459,13 @@ export async function uploadDirectory(
if (file.isDirectory()) {
uploads.push(uploadDirectory(bucketName, local, path))
} else {
- uploads.push(streamUpload(bucketName, path, fs.createReadStream(local)))
+ uploads.push(
+ streamUpload({
+ bucket: bucketName,
+ filename: path,
+ stream: fs.createReadStream(local),
+ })
+ )
}
}
await Promise.all(uploads)
@@ -467,3 +520,23 @@ export async function getReadStream(
}
return client.getObject(params).createReadStream()
}
+
+/*
+Given a signed url like '/files/signed/tmp-files-attachments/app_123456/myfile.txt' extract
+the bucket and the path from it
+*/
+export function extractBucketAndPath(
+ url: string
+): { bucket: string; path: string } | null {
+ const baseUrl = url.split("?")[0]
+
+ const regex = new RegExp(`^${signedFilePrefix}/(?[^/]+)/(?.+)$`)
+ const match = baseUrl.match(regex)
+
+ if (match && match.groups) {
+ const { bucket, path } = match.groups
+ return { bucket, path }
+ }
+
+ return null
+}
diff --git a/packages/backend-core/src/objectStore/utils.ts b/packages/backend-core/src/objectStore/utils.ts
index 4c3a84ba91..08b5238ff6 100644
--- a/packages/backend-core/src/objectStore/utils.ts
+++ b/packages/backend-core/src/objectStore/utils.ts
@@ -2,6 +2,7 @@ import { join } from "path"
import { tmpdir } from "os"
import fs from "fs"
import env from "../environment"
+import { PutBucketLifecycleConfigurationRequest } from "aws-sdk/clients/s3"
/****************************************************
* NOTE: When adding a new bucket - name *
@@ -15,6 +16,7 @@ export const ObjectStoreBuckets = {
TEMPLATES: env.TEMPLATES_BUCKET_NAME,
GLOBAL: env.GLOBAL_BUCKET_NAME,
PLUGINS: env.PLUGIN_BUCKET_NAME,
+ TEMP: env.TEMP_BUCKET_NAME,
}
const bbTmp = join(tmpdir(), ".budibase")
@@ -29,3 +31,27 @@ try {
export function budibaseTempDir() {
return bbTmp
}
+
+export const bucketTTLConfig = (
+ bucketName: string,
+ days: number
+): PutBucketLifecycleConfigurationRequest => {
+ const lifecycleRule = {
+ ID: `${bucketName}-ExpireAfter${days}days`,
+ Prefix: "",
+ Status: "Enabled",
+ Expiration: {
+ Days: days,
+ },
+ }
+ const lifecycleConfiguration = {
+ Rules: [lifecycleRule],
+ }
+
+ const params = {
+ Bucket: bucketName,
+ LifecycleConfiguration: lifecycleConfiguration,
+ }
+
+ return params
+}
diff --git a/packages/backend-core/tests/core/utilities/index.ts b/packages/backend-core/tests/core/utilities/index.ts
index 787d69be2c..b2f19a0286 100644
--- a/packages/backend-core/tests/core/utilities/index.ts
+++ b/packages/backend-core/tests/core/utilities/index.ts
@@ -4,3 +4,6 @@ export { generator } from "./structures"
export * as testContainerUtils from "./testContainerUtils"
export * as utils from "./utils"
export * from "./jestUtils"
+import * as minio from "./minio"
+
+export const objectStoreTestProviders = { minio }
diff --git a/packages/backend-core/tests/core/utilities/minio.ts b/packages/backend-core/tests/core/utilities/minio.ts
new file mode 100644
index 0000000000..cef33daa91
--- /dev/null
+++ b/packages/backend-core/tests/core/utilities/minio.ts
@@ -0,0 +1,34 @@
+import { GenericContainer, Wait, StartedTestContainer } from "testcontainers"
+import { AbstractWaitStrategy } from "testcontainers/build/wait-strategies/wait-strategy"
+import env from "../../../src/environment"
+
+let container: StartedTestContainer | undefined
+
+class ObjectStoreWaitStrategy extends AbstractWaitStrategy {
+ async waitUntilReady(container: any, boundPorts: any, startTime?: Date) {
+ const logs = Wait.forListeningPorts()
+ await logs.waitUntilReady(container, boundPorts, startTime)
+ }
+}
+
+export async function start(): Promise {
+ container = await new GenericContainer("minio/minio")
+ .withExposedPorts(9000)
+ .withCommand(["server", "/data"])
+ .withEnvironment({
+ MINIO_ACCESS_KEY: "budibase",
+ MINIO_SECRET_KEY: "budibase",
+ })
+ .withWaitStrategy(new ObjectStoreWaitStrategy().withStartupTimeout(30000))
+ .start()
+
+ const port = container.getMappedPort(9000)
+ env._set("MINIO_URL", `http://0.0.0.0:${port}`)
+}
+
+export async function stop() {
+ if (container) {
+ await container.stop()
+ container = undefined
+ }
+}
diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
index 6434c7710d..2d2022299c 100644
--- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
+++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
@@ -32,6 +32,7 @@
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte"
import BindingSidePanel from "components/common/bindings/BindingSidePanel.svelte"
+ import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
import { BindingHelpers, BindingType } from "components/common/bindings/utils"
import {
bindingsToCompletions,
@@ -356,7 +357,8 @@
value.customType !== "queryParams" &&
value.customType !== "cron" &&
value.customType !== "triggerSchema" &&
- value.customType !== "automationFields"
+ value.customType !== "automationFields" &&
+ value.type !== "attachment"
)
}
@@ -372,6 +374,15 @@
console.error(error)
}
})
+ const handleAttachmentParams = keyValuObj => {
+ let params = {}
+ if (keyValuObj?.length) {
+ for (let param of keyValuObj) {
+ params[param.url] = param.filename
+ }
+ }
+ return params
+ }
@@ -437,6 +448,33 @@
value={inputData[key]}
options={Object.keys(table?.schema || {})}
/>
+ {:else if value.type === "attachment"}
+
+
+ {label}
+
+
+
+ onChange(
+ {
+ detail: e.detail.map(({ name, value }) => ({
+ url: name,
+ filename: value,
+ })),
+ },
+ key
+ )}
+ object={handleAttachmentParams(inputData[key])}
+ allowJS
+ {bindings}
+ keyBindings
+ customButtonText={"Add attachment"}
+ keyPlaceholder={"URL"}
+ valuePlaceholder={"Filename"}
+ />
+
+
{:else if value.customType === "filters"}
Define filters
@@ -651,14 +689,22 @@
}
.block-field {
- display: flex; /* Use Flexbox */
+ display: flex;
justify-content: space-between;
- flex-direction: row; /* Arrange label and field side by side */
- align-items: center; /* Align vertically in the center */
- gap: 10px; /* Add some space between label and field */
+ flex-direction: row;
+ align-items: center;
+ gap: 10px;
flex: 1;
}
+ .attachment-field-width {
+ margin-top: var(--spacing-xs);
+ }
+
+ .label-wrapper {
+ margin-top: var(--spacing-s);
+ }
+
.test :global(.drawer) {
width: 10000px !important;
}
diff --git a/packages/builder/src/components/integration/KeyValueBuilder.svelte b/packages/builder/src/components/integration/KeyValueBuilder.svelte
index 74636fc50c..5ed18a970a 100644
--- a/packages/builder/src/components/integration/KeyValueBuilder.svelte
+++ b/packages/builder/src/components/integration/KeyValueBuilder.svelte
@@ -35,6 +35,8 @@
export let bindingDrawerLeft
export let allowHelpers = true
export let customButtonText = null
+ export let keyBindings = false
+ export let allowJS = false
export let compare = (option, value) => option === value
let fields = Object.entries(object || {}).map(([name, value]) => ({
@@ -116,12 +118,23 @@
class:readOnly-menu={readOnly && showMenu}
>
{#each fields as field, idx}
-
+ {#if keyBindings}
+ {
+ field.name = e.detail
+ changed()
+ }}
+ disabled={readOnly}
+ value={field.name}
+ {allowJS}
+ {allowHelpers}
+ drawerLeft={bindingDrawerLeft}
+ />
+ {:else}
+
+ {/if}
{#if isJsonArray(field.value)}
{:else if options}
@@ -134,14 +147,14 @@
{:else if bindings && bindings.length}
{
field.value = e.detail
changed()
}}
disabled={readOnly}
value={field.value}
- allowJS={false}
+ {allowJS}
{allowHelpers}
drawerLeft={bindingDrawerLeft}
/>
diff --git a/packages/cli/src/backups/objectStore.ts b/packages/cli/src/backups/objectStore.ts
index 32fc07c05b..2a24199603 100644
--- a/packages/cli/src/backups/objectStore.ts
+++ b/packages/cli/src/backups/objectStore.ts
@@ -9,7 +9,7 @@ const {
ObjectStore,
retrieve,
uploadDirectory,
- makeSureBucketExists,
+ createBucketIfNotExists,
} = objectStore
const bucketList = Object.values(ObjectStoreBuckets)
@@ -61,7 +61,7 @@ export async function importObjects() {
let count = 0
for (let bucket of buckets) {
const client = ObjectStore(bucket)
- await makeSureBucketExists(client, bucket)
+ await createBucketIfNotExists(client, bucket)
const files = await uploadDirectory(bucket, join(path, bucket), "/")
count += files.length
bar.update(count)
diff --git a/packages/pro b/packages/pro
index b55d5b3200..dff7b5a9dd 160000
--- a/packages/pro
+++ b/packages/pro
@@ -1 +1 @@
-Subproject commit b55d5b32003e3e999a1cbf2e5f3e6ce8d71eace7
+Subproject commit dff7b5a9dd1fd770f8a48fb8e6df1740be605f18
diff --git a/packages/server/package.json b/packages/server/package.json
index 76402785d7..b2ac4e7d43 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -61,14 +61,17 @@
"@google-cloud/firestore": "6.8.0",
"@koa/router": "8.0.8",
"@socket.io/redis-adapter": "^8.2.1",
+ "@types/xml2js": "^0.4.14",
"airtable": "0.10.1",
"arangojs": "7.2.0",
"archiver": "7.0.1",
"aws-sdk": "2.1030.0",
"bcrypt": "5.1.0",
"bcryptjs": "2.4.3",
+ "bl": "^6.0.12",
"bull": "4.10.1",
"chokidar": "3.5.3",
+ "content-disposition": "^0.5.4",
"cookies": "0.8.0",
"csvtojson": "2.0.10",
"curlconverter": "3.21.0",
diff --git a/packages/server/src/automations/steps/sendSmtpEmail.ts b/packages/server/src/automations/steps/sendSmtpEmail.ts
index f1ce3a85c2..31a7759dea 100644
--- a/packages/server/src/automations/steps/sendSmtpEmail.ts
+++ b/packages/server/src/automations/steps/sendSmtpEmail.ts
@@ -7,6 +7,7 @@ import {
AutomationStepType,
AutomationIOType,
AutomationFeature,
+ AutomationCustomIOType,
} from "@budibase/types"
export const definition: AutomationStepSchema = {
@@ -72,10 +73,10 @@ export const definition: AutomationStepSchema = {
title: "Location",
dependsOn: "addInvite",
},
- url: {
- type: AutomationIOType.STRING,
- title: "URL",
- dependsOn: "addInvite",
+ attachments: {
+ type: AutomationIOType.ATTACHMENT,
+ customType: AutomationCustomIOType.MULTI_ATTACHMENTS,
+ title: "Attachments",
},
},
required: ["to", "from", "subject", "contents"],
@@ -110,11 +111,13 @@ export async function run({ inputs }: AutomationStepInput) {
summary,
location,
url,
+ attachments,
} = inputs
if (!contents) {
contents = "No content "
}
to = to || undefined
+
try {
let response = await sendSmtpEmail({
to,
@@ -124,6 +127,7 @@ export async function run({ inputs }: AutomationStepInput) {
cc,
bcc,
automation: true,
+ attachments,
invite: addInvite
? {
startTime,
diff --git a/packages/server/src/automations/tests/sendSmtpEmail.spec.ts b/packages/server/src/automations/tests/sendSmtpEmail.spec.ts
index b86d190afd..f96abde4e6 100644
--- a/packages/server/src/automations/tests/sendSmtpEmail.spec.ts
+++ b/packages/server/src/automations/tests/sendSmtpEmail.spec.ts
@@ -50,6 +50,10 @@ describe("test the outgoing webhook action", () => {
cc: "cc",
bcc: "bcc",
addInvite: true,
+ attachments: [
+ { url: "attachment1", filename: "attachment1.txt" },
+ { url: "attachment2", filename: "attachment2.txt" },
+ ],
...invite,
}
let resp = generateResponse(inputs.to, inputs.from)
@@ -69,6 +73,10 @@ describe("test the outgoing webhook action", () => {
bcc: "bcc",
invite,
automation: true,
+ attachments: [
+ { url: "attachment1", filename: "attachment1.txt" },
+ { url: "attachment2", filename: "attachment2.txt" },
+ ],
})
})
})
diff --git a/packages/server/src/integrations/rest.ts b/packages/server/src/integrations/rest.ts
index 5fa35cc667..ffa91cdce9 100644
--- a/packages/server/src/integrations/rest.ts
+++ b/packages/server/src/integrations/rest.ts
@@ -21,6 +21,10 @@ import { performance } from "perf_hooks"
import FormData from "form-data"
import { URLSearchParams } from "url"
import { blacklist } from "@budibase/backend-core"
+import { handleFileResponse, handleXml } from "./utils"
+import { parse } from "content-disposition"
+import path from "path"
+import { Builder as XmlBuilder } from "xml2js"
const BodyTypes = {
NONE: "none",
@@ -57,8 +61,6 @@ const coreFields = {
},
}
-const { parseStringPromise: xmlParser, Builder: XmlBuilder } = require("xml2js")
-
const SCHEMA: Integration = {
docs: "https://github.com/node-fetch/node-fetch",
description:
@@ -129,42 +131,44 @@ class RestIntegration implements IntegrationBase {
}
async parseResponse(response: any, pagination: PaginationConfig | null) {
- let data, raw, headers
+ let data, raw, headers, filename
+
const contentType = response.headers.get("content-type") || ""
+ const contentDisposition = response.headers.get("content-disposition") || ""
+ if (
+ contentDisposition.includes("attachment") ||
+ contentDisposition.includes("form-data")
+ ) {
+ filename =
+ path.basename(parse(contentDisposition).parameters?.filename) || ""
+ }
+
try {
- if (response.status === 204) {
- data = []
- raw = []
- } else if (contentType.includes("application/json")) {
- data = await response.json()
- raw = JSON.stringify(data)
- } else if (
- contentType.includes("text/xml") ||
- contentType.includes("application/xml")
- ) {
- const rawXml = await response.text()
- data =
- (await xmlParser(rawXml, {
- explicitArray: false,
- trim: true,
- explicitRoot: false,
- })) || {}
- // there is only one structure, its an array, return the array so it appears as rows
- const keys = Object.keys(data)
- if (keys.length === 1 && Array.isArray(data[keys[0]])) {
- data = data[keys[0]]
- }
- raw = rawXml
- } else if (contentType.includes("application/pdf")) {
- data = await response.arrayBuffer() // Save PDF as ArrayBuffer
- raw = Buffer.from(data)
+ if (filename) {
+ return handleFileResponse(response, filename, this.startTimeMs)
} else {
- data = await response.text()
- raw = data
+ if (response.status === 204) {
+ data = []
+ raw = []
+ } else if (contentType.includes("application/json")) {
+ data = await response.json()
+ raw = JSON.stringify(data)
+ } else if (
+ contentType.includes("text/xml") ||
+ contentType.includes("application/xml")
+ ) {
+ let xmlResponse = await handleXml(response)
+ data = xmlResponse.data
+ raw = xmlResponse.rawXml
+ } else {
+ data = await response.text()
+ raw = data
+ }
}
} catch (err) {
- throw "Failed to parse response body."
+ throw `Failed to parse response body: ${err}`
}
+
const size = formatBytes(
response.headers.get("content-length") || Buffer.byteLength(raw, "utf8")
)
diff --git a/packages/server/src/integrations/tests/rest.spec.ts b/packages/server/src/integrations/tests/rest.spec.ts
index 3335f44a7b..4b20017939 100644
--- a/packages/server/src/integrations/tests/rest.spec.ts
+++ b/packages/server/src/integrations/tests/rest.spec.ts
@@ -13,9 +13,23 @@ jest.mock("node-fetch", () => {
}))
})
-import fetch from "node-fetch"
+jest.mock("@budibase/backend-core", () => {
+ const core = jest.requireActual("@budibase/backend-core")
+ return {
+ ...core,
+ context: {
+ ...core.context,
+ getProdAppId: jest.fn(() => "app-id"),
+ },
+ }
+})
+jest.mock("uuid", () => ({ v4: () => "00000000-0000-0000-0000-000000000000" }))
+
import { default as RestIntegration } from "../rest"
import { RestAuthType } from "@budibase/types"
+import fetch from "node-fetch"
+import { objectStoreTestProviders } from "@budibase/backend-core/tests"
+import { Readable } from "stream"
const FormData = require("form-data")
const { URLSearchParams } = require("url")
@@ -611,4 +625,104 @@ describe("REST Integration", () => {
expect(calledConfig.headers).toEqual({})
expect(calledConfig.agent.options.rejectUnauthorized).toBe(false)
})
+
+ describe("File Handling", () => {
+ beforeAll(async () => {
+ jest.unmock("aws-sdk")
+ await objectStoreTestProviders.minio.start()
+ })
+
+ afterAll(async () => {
+ await objectStoreTestProviders.minio.stop()
+ })
+
+ it("uploads file to object store and returns signed URL", async () => {
+ const responseData = Buffer.from("teest file contnt")
+ const filename = "test.tar.gz"
+ const contentType = "application/gzip"
+ const mockReadable = new Readable()
+ mockReadable.push(responseData)
+ mockReadable.push(null)
+ ;(fetch as unknown as jest.Mock).mockImplementationOnce(() =>
+ Promise.resolve({
+ headers: {
+ raw: () => ({
+ "content-type": [contentType],
+ "content-disposition": [`attachment; filename="${filename}"`],
+ }),
+ get: (header: any) => {
+ if (header === "content-type") return contentType
+ if (header === "content-disposition")
+ return `attachment; filename="${filename}"`
+ },
+ },
+ body: mockReadable,
+ })
+ )
+
+ const query = {
+ path: "api",
+ }
+
+ const response = await config.integration.read(query)
+
+ expect(response.data).toEqual({
+ size: responseData.byteLength,
+ name: "00000000-0000-0000-0000-000000000000.tar.gz",
+ url: expect.stringContaining(
+ "/files/signed/tmp-file-attachments/app-id/00000000-0000-0000-0000-000000000000.tar.gz"
+ ),
+ extension: "tar.gz",
+ key: expect.stringContaining(
+ "app-id/00000000-0000-0000-0000-000000000000.tar.gz"
+ ),
+ })
+ })
+
+ it("uploads file with non ascii filename to object store and returns signed URL", async () => {
+ const responseData = Buffer.from("teest file contnt")
+ const contentType = "text/plain"
+ const mockReadable = new Readable()
+ mockReadable.push(responseData)
+ mockReadable.push(null)
+ ;(fetch as unknown as jest.Mock).mockImplementationOnce(() =>
+ Promise.resolve({
+ headers: {
+ raw: () => ({
+ "content-type": [contentType],
+ "content-disposition": [
+ // eslint-disable-next-line no-useless-escape
+ `attachment; filename="£ and ? rates.pdf"; filename*=UTF-8'\'%C2%A3%20and%20%E2%82%AC%20rates.pdf`,
+ ],
+ }),
+ get: (header: any) => {
+ if (header === "content-type") return contentType
+ if (header === "content-disposition")
+ // eslint-disable-next-line no-useless-escape
+ return `attachment; filename="£ and ? rates.pdf"; filename*=UTF-8'\'%C2%A3%20and%20%E2%82%AC%20rates.pdf`
+ },
+ },
+ body: mockReadable,
+ })
+ )
+
+ const query = {
+ path: "api",
+ }
+
+ const response = await config.integration.read(query)
+
+ expect(response.data).toEqual({
+ size: responseData.byteLength,
+ name: "00000000-0000-0000-0000-000000000000.pdf",
+ url: expect.stringContaining(
+ "/files/signed/tmp-file-attachments/app-id/00000000-0000-0000-0000-000000000000.pdf"
+ ),
+ extension: "pdf",
+ key: expect.stringContaining(
+ "app-id/00000000-0000-0000-0000-000000000000.pdf"
+ ),
+ })
+ })
+ })
})
diff --git a/packages/server/src/integrations/utils/utils.ts b/packages/server/src/integrations/utils/utils.ts
index 5a6073ccab..aac3f5f74a 100644
--- a/packages/server/src/integrations/utils/utils.ts
+++ b/packages/server/src/integrations/utils/utils.ts
@@ -6,10 +6,15 @@ import {
TableSourceType,
FieldSchema,
} from "@budibase/types"
+import { context, objectStore } from "@budibase/backend-core"
+import { v4 } from "uuid"
+import { parseStringPromise as xmlParser } from "xml2js"
+import { formatBytes } from "../../utilities"
+import bl from "bl"
+import env from "../../environment"
import { DocumentType, SEPARATOR } from "../../db/utils"
import { InvalidColumns, DEFAULT_BB_DATASOURCE_ID } from "../../constants"
import { helpers, utils } from "@budibase/shared-core"
-import env from "../../environment"
import { Knex } from "knex"
const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}`
@@ -467,3 +472,74 @@ export function getPrimaryDisplay(testValue: unknown): string | undefined {
export function isValidFilter(value: any) {
return value != null && value !== ""
}
+
+export async function handleXml(response: any) {
+ let data,
+ rawXml = await response.text()
+ data =
+ (await xmlParser(rawXml, {
+ explicitArray: false,
+ trim: true,
+ explicitRoot: false,
+ })) || {}
+ // there is only one structure, its an array, return the array so it appears as rows
+ const keys = Object.keys(data)
+ if (keys.length === 1 && Array.isArray(data[keys[0]])) {
+ data = data[keys[0]]
+ }
+ return { data, rawXml }
+}
+
+export async function handleFileResponse(
+ response: any,
+ filename: string,
+ startTime: number
+) {
+ let presignedUrl,
+ size = 0
+ const fileExtension = filename.includes(".")
+ ? filename.split(".").slice(1).join(".")
+ : ""
+
+ const processedFileName = `${v4()}.${fileExtension}`
+ const key = `${context.getProdAppId()}/${processedFileName}`
+ const bucket = objectStore.ObjectStoreBuckets.TEMP
+
+ const stream = response.body.pipe(bl((error, data) => data))
+
+ if (response.body) {
+ const contentLength = response.headers.get("content-length")
+ if (contentLength) {
+ size = parseInt(contentLength, 10)
+ } else {
+ const chunks: Buffer[] = []
+ for await (const chunk of response.body) {
+ chunks.push(chunk)
+ size += chunk.length
+ }
+ }
+
+ await objectStore.streamUpload({
+ bucket,
+ filename: key,
+ stream,
+ ttl: 1,
+ type: response.headers["content-type"],
+ })
+ }
+ presignedUrl = await objectStore.getPresignedUrl(bucket, key)
+ return {
+ data: {
+ size,
+ name: processedFileName,
+ url: presignedUrl,
+ extension: fileExtension,
+ key: key,
+ },
+ info: {
+ code: response.status,
+ size: formatBytes(size.toString()),
+ time: `${Math.round(performance.now() - startTime)}ms`,
+ },
+ }
+}
diff --git a/packages/server/src/utilities/fileSystem/clientLibrary.ts b/packages/server/src/utilities/fileSystem/clientLibrary.ts
index 26e89af96b..c994502995 100644
--- a/packages/server/src/utilities/fileSystem/clientLibrary.ts
+++ b/packages/server/src/utilities/fileSystem/clientLibrary.ts
@@ -106,22 +106,22 @@ export async function updateClientLibrary(appId: string) {
}
// Upload latest manifest and client library
- const manifestUpload = objectStore.streamUpload(
- ObjectStoreBuckets.APPS,
- join(appId, "manifest.json"),
- fs.createReadStream(manifest),
- {
+ const manifestUpload = objectStore.streamUpload({
+ bucket: ObjectStoreBuckets.APPS,
+ filename: join(appId, "manifest.json"),
+ stream: fs.createReadStream(manifest),
+ extra: {
ContentType: "application/json",
- }
- )
- const clientUpload = objectStore.streamUpload(
- ObjectStoreBuckets.APPS,
- join(appId, "budibase-client.js"),
- fs.createReadStream(client),
- {
+ },
+ })
+ const clientUpload = objectStore.streamUpload({
+ bucket: ObjectStoreBuckets.APPS,
+ filename: join(appId, "budibase-client.js"),
+ stream: fs.createReadStream(client),
+ extra: {
ContentType: "application/javascript",
- }
- )
+ },
+ })
const manifestSrc = fs.promises.readFile(manifest, "utf8")
diff --git a/packages/server/src/utilities/workerRequests.ts b/packages/server/src/utilities/workerRequests.ts
index c3a0b0abfa..474f857b0a 100644
--- a/packages/server/src/utilities/workerRequests.ts
+++ b/packages/server/src/utilities/workerRequests.ts
@@ -8,7 +8,7 @@ import {
logging,
env as coreEnv,
} from "@budibase/backend-core"
-import { Ctx, User, EmailInvite } from "@budibase/types"
+import { Ctx, User, EmailInvite, EmailAttachment } from "@budibase/types"
interface Request {
ctx?: Ctx
@@ -97,6 +97,7 @@ export async function sendSmtpEmail({
bcc,
automation,
invite,
+ attachments,
}: {
to: string
from: string
@@ -105,6 +106,7 @@ export async function sendSmtpEmail({
cc: string
bcc: string
automation: boolean
+ attachments?: EmailAttachment[]
invite?: EmailInvite
}) {
// tenant ID will be set in header
@@ -122,6 +124,7 @@ export async function sendSmtpEmail({
purpose: "custom",
automation,
invite,
+ attachments,
},
})
)
diff --git a/packages/types/src/documents/app/automation.ts b/packages/types/src/documents/app/automation.ts
index fef72b78a9..c3847a2c04 100644
--- a/packages/types/src/documents/app/automation.ts
+++ b/packages/types/src/documents/app/automation.ts
@@ -10,6 +10,7 @@ export enum AutomationIOType {
ARRAY = "array",
JSON = "json",
DATE = "date",
+ ATTACHMENT = "attachment",
}
export enum AutomationCustomIOType {
@@ -30,6 +31,7 @@ export enum AutomationCustomIOType {
WEBHOOK_URL = "webhookUrl",
AUTOMATION = "automation",
AUTOMATION_FIELDS = "automationFields",
+ MULTI_ATTACHMENTS = "multi_attachments",
}
export enum AutomationTriggerStepId {
@@ -80,6 +82,11 @@ export interface EmailInvite {
url?: string
}
+export interface EmailAttachment {
+ url: string
+ filename: string
+}
+
export interface SendEmailOpts {
// workspaceId If finer grain controls being used then this will lookup config for workspace.
workspaceId?: string
@@ -97,6 +104,7 @@ export interface SendEmailOpts {
bcc?: boolean
automation?: boolean
invite?: EmailInvite
+ attachments?: EmailAttachment[]
}
export const AutomationStepIdArray = [
diff --git a/packages/worker/src/api/controllers/global/email.ts b/packages/worker/src/api/controllers/global/email.ts
index 837f79035a..74a811376a 100644
--- a/packages/worker/src/api/controllers/global/email.ts
+++ b/packages/worker/src/api/controllers/global/email.ts
@@ -15,6 +15,7 @@ export async function sendEmail(ctx: BBContext) {
bcc,
automation,
invite,
+ attachments,
} = ctx.request.body
let user: any
if (userId) {
@@ -31,6 +32,7 @@ export async function sendEmail(ctx: BBContext) {
bcc,
automation,
invite,
+ attachments,
})
ctx.body = {
...response,
diff --git a/packages/worker/src/api/routes/global/tests/realEmail.spec.ts b/packages/worker/src/api/routes/global/tests/realEmail.spec.ts
index ee53f844f9..8880e587c5 100644
--- a/packages/worker/src/api/routes/global/tests/realEmail.spec.ts
+++ b/packages/worker/src/api/routes/global/tests/realEmail.spec.ts
@@ -1,9 +1,15 @@
jest.unmock("node-fetch")
+jest.unmock("aws-sdk")
import { TestConfiguration } from "../../../../tests"
import { EmailTemplatePurpose } from "../../../../constants"
+import { objectStoreTestProviders } from "@budibase/backend-core/tests"
+import { objectStore } from "@budibase/backend-core"
+import tk from "timekeeper"
+import { EmailAttachment } from "@budibase/types"
+
+const fetch = require("node-fetch")
const nodemailer = require("nodemailer")
-const fetch = require("node-fetch")
// for the real email tests give them a long time to try complete/fail
jest.setTimeout(30000)
@@ -12,14 +18,20 @@ describe("/api/global/email", () => {
const config = new TestConfiguration()
beforeAll(async () => {
+ tk.reset()
+ await objectStoreTestProviders.minio.start()
await config.beforeAll()
})
afterAll(async () => {
+ await objectStoreTestProviders.minio.stop()
await config.afterAll()
})
- async function sendRealEmail(purpose: string) {
+ async function sendRealEmail(
+ purpose: string,
+ attachments?: EmailAttachment[]
+ ) {
let response, text
try {
const timeout = () =>
@@ -35,8 +47,14 @@ describe("/api/global/email", () => {
)
await Promise.race([config.saveEtherealSmtpConfig(), timeout()])
await Promise.race([config.saveSettingsConfig(), timeout()])
-
- const res = await config.api.emails.sendEmail(purpose).timeout(20000)
+ let res
+ if (attachments) {
+ res = await config.api.emails
+ .sendEmail(purpose, attachments)
+ .timeout(20000)
+ } else {
+ res = await config.api.emails.sendEmail(purpose).timeout(20000)
+ }
// ethereal hiccup, can't test right now
if (res.status >= 300) {
return
@@ -80,4 +98,25 @@ describe("/api/global/email", () => {
it("should be able to send a password recovery email", async () => {
await sendRealEmail(EmailTemplatePurpose.PASSWORD_RECOVERY)
})
+
+ it("should be able to send an email with attachments", async () => {
+ let bucket = "testbucket"
+ let filename = "test.txt"
+ await objectStore.upload({
+ bucket,
+ filename,
+ body: Buffer.from("test data"),
+ })
+ let presignedUrl = await objectStore.getPresignedUrl(
+ bucket,
+ filename,
+ 60000
+ )
+
+ let attachmentObject = {
+ url: presignedUrl,
+ filename,
+ }
+ await sendRealEmail(EmailTemplatePurpose.WELCOME, [attachmentObject])
+ })
})
diff --git a/packages/worker/src/tests/api/email.ts b/packages/worker/src/tests/api/email.ts
index 6ed0580229..dc464ba16d 100644
--- a/packages/worker/src/tests/api/email.ts
+++ b/packages/worker/src/tests/api/email.ts
@@ -1,3 +1,4 @@
+import { EmailAttachment } from "@budibase/types"
import TestConfiguration from "../TestConfiguration"
import { TestAPI } from "./base"
@@ -6,11 +7,12 @@ export class EmailAPI extends TestAPI {
super(config)
}
- sendEmail = (purpose: string) => {
+ sendEmail = (purpose: string, attachments?: EmailAttachment[]) => {
return this.request
.post(`/api/global/email/send`)
.send({
email: "test@example.com",
+ attachments,
purpose,
tenantId: this.config.getTenantId(),
userId: this.config.user?._id!,
diff --git a/packages/worker/src/tests/jestEnv.ts b/packages/worker/src/tests/jestEnv.ts
index c044e0817f..0e4d1851b6 100644
--- a/packages/worker/src/tests/jestEnv.ts
+++ b/packages/worker/src/tests/jestEnv.ts
@@ -4,8 +4,8 @@ process.env.JWT_SECRET = "test-jwtsecret"
process.env.LOG_LEVEL = process.env.LOG_LEVEL || "error"
process.env.MULTI_TENANCY = "1"
process.env.MINIO_URL = "http://localhost"
-process.env.MINIO_ACCESS_KEY = "test"
-process.env.MINIO_SECRET_KEY = "test"
+process.env.MINIO_ACCESS_KEY = "budibase"
+process.env.MINIO_SECRET_KEY = "budibase"
process.env.PLATFORM_URL = "http://localhost:10000"
process.env.INTERNAL_API_KEY = "tet"
process.env.DISABLE_ACCOUNT_PORTAL = "0"
diff --git a/packages/worker/src/tests/structures/configs.ts b/packages/worker/src/tests/structures/configs.ts
index 8f13058dbe..bac94a4154 100644
--- a/packages/worker/src/tests/structures/configs.ts
+++ b/packages/worker/src/tests/structures/configs.ts
@@ -62,8 +62,8 @@ export function smtpEthereal(): SMTPConfig {
from: "testfrom@example.com",
secure: false,
auth: {
- user: "wyatt.zulauf29@ethereal.email",
- pass: "tEwDtHBWWxusVWAPfa",
+ user: "mortimer.leuschke@ethereal.email",
+ pass: "5hSjsPbzRv7gEUsfzx",
},
connectionTimeout: 1000, // must be less than the jest default of 5000
},
diff --git a/packages/worker/src/utilities/email.ts b/packages/worker/src/utilities/email.ts
index 27f2876eda..db9a635356 100644
--- a/packages/worker/src/utilities/email.ts
+++ b/packages/worker/src/utilities/email.ts
@@ -4,8 +4,10 @@ import { getTemplateByPurpose, EmailTemplates } from "../constants/templates"
import { getSettingsTemplateContext } from "./templates"
import { processString } from "@budibase/string-templates"
import { User, SendEmailOpts, SMTPInnerConfig } from "@budibase/types"
-import { configs, cache } from "@budibase/backend-core"
+import { configs, cache, objectStore } from "@budibase/backend-core"
import ical from "ical-generator"
+import fetch from "node-fetch"
+import path from "path"
const nodemailer = require("nodemailer")
@@ -162,6 +164,42 @@ export async function sendEmail(
contents: opts?.contents,
}),
}
+ if (opts?.attachments) {
+ const attachments = await Promise.all(
+ opts.attachments?.map(async attachment => {
+ const isFullyFormedUrl =
+ attachment.url.startsWith("http://") ||
+ attachment.url.startsWith("https://")
+ if (isFullyFormedUrl) {
+ const response = await fetch(attachment.url)
+ if (!response.ok) {
+ throw new Error(`unexpected response ${response.statusText}`)
+ }
+ const fallbackFilename = path.basename(
+ new URL(attachment.url).pathname
+ )
+ return {
+ filename: attachment.filename || fallbackFilename,
+ content: response?.body,
+ }
+ } else {
+ const url = attachment.url
+ const result = objectStore.extractBucketAndPath(url)
+ if (result === null) {
+ throw new Error("Invalid signed URL")
+ }
+ const { bucket, path } = result
+ const readStream = await objectStore.getReadStream(bucket, path)
+ const fallbackFilename = path.split("/").pop() || ""
+ return {
+ filename: attachment.filename || fallbackFilename,
+ content: readStream,
+ }
+ }
+ })
+ )
+ message = { ...message, attachments }
+ }
message = {
...message,
diff --git a/yarn.lock b/yarn.lock
index ce39c89075..30b275c434 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6147,6 +6147,13 @@
dependencies:
"@types/webidl-conversions" "*"
+"@types/xml2js@^0.4.14":
+ version "0.4.14"
+ resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.14.tgz#5d462a2a7330345e2309c6b549a183a376de8f9a"
+ integrity sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==
+ dependencies:
+ "@types/node" "*"
+
"@types/yargs-parser@*":
version "21.0.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b"
@@ -7607,6 +7614,16 @@ bl@^4.0.3, bl@^4.1.0:
inherits "^2.0.4"
readable-stream "^3.4.0"
+bl@^6.0.12:
+ version "6.0.12"
+ resolved "https://registry.yarnpkg.com/bl/-/bl-6.0.12.tgz#77c35b96e13aeff028496c798b75389ddee9c7f8"
+ integrity sha512-EnEYHilP93oaOa2MnmNEjAcovPS3JlQZOyzGXi3EyEpPhm9qWvdDp7BmAVEVusGzp8LlwQK56Av+OkDoRjzE0w==
+ dependencies:
+ "@types/readable-stream" "^4.0.0"
+ buffer "^6.0.3"
+ inherits "^2.0.4"
+ readable-stream "^4.2.0"
+
bl@^6.0.3:
version "6.0.9"
resolved "https://registry.yarnpkg.com/bl/-/bl-6.0.9.tgz#df8fcb2ef7be2e5ee8f65afa493502914e0d816f"
@@ -8781,7 +8798,7 @@ consolidate@^0.16.0:
dependencies:
bluebird "^3.7.2"
-content-disposition@^0.5.2, content-disposition@^0.5.3, content-disposition@~0.5.2:
+content-disposition@^0.5.2, content-disposition@^0.5.3, content-disposition@^0.5.4, content-disposition@~0.5.2:
version "0.5.4"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
From b080821f295c2e3c77922a54972e0d4e1c2494d8 Mon Sep 17 00:00:00 2001
From: Peter Clement
Date: Tue, 23 Apr 2024 10:31:18 +0100
Subject: [PATCH 062/338] Update helm charts to account for temp bucket
(#13544)
* update helm charts to account for temp bucket
* typo
* move location of vars
---
charts/budibase/templates/app-service-deployment.yaml | 2 ++
.../templates/automation-worker-service-deployment.yaml | 2 ++
charts/budibase/templates/worker-service-deployment.yaml | 2 ++
charts/budibase/values.yaml | 3 +++
4 files changed, 9 insertions(+)
diff --git a/charts/budibase/templates/app-service-deployment.yaml b/charts/budibase/templates/app-service-deployment.yaml
index b380908dd1..ed7166ec5d 100644
--- a/charts/budibase/templates/app-service-deployment.yaml
+++ b/charts/budibase/templates/app-service-deployment.yaml
@@ -106,6 +106,8 @@ spec:
value: {{ .Values.services.objectStore.globalBucketName | quote }}
- name: BACKUPS_BUCKET_NAME
value: {{ .Values.services.objectStore.backupsBucketName | quote }}
+ - name: TEMP_BUCKET_NAME
+ value: {{ .Values.globals.tempBucketName | quote }}
- name: PORT
value: {{ .Values.services.apps.port | quote }}
{{ if .Values.services.worker.publicApiRateLimitPerSecond }}
diff --git a/charts/budibase/templates/automation-worker-service-deployment.yaml b/charts/budibase/templates/automation-worker-service-deployment.yaml
index 51fa9ee4bb..3c6f94ae9e 100644
--- a/charts/budibase/templates/automation-worker-service-deployment.yaml
+++ b/charts/budibase/templates/automation-worker-service-deployment.yaml
@@ -107,6 +107,8 @@ spec:
value: {{ .Values.services.objectStore.globalBucketName | quote }}
- name: BACKUPS_BUCKET_NAME
value: {{ .Values.services.objectStore.backupsBucketName | quote }}
+ - name: TEMP_BUCKET_NAME
+ value: {{ .Values.globals.tempBucketName | quote }}
- name: PORT
value: {{ .Values.services.automationWorkers.port | quote }}
{{ if .Values.services.worker.publicApiRateLimitPerSecond }}
diff --git a/charts/budibase/templates/worker-service-deployment.yaml b/charts/budibase/templates/worker-service-deployment.yaml
index e37b2bc0e4..66a9bb6c14 100644
--- a/charts/budibase/templates/worker-service-deployment.yaml
+++ b/charts/budibase/templates/worker-service-deployment.yaml
@@ -106,6 +106,8 @@ spec:
value: {{ .Values.services.objectStore.globalBucketName | quote }}
- name: BACKUPS_BUCKET_NAME
value: {{ .Values.services.objectStore.backupsBucketName | quote }}
+ - name: TEMP_BUCKET_NAME
+ value: {{ .Values.globals.tempBucketName | quote }}
- name: PORT
value: {{ .Values.services.worker.port | quote }}
- name: MULTI_TENANCY
diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml
index 9ace768625..27037cdaa8 100644
--- a/charts/budibase/values.yaml
+++ b/charts/budibase/values.yaml
@@ -121,6 +121,9 @@ globals:
# to the old value for the duration of the rotation.
jwtSecretFallback: ""
+ ## -- If using S3 the bucket name to be used for storing temporary files
+ tempBucketName: ""
+
smtp:
# -- Whether to enable SMTP or not.
enabled: false
From 129f2c5bb965e6c5387c6a550dc97dd6d1342139 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Tue, 23 Apr 2024 11:52:10 +0100
Subject: [PATCH 063/338] Remove 24 hour time setting from datetime fields
---
packages/client/manifest.json | 6 ------
1 file changed, 6 deletions(-)
diff --git a/packages/client/manifest.json b/packages/client/manifest.json
index 40abc7a9a0..a7d4b57f1a 100644
--- a/packages/client/manifest.json
+++ b/packages/client/manifest.json
@@ -3869,12 +3869,6 @@
"key": "timeOnly",
"defaultValue": false
},
- {
- "type": "boolean",
- "label": "24-hour time",
- "key": "time24hr",
- "defaultValue": false
- },
{
"type": "boolean",
"label": "Ignore time zones",
From 2e46287b7e77f3ac059a31d05b5bc0e4716f3df5 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Tue, 23 Apr 2024 11:52:24 +0100
Subject: [PATCH 064/338] Remove testing code
---
.../[componentId]/new/_components/componentStructure.json | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json
index 494b2736d7..03e22acd4d 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json
@@ -68,7 +68,6 @@
"jsonfield",
"relationshipfield",
"datetimefield",
- "newdatetimefield",
"multifieldselect",
"s3upload",
"codescanner",
From 4f1220dd26bf2e246c7316923092e4086e24e4aa Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Tue, 23 Apr 2024 11:54:44 +0100
Subject: [PATCH 065/338] Restore client date range picker component
---
.../src/components/app/DateRangePicker.svelte | 76 +++++++++++++++++++
packages/client/src/components/app/index.js | 1 +
2 files changed, 77 insertions(+)
create mode 100644 packages/client/src/components/app/DateRangePicker.svelte
diff --git a/packages/client/src/components/app/DateRangePicker.svelte b/packages/client/src/components/app/DateRangePicker.svelte
new file mode 100644
index 0000000000..8131f3bd89
--- /dev/null
+++ b/packages/client/src/components/app/DateRangePicker.svelte
@@ -0,0 +1,76 @@
+
+
+
+ (value = e.detail)}
+ />
+
diff --git a/packages/client/src/components/app/index.js b/packages/client/src/components/app/index.js
index 1c5722eb40..e23e19704c 100644
--- a/packages/client/src/components/app/index.js
+++ b/packages/client/src/components/app/index.js
@@ -29,6 +29,7 @@ export { default as image } from "./Image.svelte"
export { default as embed } from "./Embed.svelte"
export { default as icon } from "./Icon.svelte"
export { default as backgroundimage } from "./BackgroundImage.svelte"
+export { default as daterangepicker } from "./DateRangePicker.svelte"
export { default as cardstat } from "./CardStat.svelte"
export { default as spectrumcard } from "./SpectrumCard.svelte"
export { default as tag } from "./Tag.svelte"
From f0102286295bb5f788d8db42ebbdf827b7ec5026 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Tue, 23 Apr 2024 11:59:14 +0100
Subject: [PATCH 066/338] Respect app custom colour choices where possible
---
packages/bbui/src/Form/Core/DatePicker/Calendar.svelte | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/packages/bbui/src/Form/Core/DatePicker/Calendar.svelte b/packages/bbui/src/Form/Core/DatePicker/Calendar.svelte
index b073ac78f9..83692ff898 100644
--- a/packages/bbui/src/Form/Core/DatePicker/Calendar.svelte
+++ b/packages/bbui/src/Form/Core/DatePicker/Calendar.svelte
@@ -198,10 +198,13 @@
}
.spectrum-Calendar-date.is-today.is-selected,
.spectrum-Calendar-date.is-today.is-selected::before {
- border-color: var(--spectrum-global-color-blue-700);
+ border-color: var(
+ --primaryColorHover,
+ var(--spectrum-global-color-blue-700)
+ );
}
.spectrum-Calendar-date.is-selected:not(.is-range-selection) {
- background: var(--spectrum-global-color-blue-400);
+ background: var(--primaryColor, var(--spectrum-global-color-blue-400));
}
.spectrum-Calendar tr {
box-sizing: content-box;
From a99c15fb26ec1d48c3fe55deff990083c77a0e2a Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Tue, 23 Apr 2024 12:05:30 +0100
Subject: [PATCH 067/338] Fix wrong class name being used to ignore grid
keypresses when editing dates in modals
---
.../src/components/grid/overlays/KeyboardManager.svelte | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte b/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte
index 4d2ff87b5c..cef4144833 100644
--- a/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte
+++ b/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte
@@ -20,7 +20,7 @@
const ignoredOriginSelectors = [
".spectrum-Modal",
- ".spectrum-Calendar",
+ ".date-time-popover",
"#builder-side-panel-container",
"[data-grid-ignore]",
]
From 203acc0efdd1df43e2d5f05e523c81d86213b4ba Mon Sep 17 00:00:00 2001
From: Dean
Date: Tue, 23 Apr 2024 12:40:57 +0100
Subject: [PATCH 068/338] Fix for regression in api initialisation
---
packages/frontend-core/src/components/FilterUsers.svelte | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/frontend-core/src/components/FilterUsers.svelte b/packages/frontend-core/src/components/FilterUsers.svelte
index 4613b8c40f..489426df1e 100644
--- a/packages/frontend-core/src/components/FilterUsers.svelte
+++ b/packages/frontend-core/src/components/FilterUsers.svelte
@@ -1,7 +1,9 @@
+
+
+ {#if showCalendar}
+ handleChange(e.detail)}
+ bind:this={calendar}
+ />
+ {/if}
+
+
+
+
diff --git a/packages/bbui/src/Form/Core/DatePicker/NumberInput.svelte b/packages/bbui/src/Form/Core/DatePicker/NumberInput.svelte
index 91dd95ff5f..dc4886d28d 100644
--- a/packages/bbui/src/Form/Core/DatePicker/NumberInput.svelte
+++ b/packages/bbui/src/Form/Core/DatePicker/NumberInput.svelte
@@ -33,7 +33,7 @@
font-weight: bold;
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
- box-sizing: content-box;
+ box-sizing: content-box !important;
}
input:focus,
input:hover {
diff --git a/packages/bbui/src/Form/Core/DatePicker/TimePicker.svelte b/packages/bbui/src/Form/Core/DatePicker/TimePicker.svelte
index adf2a5e87a..047e5a4f08 100644
--- a/packages/bbui/src/Form/Core/DatePicker/TimePicker.svelte
+++ b/packages/bbui/src/Form/Core/DatePicker/TimePicker.svelte
@@ -2,18 +2,20 @@
import { cleanInput } from "./utils"
import dayjs from "dayjs"
import NumberInput from "./NumberInput.svelte"
+ import { createEventDispatcher } from "svelte"
export let value
- export let onChange
+
+ const dispatch = createEventDispatcher()
$: displayValue = value || dayjs()
const handleHourChange = e => {
- onChange(displayValue.hour(parseInt(e.target.value)))
+ dispatch("change", displayValue.hour(parseInt(e.target.value)))
}
const handleMinuteChange = e => {
- onChange(displayValue.minute(parseInt(e.target.value)))
+ dispatch("change", displayValue.minute(parseInt(e.target.value)))
}
const cleanHour = cleanInput({ max: 23, pad: 2, fallback: "00" })
@@ -51,7 +53,7 @@
.time-picker span {
font-weight: bold;
font-size: 18px;
- z-index: -1;
+ z-index: 0;
margin-bottom: 1px;
}
diff --git a/packages/bbui/src/Form/Core/index.js b/packages/bbui/src/Form/Core/index.js
index 533a1956c5..7117b90081 100644
--- a/packages/bbui/src/Form/Core/index.js
+++ b/packages/bbui/src/Form/Core/index.js
@@ -9,6 +9,7 @@ export { default as CoreCombobox } from "./Combobox.svelte"
export { default as CoreSwitch } from "./Switch.svelte"
export { default as CoreSearch } from "./Search.svelte"
export { default as CoreDatePicker } from "./DatePicker/DatePicker.svelte"
+export { default as CoreDatePickerPopoverContents } from "./DatePicker/DatePickerPopoverContents.svelte"
export { default as CoreDateRangePicker } from "./DateRangePicker.svelte"
export { default as CoreDropzone } from "./Dropzone.svelte"
export { default as CoreStepper } from "./Stepper.svelte"
diff --git a/packages/bbui/src/helpers.js b/packages/bbui/src/helpers.js
index c98ebad2c7..4448527fea 100644
--- a/packages/bbui/src/helpers.js
+++ b/packages/bbui/src/helpers.js
@@ -117,7 +117,9 @@ export const copyToClipboard = value => {
})
}
-export const parseDate = (value, { dateOnly } = {}) => {
+// Parsed a date value. This is usually an ISO string, but can be a
+// bunch of different formats and shapes depending on schema flags.
+export const parseDate = (value, { enableTime = true }) => {
// If empty then invalid
if (!value) {
return null
@@ -131,7 +133,7 @@ export const parseDate = (value, { dateOnly } = {}) => {
}
// If date only, check for cases where we received a UTC string
- else if (dateOnly && value.endsWith("Z")) {
+ else if (!enableTime && value.endsWith("Z")) {
value = value.split("Z")[0]
}
}
@@ -148,7 +150,42 @@ export const parseDate = (value, { dateOnly } = {}) => {
return dayjs(Math.floor(parsedDate.valueOf() / 1000) * 1000)
}
-export const getDateDisplayValue = (value, { enableTime, timeOnly }) => {
+// Stringifies a dayjs object to create an ISO string that respects the various
+// schema flags
+export const stringifyDate = (
+ value,
+ { enableTime = true, timeOnly = false, ignoreTimezones = false }
+) => {
+ if (!value) {
+ return null
+ }
+
+ // Time only fields always ignore timezones, otherwise they make no sense.
+ // For non-timezone-aware fields, create an ISO 8601 timestamp of the exact
+ // time picked, without timezone
+ const offsetForTimezone = (enableTime && ignoreTimezones) || timeOnly
+ if (offsetForTimezone) {
+ // Ensure we use the correct offset for the date
+ const referenceDate = timeOnly ? new Date() : value.toDate()
+ const offset = referenceDate.getTimezoneOffset() * 60000
+ return new Date(value.valueOf() - offset).toISOString().slice(0, -1)
+ }
+
+ // For date-only fields, construct a manual timestamp string without a time
+ // or time zone
+ else if (!enableTime) {
+ const year = value.year()
+ const month = `${value.month() + 1}`.padStart(2, "0")
+ const day = `${value.date()}`.padStart(2, "0")
+ return `${year}-${month}-${day}T00:00:00.000`
+ }
+}
+
+// Formats a dayjs date according to schema flags
+export const getDateDisplayValue = (
+ value,
+ { enableTime = true, timeOnly = false }
+) => {
if (!value?.isValid()) {
return ""
}
diff --git a/packages/frontend-core/src/components/grid/cells/DateCell.svelte b/packages/frontend-core/src/components/grid/cells/DateCell.svelte
index 019a0db9c2..394b6e1f43 100644
--- a/packages/frontend-core/src/components/grid/cells/DateCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/DateCell.svelte
@@ -1,6 +1,13 @@
-
+
+
+
{displayValue}
@@ -55,17 +107,14 @@
{/if}
-{#if editable}
-
-
+ onChange(e.detail)}
- enableTime={!dateOnly}
+ {enableTime}
{timeOnly}
- ignoreTimezones={schema.ignoreTimezones}
- bind:api={datePickerAPI}
- on:open={() => (isOpen = true)}
- on:close={() => (isOpen = false)}
+ {ignoreTimezones}
useKeyboardShortcuts={false}
/>
@@ -80,6 +129,10 @@
align-items: center;
flex: 1 1 auto;
gap: var(--cell-spacing);
+ user-select: none;
+ }
+ .container.editable:hover {
+ cursor: pointer;
}
.value {
flex: 1 1 auto;
@@ -92,9 +145,10 @@
}
.picker {
position: absolute;
- opacity: 0;
- }
- .picker :global(.spectrum-Textfield-input) {
- width: 100%;
+ top: 100%;
+ left: -1px;
+ background: var(--grid-background-alt);
+ border: var(--cell-border);
+ border-radius: 2px;
}
diff --git a/packages/frontend-core/src/components/grid/cells/LongFormCell.svelte b/packages/frontend-core/src/components/grid/cells/LongFormCell.svelte
index 0299e66e2f..b3eab24fa1 100644
--- a/packages/frontend-core/src/components/grid/cells/LongFormCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/LongFormCell.svelte
@@ -103,8 +103,8 @@
position: absolute;
top: 0;
left: 0;
- width: calc(100% + var(--max-cell-render-width-overflow));
- height: calc(var(--row-height) + var(--max-cell-render-height));
+ width: calc(100% + var(--max-cell-render-verflow));
+ height: calc(var(--row-height) + var(--max-cell-render-overflow));
z-index: 1;
border-radius: 2px;
resize: none;
diff --git a/packages/frontend-core/src/components/grid/cells/OptionsCell.svelte b/packages/frontend-core/src/components/grid/cells/OptionsCell.svelte
index ede9bd1cff..158451c930 100644
--- a/packages/frontend-core/src/components/grid/cells/OptionsCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/OptionsCell.svelte
@@ -23,7 +23,7 @@
$: values = Array.isArray(value) ? value : [value].filter(x => x != null)
$: {
// Close when deselected
- if (!focused) {
+ if (!focused && isOpen) {
close()
}
}
@@ -219,7 +219,7 @@
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
- max-height: var(--max-cell-render-height);
+ max-height: var(--max-cell-render-overflow);
overflow-y: auto;
border: var(--cell-border);
box-shadow: 0 0 20px -4px rgba(0, 0, 0, 0.15);
diff --git a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte
index bf1fe92ef0..a52dd9d53c 100644
--- a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte
@@ -35,7 +35,7 @@
$: lookupMap = buildLookupMap(value, isOpen)
$: debouncedSearch(searchString)
$: {
- if (!focused) {
+ if (!focused && isOpen) {
close()
}
}
@@ -451,7 +451,7 @@
left: 0;
width: 100%;
max-height: calc(
- var(--max-cell-render-height) + var(--row-height) - var(--values-height)
+ var(--max-cell-render-overflow) + var(--row-height) - var(--values-height)
);
background: var(--grid-background-alt);
border: var(--cell-border);
diff --git a/packages/frontend-core/src/components/grid/layout/Grid.svelte b/packages/frontend-core/src/components/grid/layout/Grid.svelte
index b6c686fd62..468f49bdec 100644
--- a/packages/frontend-core/src/components/grid/layout/Grid.svelte
+++ b/packages/frontend-core/src/components/grid/layout/Grid.svelte
@@ -22,8 +22,7 @@
import NewRow from "./NewRow.svelte"
import { createGridWebsocket } from "../lib/websocket"
import {
- MaxCellRenderHeight,
- MaxCellRenderWidthOverflow,
+ MaxCellRenderOverflow,
GutterWidth,
DefaultRowHeight,
} from "../lib/constants"
@@ -78,6 +77,7 @@
contentLines,
gridFocused,
error,
+ focusedCellId,
} = context
// Keep config store up to date with props
@@ -129,7 +129,7 @@
class:quiet
on:mouseenter={() => gridFocused.set(true)}
on:mouseleave={() => gridFocused.set(false)}
- style="--row-height:{$rowHeight}px; --default-row-height:{DefaultRowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-height:{MaxCellRenderHeight}px; --max-cell-render-width-overflow:{MaxCellRenderWidthOverflow}px; --content-lines:{$contentLines};"
+ style="--row-height:{$rowHeight}px; --default-row-height:{DefaultRowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-overflow:{MaxCellRenderOverflow}px; --content-lines:{$contentLines};"
>
{#if showControls}
diff --git a/packages/frontend-core/src/components/grid/lib/constants.js b/packages/frontend-core/src/components/grid/lib/constants.js
index a6e6723463..37d829873d 100644
--- a/packages/frontend-core/src/components/grid/lib/constants.js
+++ b/packages/frontend-core/src/components/grid/lib/constants.js
@@ -1,5 +1,4 @@
export const Padding = 246
-export const MaxCellRenderHeight = 222
export const ScrollBarSize = 8
export const GutterWidth = 72
export const DefaultColumnWidth = 200
@@ -12,4 +11,4 @@ export const NewRowID = "new"
export const BlankRowID = "blank"
export const RowPageSize = 100
export const FocusedCellMinOffset = 48
-export const MaxCellRenderWidthOverflow = Padding - 3 * ScrollBarSize
+export const MaxCellRenderOverflow = Padding - 3 * ScrollBarSize
diff --git a/packages/frontend-core/src/components/grid/stores/viewport.js b/packages/frontend-core/src/components/grid/stores/viewport.js
index 8df8acd0f4..96a5a954ee 100644
--- a/packages/frontend-core/src/components/grid/stores/viewport.js
+++ b/packages/frontend-core/src/components/grid/stores/viewport.js
@@ -1,7 +1,6 @@
import { derived } from "svelte/store"
import {
- MaxCellRenderHeight,
- MaxCellRenderWidthOverflow,
+ MaxCellRenderOverflow,
MinColumnWidth,
ScrollBarSize,
} from "../lib/constants"
@@ -95,11 +94,11 @@ export const deriveStores = context => {
// Compute the last row index with space to render popovers below it
const minBottom =
- $height - ScrollBarSize * 3 - MaxCellRenderHeight + offset
+ $height - ScrollBarSize * 3 - MaxCellRenderOverflow + offset
const lastIdx = Math.floor(minBottom / $rowHeight)
// Compute the first row index with space to render popovers above it
- const minTop = MaxCellRenderHeight + offset
+ const minTop = MaxCellRenderOverflow + offset
const firstIdx = Math.ceil(minTop / $rowHeight)
// Use the greater of the two indices so that we prefer content below,
@@ -117,7 +116,7 @@ export const deriveStores = context => {
let inversionIdx = $visibleColumns.length
for (let i = $visibleColumns.length - 1; i >= 0; i--, inversionIdx--) {
const rightEdge = $visibleColumns[i].left + $visibleColumns[i].width
- if (rightEdge + MaxCellRenderWidthOverflow <= cutoff) {
+ if (rightEdge + MaxCellRenderOverflow <= cutoff) {
break
}
}
From c8446fa5a6d963caeaac71c3cd42749a96f18113 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 23 Apr 2024 22:12:24 +0000
Subject: [PATCH 073/338] Bump mysql2 from 3.5.2 to 3.9.7 in /packages/server
Bumps [mysql2](https://github.com/sidorares/node-mysql2) from 3.5.2 to 3.9.7.
- [Release notes](https://github.com/sidorares/node-mysql2/releases)
- [Changelog](https://github.com/sidorares/node-mysql2/blob/master/Changelog.md)
- [Commits](https://github.com/sidorares/node-mysql2/compare/v3.5.2...v3.9.7)
---
updated-dependencies:
- dependency-name: mysql2
dependency-type: direct:production
...
Signed-off-by: dependabot[bot]
---
packages/server/package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/server/package.json b/packages/server/package.json
index b2ac4e7d43..5e25fd3fc5 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -98,7 +98,7 @@
"memorystream": "0.3.1",
"mongodb": "^6.3.0",
"mssql": "10.0.1",
- "mysql2": "3.5.2",
+ "mysql2": "3.9.7",
"node-fetch": "2.6.7",
"object-sizeof": "2.6.1",
"open": "8.4.0",
From cbf33adc0e4433356d5dde6f977da58e633d058f Mon Sep 17 00:00:00 2001
From: Gerard Burns
Date: Wed, 24 Apr 2024 08:05:29 +0100
Subject: [PATCH 074/338] fix
---
packages/builder/src/stores/builder/components.js | 3 +++
1 file changed, 3 insertions(+)
diff --git a/packages/builder/src/stores/builder/components.js b/packages/builder/src/stores/builder/components.js
index fe5f4e8a05..a2f5810d64 100644
--- a/packages/builder/src/stores/builder/components.js
+++ b/packages/builder/src/stores/builder/components.js
@@ -440,11 +440,14 @@ export class ComponentStore extends BudiStore {
return state
})
+ componentTreeNodesStore.makeNodeVisible(componentInstance._id)
+
// Log event
analytics.captureEvent(Events.COMPONENT_CREATED, {
name: componentInstance._component,
})
+
return componentInstance
}
From 46d8a0698190a24cab9525dd119de5dea4e15261 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Wed, 24 Apr 2024 08:52:39 +0100
Subject: [PATCH 075/338] Update grid min size to accomodate new date picker
---
.../src/components/app/GridBlock.svelte | 4 +--
.../src/components/grid/cells/DateCell.svelte | 25 ++++++++++---------
.../src/components/grid/layout/Grid.svelte | 5 +++-
.../src/components/grid/lib/constants.js | 5 ++--
4 files changed, 22 insertions(+), 17 deletions(-)
diff --git a/packages/client/src/components/app/GridBlock.svelte b/packages/client/src/components/app/GridBlock.svelte
index 55e5a2bd66..deefe6726a 100644
--- a/packages/client/src/components/app/GridBlock.svelte
+++ b/packages/client/src/components/app/GridBlock.svelte
@@ -149,8 +149,8 @@
border: 1px solid var(--spectrum-global-color-gray-300);
border-radius: 4px;
overflow: hidden;
- min-height: 230px;
- height: 410px;
+ /* min-height: 230px;*/
+ /* height: 410px;*/
}
div.in-builder :global(*) {
pointer-events: none;
diff --git a/packages/frontend-core/src/components/grid/cells/DateCell.svelte b/packages/frontend-core/src/components/grid/cells/DateCell.svelte
index 394b6e1f43..04d5841f49 100644
--- a/packages/frontend-core/src/components/grid/cells/DateCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/DateCell.svelte
@@ -48,6 +48,10 @@
const close = () => {
isOpen = false
+
+ // Only save the changed value when closing. If the value is unchanged then
+ // this is handled upstream and no action is taken.
+ onChange(value)
}
const onKeyDown = e => {
@@ -70,22 +74,19 @@
}
const changeDate = (quantity, unit) => {
+ let newValue
if (!value) {
- value = dayjs()
+ newValue = dayjs()
} else {
- value = dayjs(value).add(quantity, unit)
+ newValue = dayjs(value).add(quantity, unit)
}
- debouncedOnChange(
- Helpers.stringifyDate(value, {
- enableTime,
- timeOnly,
- ignoreTimezones,
- })
- )
+ value = Helpers.stringifyDate(newValue, {
+ enableTime,
+ timeOnly,
+ ignoreTimezones,
+ })
}
- const debouncedOnChange = debounce(onChange, 250)
-
onMount(() => {
api = {
onKeyDown,
@@ -111,7 +112,7 @@
onChange(e.detail)}
+ on:change={e => (value = e.detail)}
{enableTime}
{timeOnly}
{ignoreTimezones}
diff --git a/packages/frontend-core/src/components/grid/layout/Grid.svelte b/packages/frontend-core/src/components/grid/layout/Grid.svelte
index 468f49bdec..d62715083a 100644
--- a/packages/frontend-core/src/components/grid/layout/Grid.svelte
+++ b/packages/frontend-core/src/components/grid/layout/Grid.svelte
@@ -25,6 +25,7 @@
MaxCellRenderOverflow,
GutterWidth,
DefaultRowHeight,
+ MinHeight,
} from "../lib/constants"
export let API = null
@@ -129,7 +130,8 @@
class:quiet
on:mouseenter={() => gridFocused.set(true)}
on:mouseleave={() => gridFocused.set(false)}
- style="--row-height:{$rowHeight}px; --default-row-height:{DefaultRowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-overflow:{MaxCellRenderOverflow}px; --content-lines:{$contentLines};"
+ style="--row-height:{$rowHeight}px; --default-row-height:{DefaultRowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-overflow:{MaxCellRenderOverflow}px; --content-lines:{$contentLines}; --min-height:{MinHeight +
+ $rowHeight}px;"
>
{#if showControls}
@@ -219,6 +221,7 @@
position: relative;
overflow: hidden;
background: var(--grid-background);
+ min-height: var(--min-height);
}
.grid,
.grid :global(*) {
diff --git a/packages/frontend-core/src/components/grid/lib/constants.js b/packages/frontend-core/src/components/grid/lib/constants.js
index 37d829873d..1a01c9f1bd 100644
--- a/packages/frontend-core/src/components/grid/lib/constants.js
+++ b/packages/frontend-core/src/components/grid/lib/constants.js
@@ -1,4 +1,4 @@
-export const Padding = 246
+export const Padding = 400
export const ScrollBarSize = 8
export const GutterWidth = 72
export const DefaultColumnWidth = 200
@@ -11,4 +11,5 @@ export const NewRowID = "new"
export const BlankRowID = "blank"
export const RowPageSize = 100
export const FocusedCellMinOffset = 48
-export const MaxCellRenderOverflow = Padding - 3 * ScrollBarSize
+export const MaxCellRenderOverflow = 222
+export const MinHeight = Padding + SmallRowHeight
From 8ab2ca41d5d5a0d0aefabeec0c6f062699c7ab06 Mon Sep 17 00:00:00 2001
From: Dean
Date: Wed, 24 Apr 2024 09:54:10 +0100
Subject: [PATCH 076/338] Added array parsing for search query config
---
.../src/api/controllers/row/utils/utils.ts | 22 +++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/packages/server/src/api/controllers/row/utils/utils.ts b/packages/server/src/api/controllers/row/utils/utils.ts
index 7eb4901ded..962756ff87 100644
--- a/packages/server/src/api/controllers/row/utils/utils.ts
+++ b/packages/server/src/api/controllers/row/utils/utils.ts
@@ -190,6 +190,23 @@ export function isUserMetadataTable(tableId: string) {
return tableId === InternalTables.USER_METADATA
}
+export async function enrichArrayContext(
+ fields: any[],
+ inputs = {},
+ helpers = true
+): Promise {
+ const map: Record = {}
+ for (let index in fields) {
+ map[index] = fields[index]
+ }
+ const output = await enrichSearchContext(map, inputs, helpers)
+ const outputArray: any[] = []
+ for (let [key, value] of Object.entries(output)) {
+ outputArray[parseInt(key)] = value
+ }
+ return outputArray
+}
+
export async function enrichSearchContext(
fields: Record,
inputs = {},
@@ -200,6 +217,11 @@ export async function enrichSearchContext(
return enrichedQuery
}
const parameters = { ...inputs }
+
+ if (Array.isArray(fields)) {
+ return enrichArrayContext(fields, inputs, helpers)
+ }
+
// enrich the fields with dynamic parameters
for (let key of Object.keys(fields)) {
if (fields[key] == null) {
From 2fc96549aa893bceef425af11237cc0914021f63 Mon Sep 17 00:00:00 2001
From: Budibase Staging Release Bot <>
Date: Wed, 24 Apr 2024 12:05:06 +0000
Subject: [PATCH 077/338] Bump version to 2.23.12
---
lerna.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lerna.json b/lerna.json
index 728cddc194..94631c6820 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "version": "2.23.11",
+ "version": "2.23.12",
"npmClient": "yarn",
"packages": [
"packages/*",
From 4ce7162bb09ef865f641ca9fc453cd21cd9e4552 Mon Sep 17 00:00:00 2001
From: Sam Rose
Date: Wed, 24 Apr 2024 15:20:10 +0100
Subject: [PATCH 078/338] wip
---
.../src/api/routes/tests/search.spec.ts | 34 ++++++++++++++++---
1 file changed, 29 insertions(+), 5 deletions(-)
diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts
index 698ea0c10b..cc3bde3a07 100644
--- a/packages/server/src/api/routes/tests/search.spec.ts
+++ b/packages/server/src/api/routes/tests/search.spec.ts
@@ -3,6 +3,7 @@ import { DatabaseName, getDatasource } from "../../../integrations/tests/utils"
import * as setup from "./utilities"
import {
+ AutoFieldSubType,
Datasource,
EmptyFilterOption,
FieldType,
@@ -18,12 +19,12 @@ import _ from "lodash"
jest.unmock("mssql")
describe.each([
- ["internal", undefined],
- ["internal-sqs", undefined],
+ // ["internal", undefined],
+ // ["internal-sqs", undefined],
[DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
- [DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)],
- [DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)],
- [DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)],
+ // [DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)],
+ // [DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)],
+ // [DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)],
])("/api/:sourceId/search (%s)", (name, dsProvider) => {
const isSqs = name === "internal-sqs"
const isInternal = name === "internal"
@@ -675,4 +676,27 @@ describe.each([
}).toContainExactly([{ num: SMALL }, { num: MEDIUM }]))
})
})
+
+ isInternal &&
+ describe.only("auto", () => {
+ beforeAll(async () => {
+ await createTable({
+ auto: {
+ name: "auto",
+ type: FieldType.AUTO,
+ autocolumn: true,
+ subtype: AutoFieldSubType.AUTO_ID,
+ },
+ })
+ await createRows([{}, {}, {}])
+ })
+
+ describe("equal", () => {
+ it("successfully finds a row", () =>
+ expectQuery({ equal: { auto: 1 } }).toContainExactly([{ auto: 1 }]))
+
+ it("fails to find nonexistent row", () =>
+ expectQuery({ equal: { auto: 0 } }).toFindNothing())
+ })
+ })
})
From 849253faba49fd79917780e49bcba9de85df81d8 Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Wed, 24 Apr 2024 15:37:47 +0100
Subject: [PATCH 079/338] Bringing back the old mechanism of returning the
client library through a pre-signed URL, rather than always serving through
the service.
---
.../backend-core/src/objectStore/buckets/app.ts | 17 +++++------------
1 file changed, 5 insertions(+), 12 deletions(-)
diff --git a/packages/backend-core/src/objectStore/buckets/app.ts b/packages/backend-core/src/objectStore/buckets/app.ts
index 43bc965c65..74fdd2e92a 100644
--- a/packages/backend-core/src/objectStore/buckets/app.ts
+++ b/packages/backend-core/src/objectStore/buckets/app.ts
@@ -13,23 +13,16 @@ export function clientLibraryPath(appId: string) {
* due to issues with the domain we were unable to continue doing this - keeping
* incase we are able to switch back to CDN path again in future.
*/
-export function clientLibraryCDNUrl(appId: string, version: string) {
+export function cloudClientLibraryUrl(appId: string, version: string) {
let file = clientLibraryPath(appId)
- if (env.CLOUDFRONT_CDN) {
- // append app version to bust the cache
- if (version) {
- file += `?v=${version}`
- }
- // don't need to use presigned for client with cloudfront
- // file is public
- return cloudfront.getUrl(file)
- } else {
- return objectStore.getPresignedUrl(env.APPS_BUCKET_NAME, file)
- }
+ return objectStore.getPresignedUrl(env.APPS_BUCKET_NAME, file)
}
export function clientLibraryUrl(appId: string, version: string) {
let tenantId, qsParams: { appId: string; version: string; tenantId?: string }
+ if (env.isProd() && !env.SELF_HOSTED) {
+ return cloudClientLibraryUrl(appId, version)
+ }
try {
tenantId = getTenantId()
} finally {
From 795991438f769fc2969d83feabed11b3af338494 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Wed, 24 Apr 2024 15:47:39 +0100
Subject: [PATCH 080/338] Use new grid popover for date cells to allow overflow
outside grid
---
packages/bbui/src/Popover/Popover.svelte | 3 +-
.../src/components/grid/cells/DataCell.svelte | 2 +
.../src/components/grid/cells/DateCell.svelte | 38 +++++++++++-------
.../src/components/grid/layout/Grid.svelte | 10 ++---
.../src/components/grid/layout/GridRow.svelte | 2 +
.../src/components/grid/lib/constants.js | 3 +-
.../grid/overlays/GridPopover.svelte | 40 +++++++++++++++++++
.../grid/overlays/PopoverOverlay.svelte | 9 +++++
.../grid/overlays/ScrollOverlay.svelte | 2 +
9 files changed, 84 insertions(+), 25 deletions(-)
create mode 100644 packages/frontend-core/src/components/grid/overlays/GridPopover.svelte
create mode 100644 packages/frontend-core/src/components/grid/overlays/PopoverOverlay.svelte
diff --git a/packages/bbui/src/Popover/Popover.svelte b/packages/bbui/src/Popover/Popover.svelte
index 263a0b6dc6..079aee32b5 100644
--- a/packages/bbui/src/Popover/Popover.svelte
+++ b/packages/bbui/src/Popover/Popover.svelte
@@ -116,12 +116,11 @@
min-width: var(--spectrum-global-dimension-size-2000);
border-color: var(--spectrum-global-color-gray-300);
overflow: auto;
- transition: opacity 260ms ease-out, transform 260ms ease-out;
+ transition: opacity 260ms ease-out;
}
.hidden {
opacity: 0;
pointer-events: none;
- transform: translateY(-20px);
}
.customZindex {
z-index: var(--customZindex) !important;
diff --git a/packages/frontend-core/src/components/grid/cells/DataCell.svelte b/packages/frontend-core/src/components/grid/cells/DataCell.svelte
index d8cff26b9d..33f74b116c 100644
--- a/packages/frontend-core/src/components/grid/cells/DataCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/DataCell.svelte
@@ -22,6 +22,7 @@
export let invertY = false
export let contentLines = 1
export let hidden = false
+ export let rand
const emptyError = writable(null)
@@ -96,6 +97,7 @@
{invertY}
{invertX}
{contentLines}
+ {rand}
/>
diff --git a/packages/frontend-core/src/components/grid/cells/DateCell.svelte b/packages/frontend-core/src/components/grid/cells/DateCell.svelte
index 04d5841f49..26f5653fd0 100644
--- a/packages/frontend-core/src/components/grid/cells/DateCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/DateCell.svelte
@@ -7,7 +7,7 @@
} from "@budibase/bbui"
import { onMount } from "svelte"
import dayjs from "dayjs"
- import { debounce } from "../../../utils/utils"
+ import GridPopover from "../overlays/GridPopover.svelte"
export let value
export let schema
@@ -15,8 +15,12 @@
export let focused = false
export let readonly = false
export let api
+ export let invertX = false
+ export let invertY = false
+ export let rand
let isOpen
+ let anchor
$: timeOnly = schema?.timeOnly
$: enableTime = !schema?.dateOnly
@@ -99,7 +103,12 @@
-
+
{displayValue}
@@ -109,16 +118,18 @@
{#if isOpen}
-
- (value = e.detail)}
- {enableTime}
- {timeOnly}
- {ignoreTimezones}
- useKeyboardShortcuts={false}
- />
-
+
+
+ (value = e.detail)}
+ {enableTime}
+ {timeOnly}
+ {ignoreTimezones}
+ useKeyboardShortcuts={false}
+ />
+
+
{/if}
`
+
+ const updateInitialOffsets = open => {
+ if (!open) {
+ return
+ }
+ initialOffsetX = $scroll.left
+ initialOffsetY = $scroll.top % $rowHeight
+ }
+
+
+
+{@html markup}
+
+
+
diff --git a/packages/frontend-core/src/components/grid/overlays/PopoverOverlay.svelte b/packages/frontend-core/src/components/grid/overlays/PopoverOverlay.svelte
new file mode 100644
index 0000000000..a03ba6d927
--- /dev/null
+++ b/packages/frontend-core/src/components/grid/overlays/PopoverOverlay.svelte
@@ -0,0 +1,9 @@
+
+
+
diff --git a/packages/frontend-core/src/components/grid/overlays/ScrollOverlay.svelte b/packages/frontend-core/src/components/grid/overlays/ScrollOverlay.svelte
index 43a64f3fbd..c256e0dc19 100644
--- a/packages/frontend-core/src/components/grid/overlays/ScrollOverlay.svelte
+++ b/packages/frontend-core/src/components/grid/overlays/ScrollOverlay.svelte
@@ -127,6 +127,7 @@
on:mousedown={startVDragging}
on:touchstart={startVDragging}
class:dragging={isDraggingV}
+ data-ignore-click-outside="true"
/>
{/if}
{#if $showHScrollbar}
@@ -137,6 +138,7 @@
on:mousedown={startHDragging}
on:touchstart={startHDragging}
class:dragging={isDraggingH}
+ data-ignore-click-outside="true"
/>
{/if}
From f76abb0ca93434b4047f544434430046812e62a7 Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Wed, 24 Apr 2024 15:59:11 +0100
Subject: [PATCH 081/338] Getting rid of linting error.
---
packages/backend-core/src/objectStore/buckets/app.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/backend-core/src/objectStore/buckets/app.ts b/packages/backend-core/src/objectStore/buckets/app.ts
index 74fdd2e92a..3ad575150e 100644
--- a/packages/backend-core/src/objectStore/buckets/app.ts
+++ b/packages/backend-core/src/objectStore/buckets/app.ts
@@ -13,7 +13,7 @@ export function clientLibraryPath(appId: string) {
* due to issues with the domain we were unable to continue doing this - keeping
* incase we are able to switch back to CDN path again in future.
*/
-export function cloudClientLibraryUrl(appId: string, version: string) {
+function cloudClientLibraryUrl(appId: string) {
let file = clientLibraryPath(appId)
return objectStore.getPresignedUrl(env.APPS_BUCKET_NAME, file)
}
@@ -21,7 +21,7 @@ export function cloudClientLibraryUrl(appId: string, version: string) {
export function clientLibraryUrl(appId: string, version: string) {
let tenantId, qsParams: { appId: string; version: string; tenantId?: string }
if (env.isProd() && !env.SELF_HOSTED) {
- return cloudClientLibraryUrl(appId, version)
+ return cloudClientLibraryUrl(appId)
}
try {
tenantId = getTenantId()
From 443be4cdab85c4a6d0083f3b16d34d6cdb86dde9 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Wed, 24 Apr 2024 16:28:44 +0100
Subject: [PATCH 082/338] More upgrades to grids to support new popovers and
use popovers for options cells
---
.../backend/DataTable/TableDataTable.svelte | 2 +-
.../src/components/grid/cells/DateCell.svelte | 33 ++++--------
.../components/grid/cells/OptionsCell.svelte | 51 ++++++++-----------
.../grid/layout/GridScrollWrapper.svelte | 4 ++
.../grid/overlays/GridPopover.svelte | 38 +++++---------
.../grid/overlays/ScrollOverlay.svelte | 10 ++--
.../src/components/grid/stores/reorder.js | 2 +-
7 files changed, 56 insertions(+), 84 deletions(-)
diff --git a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte
index 6a5cd2f282..fe393c5268 100644
--- a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte
+++ b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte
@@ -101,7 +101,7 @@
diff --git a/packages/frontend-core/src/components/grid/cells/OptionsCell.svelte b/packages/frontend-core/src/components/grid/cells/OptionsCell.svelte
index 158451c930..81882c867c 100644
--- a/packages/frontend-core/src/components/grid/cells/OptionsCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/OptionsCell.svelte
@@ -1,7 +1,8 @@
-
-{@html markup}
-
+ dispatch("close")}>
+
+
+
+
diff --git a/packages/frontend-core/src/components/grid/overlays/ScrollOverlay.svelte b/packages/frontend-core/src/components/grid/overlays/ScrollOverlay.svelte
index c256e0dc19..b10f245fcb 100644
--- a/packages/frontend-core/src/components/grid/overlays/ScrollOverlay.svelte
+++ b/packages/frontend-core/src/components/grid/overlays/ScrollOverlay.svelte
@@ -17,6 +17,7 @@
height,
isDragging,
menu,
+ focusedCellAPI,
} = getContext("grid")
// State for dragging bars
@@ -47,10 +48,11 @@
$: barLeft = ScrollBarSize + availWidth * ($scrollLeft / $maxScrollLeft)
// Helper to close the context menu if it's open
- const closeMenu = () => {
+ const closePopovers = () => {
if ($menu.visible) {
menu.actions.close()
}
+ $focusedCellAPI?.blur()
}
const getLocation = e => {
@@ -70,7 +72,7 @@
document.addEventListener("mouseup", stopVDragging)
document.addEventListener("touchend", stopVDragging)
isDraggingV = true
- closeMenu()
+ closePopovers()
}
const moveVDragging = domDebounce(e => {
const delta = getLocation(e).y - initialMouse
@@ -99,7 +101,7 @@
document.addEventListener("mouseup", stopHDragging)
document.addEventListener("touchend", stopHDragging)
isDraggingH = true
- closeMenu()
+ closePopovers()
}
const moveHDragging = domDebounce(e => {
const delta = getLocation(e).x - initialMouse
@@ -127,7 +129,6 @@
on:mousedown={startVDragging}
on:touchstart={startVDragging}
class:dragging={isDraggingV}
- data-ignore-click-outside="true"
/>
{/if}
{#if $showHScrollbar}
@@ -138,7 +139,6 @@
on:mousedown={startHDragging}
on:touchstart={startHDragging}
class:dragging={isDraggingH}
- data-ignore-click-outside="true"
/>
{/if}
diff --git a/packages/frontend-core/src/components/grid/stores/reorder.js b/packages/frontend-core/src/components/grid/stores/reorder.js
index f820593174..fed9f4c6ef 100644
--- a/packages/frontend-core/src/components/grid/stores/reorder.js
+++ b/packages/frontend-core/src/components/grid/stores/reorder.js
@@ -87,7 +87,7 @@ export const createActions = context => {
// Check if we need to start auto-scrolling
const $reorder = get(reorder)
const proximityCutoff = 140
- const speedFactor = 8
+ const speedFactor = 16
const rightProximity = Math.max(0, $reorder.gridLeft + $reorder.width - x)
const leftProximity = Math.max(0, x - $reorder.gridLeft)
if (rightProximity < proximityCutoff) {
From 2187d25711241bbc1525868b531f2aa683b0509c Mon Sep 17 00:00:00 2001
From: Michael Drury
Date: Wed, 24 Apr 2024 17:12:36 +0100
Subject: [PATCH 083/338] Revert "Changing client library to be retrieved via
pre-signed URL for Cloud"
---
.../backend-core/src/objectStore/buckets/app.ts | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/packages/backend-core/src/objectStore/buckets/app.ts b/packages/backend-core/src/objectStore/buckets/app.ts
index 3ad575150e..43bc965c65 100644
--- a/packages/backend-core/src/objectStore/buckets/app.ts
+++ b/packages/backend-core/src/objectStore/buckets/app.ts
@@ -13,16 +13,23 @@ export function clientLibraryPath(appId: string) {
* due to issues with the domain we were unable to continue doing this - keeping
* incase we are able to switch back to CDN path again in future.
*/
-function cloudClientLibraryUrl(appId: string) {
+export function clientLibraryCDNUrl(appId: string, version: string) {
let file = clientLibraryPath(appId)
- return objectStore.getPresignedUrl(env.APPS_BUCKET_NAME, file)
+ if (env.CLOUDFRONT_CDN) {
+ // append app version to bust the cache
+ if (version) {
+ file += `?v=${version}`
+ }
+ // don't need to use presigned for client with cloudfront
+ // file is public
+ return cloudfront.getUrl(file)
+ } else {
+ return objectStore.getPresignedUrl(env.APPS_BUCKET_NAME, file)
+ }
}
export function clientLibraryUrl(appId: string, version: string) {
let tenantId, qsParams: { appId: string; version: string; tenantId?: string }
- if (env.isProd() && !env.SELF_HOSTED) {
- return cloudClientLibraryUrl(appId)
- }
try {
tenantId = getTenantId()
} finally {
From 377cd97f4b85ab63810da1ee386a1ffb350291e7 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Wed, 24 Apr 2024 17:35:23 +0100
Subject: [PATCH 084/338] Update attachment cells to use popovers
---
.../grid/cells/AttachmentCell.svelte | 49 ++++++++-----------
.../src/components/grid/cells/DataCell.svelte | 4 +-
.../src/components/grid/cells/DateCell.svelte | 4 +-
.../components/grid/cells/HeaderCell.svelte | 4 +-
.../components/grid/cells/OptionsCell.svelte | 4 +-
.../src/components/grid/layout/Grid.svelte | 6 +--
.../src/components/grid/layout/GridRow.svelte | 4 +-
.../grid/overlays/GridPopover.svelte | 6 +--
.../src/components/grid/stores/menu.js | 4 +-
9 files changed, 39 insertions(+), 46 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte b/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte
index e7dc51e5d5..ac1bb464b3 100644
--- a/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte
@@ -1,6 +1,7 @@
@@ -14,7 +14,7 @@
bind:open
{anchor}
align={invertX ? "right" : "left"}
- portalTarget="#grid-{rand} .grid-popover-container"
+ portalTarget="#{gridID} .grid-popover-container"
offset={1}
>
dispatch("close")}>
diff --git a/packages/frontend-core/src/components/grid/stores/menu.js b/packages/frontend-core/src/components/grid/stores/menu.js
index 2d11b65bd4..ea32285a95 100644
--- a/packages/frontend-core/src/components/grid/stores/menu.js
+++ b/packages/frontend-core/src/components/grid/stores/menu.js
@@ -13,13 +13,13 @@ export const createStores = () => {
}
export const createActions = context => {
- const { menu, focusedCellId, rand } = context
+ const { menu, focusedCellId, gridID } = context
const open = (cellId, e) => {
e.preventDefault()
// Get DOM node for grid data wrapper to compute relative position to
- const gridNode = document.getElementById(`grid-${rand}`)
+ const gridNode = document.getElementById(gridID)
const dataNode = gridNode?.getElementsByClassName("grid-data-outer")?.[0]
if (!dataNode) {
return
From 23bd635a8bf0ee534824b04db8af31052a861eb0 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Wed, 24 Apr 2024 20:26:58 +0100
Subject: [PATCH 085/338] Update relationship cells to use popovers
---
.../grid/cells/AttachmentCell.svelte | 9 ++-
.../src/components/grid/cells/DateCell.svelte | 2 +-
.../components/grid/cells/OptionsCell.svelte | 8 +--
.../grid/cells/RelationshipCell.svelte | 60 +++++--------------
.../src/components/grid/lib/constants.js | 5 ++
.../grid/overlays/GridPopover.svelte | 45 ++++++++++++--
6 files changed, 70 insertions(+), 59 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte b/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte
index ac1bb464b3..a74878467b 100644
--- a/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte
@@ -93,7 +93,14 @@
{#if isOpen}
-
+
{#if isOpen}
-
+
{#if isOpen}
-
- e.stopPropagation()}>
+
+
{#each options as option, idx}
{@const color = optionColors[option] || getOptionColor(option)}
@@ -219,10 +219,6 @@
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
- max-height: var(--max-cell-render-overflow);
- overflow-y: auto;
- min-width: 200px;
- max-width: 400px;
}
.option {
flex: 0 0 var(--default-row-height);
diff --git a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte
index a52dd9d53c..ba84316ab4 100644
--- a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte
@@ -1,8 +1,9 @@
- dispatch("close")}>
+
dispatch("close")}
+ on:wheel={e => e.stopPropagation()}
+ >
@@ -26,5 +54,12 @@
:global(.grid-popover-container .spectrum-Popover) {
background: var(--grid-background-alt);
border: var(--cell-border);
+ min-width: none;
+ max-width: none;
+ overflow: hidden;
+ }
+ .grid-popover-contents {
+ overflow-y: auto;
+ overflow-x: hidden;
}
From 957facb99cb073d8e7f9bc41c915a9ba08255a45 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Wed, 24 Apr 2024 20:39:16 +0100
Subject: [PATCH 086/338] Update long form cells to use popovers
---
.../components/grid/cells/LongFormCell.svelte | 64 +++++++++----------
.../grid/overlays/GridPopover.svelte | 2 +
2 files changed, 32 insertions(+), 34 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/cells/LongFormCell.svelte b/packages/frontend-core/src/components/grid/cells/LongFormCell.svelte
index b3eab24fa1..6a05b4ae49 100644
--- a/packages/frontend-core/src/components/grid/cells/LongFormCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/LongFormCell.svelte
@@ -1,6 +1,7 @@
-{#if isOpen}
-
-{:else}
-
-
-
+
+
{#if isOpen}
diff --git a/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte b/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte
index 13d3c7a1b3..c750535425 100644
--- a/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte
+++ b/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte
@@ -36,7 +36,7 @@
diff --git a/packages/frontend-core/src/components/grid/overlays/MenuOverlay.svelte b/packages/frontend-core/src/components/grid/overlays/MenuOverlay.svelte
index 55308d4c6e..60214a104a 100644
--- a/packages/frontend-core/src/components/grid/overlays/MenuOverlay.svelte
+++ b/packages/frontend-core/src/components/grid/overlays/MenuOverlay.svelte
@@ -1,7 +1,8 @@
+
+
{#if $menu.visible}
-
+ {#key style}
+
+
+
+ Copy
+
+
+ Paste
+
+ dispatch("edit-row", $focusedRow)}
+ on:click={menu.actions.close}
+ >
+ Edit row in modal
+
+ copyToClipboard($focusedRow?._id)}
+ on:click={menu.actions.close}
+ >
+ Copy row _id
+
+ copyToClipboard($focusedRow?._rev)}
+ on:click={menu.actions.close}
+ >
+ Copy row _rev
+
+
+ Duplicate row
+
+
+ Delete row
+
+
+
+ {/key}
{/if}
From b5eb60acd7e85ed148449aee11039e1cd7ae660f Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Wed, 24 Apr 2024 20:58:16 +0100
Subject: [PATCH 088/338] Grid cell style updates
---
.../src/components/grid/cells/AttachmentCell.svelte | 1 +
.../src/components/grid/cells/OptionsCell.svelte | 3 +--
.../src/components/grid/cells/RelationshipCell.svelte | 7 -------
.../src/components/grid/overlays/GridPopover.svelte | 3 +--
4 files changed, 3 insertions(+), 11 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte b/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte
index a74878467b..ed1fa5bbe5 100644
--- a/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte
@@ -145,6 +145,7 @@
user-select: none;
}
.dropzone {
+ background: var(--grid-background-alt);
width: 320px;
padding: var(--cell-padding);
}
diff --git a/packages/frontend-core/src/components/grid/cells/OptionsCell.svelte b/packages/frontend-core/src/components/grid/cells/OptionsCell.svelte
index 8929c38cd8..f950cfa590 100644
--- a/packages/frontend-core/src/components/grid/cells/OptionsCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/OptionsCell.svelte
@@ -228,11 +228,10 @@
justify-content: space-between;
align-items: center;
gap: var(--cell-spacing);
- background-color: var(--grid-background-alt);
}
.option:hover,
.option.focused {
- background-color: var(--spectrum-global-color-gray-200);
+ background-color: var(--grid-background-alt);
cursor: pointer;
}
diff --git a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte
index 93e50d9603..ed34b6c6e8 100644
--- a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte
@@ -231,7 +231,6 @@
class="wrapper"
class:editable
class:focused
- class:invertY
style="--color:{color};"
bind:this={anchor}
>
@@ -324,7 +323,6 @@
min-height: var(--row-height);
max-height: var(--row-height);
overflow: hidden;
- --max-relationship-height: 96px;
}
.wrapper.focused {
position: absolute;
@@ -336,10 +334,6 @@
max-height: none;
overflow: visible;
}
- .wrapper.invertY {
- top: auto;
- bottom: 0;
- }
.container {
min-height: var(--row-height);
@@ -350,7 +344,6 @@
.focused .container {
overflow-y: auto;
border-radius: 2px;
- max-height: var(--max-relationship-height);
}
.focused .container:after {
content: " ";
diff --git a/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte b/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte
index c750535425..a006f9fce1 100644
--- a/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte
+++ b/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte
@@ -52,8 +52,7 @@
diff --git a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
index 07c7438684..da9d179904 100644
--- a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
@@ -46,7 +46,6 @@
let open = false
let editIsOpen = false
let timeout
- let popover
let migrationModal
let searchValue
let input
@@ -104,11 +103,6 @@
dispatch("edit-column", column.schema)
}
- const cancelEdit = () => {
- popover.hide()
- editIsOpen = false
- }
-
const onMouseDown = e => {
if (e.button === 0 && orderable) {
timeout = setTimeout(() => {
@@ -234,7 +228,7 @@
}
const debouncedUpdateFilter = debounce(updateFilter, 250)
- onMount(() => subscribe("close-edit-column", cancelEdit))
+ onMount(() => subscribe("close-edit-column", close))
@@ -476,7 +470,7 @@
}
.content {
- width: 300px;
+ width: 360px;
padding: 20px;
display: flex;
flex-direction: column;
diff --git a/packages/frontend-core/src/components/grid/lib/constants.js b/packages/frontend-core/src/components/grid/lib/constants.js
index c42e49875f..4b5d04894a 100644
--- a/packages/frontend-core/src/components/grid/lib/constants.js
+++ b/packages/frontend-core/src/components/grid/lib/constants.js
@@ -10,7 +10,7 @@ export const DefaultRowHeight = SmallRowHeight
export const NewRowID = "new"
export const BlankRowID = "blank"
export const RowPageSize = 100
-export const FocusedCellMinOffset = 48
+export const FocusedCellMinOffset = ScrollBarSize * 3
export const ControlsHeight = 50
// Popovers
From 80cbd70687197f57ff39e6470478c1804e20548b Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Thu, 25 Apr 2024 08:46:51 +0100
Subject: [PATCH 092/338] Improve clickoutside to handle right clicks properly
---
packages/bbui/src/Actions/click_outside.js | 6 +++-
.../backend/DataTable/TableDataTable.svelte | 3 +-
.../grid/layout/NewColumnButton.svelte | 30 ++++++++-----------
.../grid/overlays/GridPopover.svelte | 3 +-
4 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/packages/bbui/src/Actions/click_outside.js b/packages/bbui/src/Actions/click_outside.js
index 76d015bee6..ee478c70c0 100644
--- a/packages/bbui/src/Actions/click_outside.js
+++ b/packages/bbui/src/Actions/click_outside.js
@@ -15,6 +15,9 @@ let clickHandlers = []
* Handle a body click event
*/
const handleClick = event => {
+ // Treat right clicks (context menu events) as normal clicks
+ const eventType = event.type === "contextmenu" ? "click" : event.type
+
// Ignore click if this is an ignored class
if (event.target.closest('[data-ignore-click-outside="true"]')) {
return
@@ -28,7 +31,7 @@ const handleClick = event => {
// Process handlers
clickHandlers.forEach(handler => {
// Check that we're the right kind of click event
- if (handler.allowedType && event.type !== handler.allowedType) {
+ if (handler.allowedType && eventType !== handler.allowedType) {
return
}
@@ -51,6 +54,7 @@ const handleClick = event => {
}
document.documentElement.addEventListener("click", handleClick, true)
document.documentElement.addEventListener("mousedown", handleClick, true)
+document.documentElement.addEventListener("contextmenu", handleClick, true)
/**
* Adds or updates a click handler
diff --git a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte
index fe393c5268..faac85f9e3 100644
--- a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte
+++ b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte
@@ -101,11 +101,10 @@
diff --git a/packages/frontend-core/src/components/grid/layout/NewColumnButton.svelte b/packages/frontend-core/src/components/grid/layout/NewColumnButton.svelte
index 9a08666857..970a645a74 100644
--- a/packages/frontend-core/src/components/grid/layout/NewColumnButton.svelte
+++ b/packages/frontend-core/src/components/grid/layout/NewColumnButton.svelte
@@ -1,6 +1,7 @@
Date: Thu, 25 Apr 2024 09:01:31 +0100
Subject: [PATCH 093/338] Add support for honouring original popover height in
position dropdown utility
---
packages/bbui/src/Actions/position_dropdown.js | 6 ++++++
packages/bbui/src/Popover/Popover.svelte | 2 ++
.../src/components/backend/DataTable/TableDataTable.svelte | 3 ++-
.../src/components/grid/overlays/GridPopover.svelte | 4 ++--
4 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js
index 4929a9beaf..42ee1cfef7 100644
--- a/packages/bbui/src/Actions/position_dropdown.js
+++ b/packages/bbui/src/Actions/position_dropdown.js
@@ -19,6 +19,7 @@ export default function positionDropdown(element, opts) {
useAnchorWidth,
offset = 5,
customUpdate,
+ noShrink,
} = opts
if (!anchor) {
return
@@ -82,6 +83,11 @@ export default function positionDropdown(element, opts) {
} else {
styles.left = anchorBounds.left
}
+
+ // Remove max height restriction if we don't want to shrink
+ if (noShrink) {
+ delete styles.maxHeight
+ }
}
// Apply styles
diff --git a/packages/bbui/src/Popover/Popover.svelte b/packages/bbui/src/Popover/Popover.svelte
index 079aee32b5..d1447bdab9 100644
--- a/packages/bbui/src/Popover/Popover.svelte
+++ b/packages/bbui/src/Popover/Popover.svelte
@@ -25,6 +25,7 @@
export let handlePostionUpdate
export let showPopover = true
export let clickOutsideOverride = false
+ export let noShrink = false
$: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum"
@@ -91,6 +92,7 @@
useAnchorWidth,
offset,
customUpdate: handlePostionUpdate,
+ noShrink,
}}
use:clickOutside={{
callback: dismissible ? handleOutsideClick : () => {},
diff --git a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte
index faac85f9e3..65aa26c9c9 100644
--- a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte
+++ b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte
@@ -101,7 +101,8 @@
diff --git a/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte b/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte
index 9b77b345f5..d8b8b0fdae 100644
--- a/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte
+++ b/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte
@@ -20,7 +20,7 @@
$: style = buildStyles(minWidth, maxWidth, maxHeight)
const buildStyles = (minWidth, maxWidth, maxHeight) => {
- let style = "min-height: 100px;"
+ let style = ""
if (minWidth != null) {
style += `min-width: ${minWidth}px;`
}
From c79ea9b0678f8fdb0a03f4dc6a88d674b356d7c0 Mon Sep 17 00:00:00 2001
From: jvcalderon
Date: Thu, 25 Apr 2024 12:01:45 +0200
Subject: [PATCH 100/338] Adds ENTERPRISE_BASIC_TRIAL
---
packages/types/src/sdk/licensing/plan.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/types/src/sdk/licensing/plan.ts b/packages/types/src/sdk/licensing/plan.ts
index 016caf8c38..af682cbad1 100644
--- a/packages/types/src/sdk/licensing/plan.ts
+++ b/packages/types/src/sdk/licensing/plan.ts
@@ -11,6 +11,7 @@ export enum PlanType {
/** @deprecated */
BUSINESS = "business",
ENTERPRISE_BASIC = "enterprise_basic",
+ ENTERPRISE_BASIC_TRIAL = "enterprise_basic_trial",
ENTERPRISE = "enterprise",
}
From d4c0f489c82a2a50ea7bbad33321ec58020423d0 Mon Sep 17 00:00:00 2001
From: jvcalderon
Date: Thu, 25 Apr 2024 12:02:23 +0200
Subject: [PATCH 101/338] Update Submodules
---
packages/account-portal | 2 +-
packages/pro | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/account-portal b/packages/account-portal
index c167c331ff..a9508c96ef 160000
--- a/packages/account-portal
+++ b/packages/account-portal
@@ -1 +1 @@
-Subproject commit c167c331ff9b8161fc18e2ecbaaf1ea5815ba964
+Subproject commit a9508c96efb7bf056c3988d9a71e763c6e4a7b83
diff --git a/packages/pro b/packages/pro
index dff7b5a9dd..e3e8578a48 160000
--- a/packages/pro
+++ b/packages/pro
@@ -1 +1 @@
-Subproject commit dff7b5a9dd1fd770f8a48fb8e6df1740be605f18
+Subproject commit e3e8578a480a31365f11fdfbf7b0c383c056f9b4
From afdbf4cc42e4dae7ba3a4cd9419c01a35479c6d8 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 22 Apr 2024 11:14:23 +0200
Subject: [PATCH 102/338] Add BB_REFERENCE_SINGLE type
---
.../server/src/integrations/base/sqlTable.ts | 5 ++-
.../server/src/integrations/googlesheets.ts | 35 +++++++++++++------
.../server/src/integrations/utils/utils.ts | 1 +
.../server/src/sdk/app/tables/internal/sqs.ts | 1 +
.../src/utilities/rowProcessor/index.ts | 3 +-
packages/server/src/utilities/schema.ts | 3 +-
packages/shared-core/src/filters.ts | 6 +++-
packages/shared-core/src/table.ts | 2 ++
packages/types/src/documents/app/row.ts | 2 ++
.../types/src/documents/app/table/schema.ts | 7 ++++
10 files changed, 50 insertions(+), 15 deletions(-)
diff --git a/packages/server/src/integrations/base/sqlTable.ts b/packages/server/src/integrations/base/sqlTable.ts
index 3c55d75b8b..087f068386 100644
--- a/packages/server/src/integrations/base/sqlTable.ts
+++ b/packages/server/src/integrations/base/sqlTable.ts
@@ -61,7 +61,8 @@ function generateSchema(
case FieldType.BARCODEQR:
schema.text(key)
break
- case FieldType.BB_REFERENCE: {
+ case FieldType.BB_REFERENCE:
+ case FieldType.BB_REFERENCE_SINGLE: {
const subtype = column.subtype
switch (subtype) {
case FieldSubtype.USER:
@@ -127,6 +128,8 @@ function generateSchema(
.references(`${tableName}.${relatedPrimary}`)
}
break
+ default:
+ utils.unreachable(column.type)
}
}
diff --git a/packages/server/src/integrations/googlesheets.ts b/packages/server/src/integrations/googlesheets.ts
index 7215c337d7..8288a07846 100644
--- a/packages/server/src/integrations/googlesheets.ts
+++ b/packages/server/src/integrations/googlesheets.ts
@@ -52,17 +52,30 @@ interface AuthTokenResponse {
access_token: string
}
-const ALLOWED_TYPES = [
- FieldType.STRING,
- FieldType.FORMULA,
- FieldType.NUMBER,
- FieldType.LONGFORM,
- FieldType.DATETIME,
- FieldType.OPTIONS,
- FieldType.BOOLEAN,
- FieldType.BARCODEQR,
- FieldType.BB_REFERENCE,
-]
+const isTypeAllowed: Record = {
+ [FieldType.STRING]: true,
+ [FieldType.FORMULA]: true,
+ [FieldType.NUMBER]: true,
+ [FieldType.LONGFORM]: true,
+ [FieldType.DATETIME]: true,
+ [FieldType.OPTIONS]: true,
+ [FieldType.BOOLEAN]: true,
+ [FieldType.BARCODEQR]: true,
+ [FieldType.BB_REFERENCE]: true,
+ [FieldType.BB_REFERENCE_SINGLE]: true,
+ [FieldType.ARRAY]: false,
+ [FieldType.ATTACHMENTS]: false,
+ [FieldType.ATTACHMENT_SINGLE]: false,
+ [FieldType.LINK]: false,
+ [FieldType.AUTO]: false,
+ [FieldType.JSON]: false,
+ [FieldType.INTERNAL]: false,
+ [FieldType.BIGINT]: false,
+}
+
+const ALLOWED_TYPES = Object.entries(isTypeAllowed)
+ .filter(([_, allowed]) => allowed)
+ .map(([type]) => type as FieldType)
const SCHEMA: Integration = {
plus: true,
diff --git a/packages/server/src/integrations/utils/utils.ts b/packages/server/src/integrations/utils/utils.ts
index aac3f5f74a..581c9b6a22 100644
--- a/packages/server/src/integrations/utils/utils.ts
+++ b/packages/server/src/integrations/utils/utils.ts
@@ -378,6 +378,7 @@ function copyExistingPropsOver(
case FieldType.ATTACHMENT_SINGLE:
case FieldType.JSON:
case FieldType.BB_REFERENCE:
+ case FieldType.BB_REFERENCE_SINGLE:
shouldKeepSchema = keepIfType(FieldType.JSON, FieldType.STRING)
break
diff --git a/packages/server/src/sdk/app/tables/internal/sqs.ts b/packages/server/src/sdk/app/tables/internal/sqs.ts
index 0726c94962..391e186bc0 100644
--- a/packages/server/src/sdk/app/tables/internal/sqs.ts
+++ b/packages/server/src/sdk/app/tables/internal/sqs.ts
@@ -45,6 +45,7 @@ const FieldTypeMap: Record = {
[FieldType.BIGINT]: SQLiteType.TEXT,
// TODO: consider the difference between multi-user and single user types (subtyping)
[FieldType.BB_REFERENCE]: SQLiteType.TEXT,
+ [FieldType.BB_REFERENCE_SINGLE]: SQLiteType.TEXT,
}
function buildRelationshipDefinitions(
diff --git a/packages/server/src/utilities/rowProcessor/index.ts b/packages/server/src/utilities/rowProcessor/index.ts
index e69cfa471a..418b38bc38 100644
--- a/packages/server/src/utilities/rowProcessor/index.ts
+++ b/packages/server/src/utilities/rowProcessor/index.ts
@@ -244,7 +244,8 @@ export async function outputProcessing(
}
} else if (
!opts.skipBBReferences &&
- column.type == FieldType.BB_REFERENCE
+ (column.type == FieldType.BB_REFERENCE ||
+ column.type == FieldType.BB_REFERENCE_SINGLE)
) {
for (let row of enriched) {
row[property] = await processOutputBBReferences(
diff --git a/packages/server/src/utilities/schema.ts b/packages/server/src/utilities/schema.ts
index 4c7f0b7423..156ea5ea7b 100644
--- a/packages/server/src/utilities/schema.ts
+++ b/packages/server/src/utilities/schema.ts
@@ -92,7 +92,8 @@ export function validate(rows: Rows, schema: TableSchema): ValidationResults {
) {
results.schemaValidation[columnName] = false
} else if (
- columnType === FieldType.BB_REFERENCE &&
+ (columnType === FieldType.BB_REFERENCE ||
+ columnType === FieldType.BB_REFERENCE_SINGLE) &&
!isValidBBReference(columnData, columnSubtype)
) {
results.schemaValidation[columnName] = false
diff --git a/packages/shared-core/src/filters.ts b/packages/shared-core/src/filters.ts
index 0554e0c1e4..849f22e991 100644
--- a/packages/shared-core/src/filters.ts
+++ b/packages/shared-core/src/filters.ts
@@ -68,7 +68,11 @@ export const getValidOperatorsForType = (
ops = numOps
} else if (type === FieldType.FORMULA && formulaType === FormulaType.STATIC) {
ops = stringOps.concat([Op.MoreThan, Op.LessThan])
- } else if (type === FieldType.BB_REFERENCE && subtype == FieldSubtype.USER) {
+ } else if (
+ (type === FieldType.BB_REFERENCE_SINGLE ||
+ type === FieldType.BB_REFERENCE) &&
+ subtype == FieldSubtype.USER
+ ) {
ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.In]
} else if (type === FieldType.BB_REFERENCE && subtype == FieldSubtype.USERS) {
ops = [Op.Contains, Op.NotContains, Op.ContainsAny, Op.Empty, Op.NotEmpty]
diff --git a/packages/shared-core/src/table.ts b/packages/shared-core/src/table.ts
index 26a7e77cd0..2b3586932a 100644
--- a/packages/shared-core/src/table.ts
+++ b/packages/shared-core/src/table.ts
@@ -18,6 +18,7 @@ const allowDisplayColumnByType: Record = {
[FieldType.LINK]: false,
[FieldType.JSON]: false,
[FieldType.BB_REFERENCE]: false,
+ [FieldType.BB_REFERENCE_SINGLE]: false,
}
const allowSortColumnByType: Record = {
@@ -39,6 +40,7 @@ const allowSortColumnByType: Record = {
[FieldType.ARRAY]: false,
[FieldType.LINK]: false,
[FieldType.BB_REFERENCE]: false,
+ [FieldType.BB_REFERENCE_SINGLE]: false,
}
export function canBeDisplayColumn(type: FieldType): boolean {
diff --git a/packages/types/src/documents/app/row.ts b/packages/types/src/documents/app/row.ts
index 865ab4ba64..ca9b11ffe5 100644
--- a/packages/types/src/documents/app/row.ts
+++ b/packages/types/src/documents/app/row.ts
@@ -107,6 +107,8 @@ export enum FieldType {
* an array of resource IDs, the API will squash these down and validate them before saving the row.
*/
BB_REFERENCE = "bb_reference",
+ // TODO
+ BB_REFERENCE_SINGLE = "bb_reference_single",
}
export interface RowAttachment {
diff --git a/packages/types/src/documents/app/table/schema.ts b/packages/types/src/documents/app/table/schema.ts
index 63a5876bc0..22ad9f7bb5 100644
--- a/packages/types/src/documents/app/table/schema.ts
+++ b/packages/types/src/documents/app/table/schema.ts
@@ -112,6 +112,11 @@ export interface BBReferenceFieldMetadata
subtype: FieldSubtype.USER | FieldSubtype.USERS
relationshipType?: RelationshipType
}
+export interface BBReferenceSingleFieldMetadata
+ extends Omit {
+ type: FieldType.BB_REFERENCE_SINGLE
+ subtype: FieldSubtype.USER | FieldSubtype.USERS
+}
export interface AttachmentFieldMetadata extends BaseFieldSchema {
type: FieldType.ATTACHMENTS
@@ -163,6 +168,7 @@ interface OtherFieldMetadata extends BaseFieldSchema {
| FieldType.NUMBER
| FieldType.LONGFORM
| FieldType.BB_REFERENCE
+ | FieldType.BB_REFERENCE_SINGLE
| FieldType.ATTACHMENTS
>
}
@@ -178,6 +184,7 @@ export type FieldSchema =
| BBReferenceFieldMetadata
| JsonFieldMetadata
| AttachmentFieldMetadata
+ | BBReferenceSingleFieldMetadata
export interface TableSchema {
[key: string]: FieldSchema
From 2555a145b26dd1a174824fbc9b9f49cf6066ba4a Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 22 Apr 2024 12:32:51 +0200
Subject: [PATCH 103/338] Add user/users column types
---
.../backend/DataTable/modals/CreateEditColumn.svelte | 10 ++++------
packages/builder/src/constants/backend/index.js | 11 ++++++++---
packages/frontend-core/src/constants.js | 5 +++--
3 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index d271462f3e..a837a9aad8 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -373,10 +373,6 @@
.map(([_, fieldDefinition]) => fieldDefinition)
}
- const isUsers =
- editableColumn.type === FieldType.BB_REFERENCE &&
- editableColumn.subtype === FieldSubtype.USERS
-
if (!externalTable) {
return [
FIELDS.STRING,
@@ -393,7 +389,8 @@
FIELDS.LINK,
FIELDS.FORMULA,
FIELDS.JSON,
- isUsers ? FIELDS.USERS : FIELDS.USER,
+ FIELDS.USER,
+ FIELDS.USERS,
FIELDS.AUTO,
]
} else {
@@ -407,7 +404,8 @@
FIELDS.BOOLEAN,
FIELDS.FORMULA,
FIELDS.BIGINT,
- isUsers ? FIELDS.USERS : FIELDS.USER,
+ FIELDS.USER,
+ FIELDS.USERS,
]
// no-sql or a spreadsheet
if (!externalTable || table.sql) {
diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.js
index 84975d93e2..30f6e20b45 100644
--- a/packages/builder/src/constants/backend/index.js
+++ b/packages/builder/src/constants/backend/index.js
@@ -4,6 +4,7 @@ import {
INTERNAL_TABLE_SOURCE_ID,
AutoFieldSubType,
Hosting,
+ FieldTypeSubtypes,
} from "@budibase/types"
import { Constants } from "@budibase/frontend-core"
@@ -159,15 +160,19 @@ export const FIELDS = {
},
USER: {
name: "User",
- type: FieldType.BB_REFERENCE,
+ type: FieldType.BB_REFERENCE_SINGLE,
subtype: FieldSubtype.USER,
- icon: TypeIconMap[FieldType.USER],
+ icon: TypeIconMap[FieldType.BB_REFERENCE_SINGLE][
+ FieldTypeSubtypes.BB_REFERENCE.USER
+ ],
},
USERS: {
name: "Users",
type: FieldType.BB_REFERENCE,
subtype: FieldSubtype.USERS,
- icon: TypeIconMap[FieldType.USERS],
+ icon: TypeIconMap[FieldType.BB_REFERENCE][
+ FieldTypeSubtypes.BB_REFERENCE.USERS
+ ],
constraints: {
type: "array",
},
diff --git a/packages/frontend-core/src/constants.js b/packages/frontend-core/src/constants.js
index 95228c3bdc..b3b1edac65 100644
--- a/packages/frontend-core/src/constants.js
+++ b/packages/frontend-core/src/constants.js
@@ -131,10 +131,11 @@ export const TypeIconMap = {
[FieldType.JSON]: "Brackets",
[FieldType.BIGINT]: "TagBold",
[FieldType.AUTO]: "MagicWand",
- [FieldType.USER]: "User",
- [FieldType.USERS]: "UserGroup",
[FieldType.BB_REFERENCE]: {
[FieldTypeSubtypes.BB_REFERENCE.USER]: "User",
[FieldTypeSubtypes.BB_REFERENCE.USERS]: "UserGroup",
},
+ [FieldType.BB_REFERENCE_SINGLE]: {
+ [FieldTypeSubtypes.BB_REFERENCE.USER]: "User",
+ },
}
From 891a04c37637590977db7c8d9318dce268918689 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 22 Apr 2024 12:38:58 +0200
Subject: [PATCH 104/338] Remove is multiple toggle
---
.../DataTable/modals/CreateEditColumn.svelte | 14 --------------
1 file changed, 14 deletions(-)
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index a837a9aad8..574b15de1a 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -684,20 +684,6 @@
Open schema editor
- {:else if isUsersColumn(editableColumn) && datasource?.source !== SourceName.GOOGLE_SHEETS}
-
- handleTypeChange(
- makeFieldId(
- FieldType.BB_REFERENCE,
- e.detail ? FieldSubtype.USERS : FieldSubtype.USER
- )
- )}
- disabled={!isCreating}
- thin
- text="Allow multiple users"
- />
{/if}
{#if editableColumn.type === AUTO_TYPE || editableColumn.autocolumn}
Date: Mon, 22 Apr 2024 13:05:09 +0200
Subject: [PATCH 105/338] Prevent flashing on edition
---
.../DataTable/modals/grid/GridEditColumnModal.svelte | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/packages/builder/src/components/backend/DataTable/modals/grid/GridEditColumnModal.svelte b/packages/builder/src/components/backend/DataTable/modals/grid/GridEditColumnModal.svelte
index 020c58d19f..2ecb7dfd59 100644
--- a/packages/builder/src/components/backend/DataTable/modals/grid/GridEditColumnModal.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/grid/GridEditColumnModal.svelte
@@ -13,7 +13,9 @@
onMount(() => subscribe("edit-column", editColumn))
-
+{#if editableColumn}
+
+{/if}
From b13d47e88d880eeb7f8b2413a1064d21e281bffc Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 22 Apr 2024 13:11:13 +0200
Subject: [PATCH 106/338] Fix display items
---
.../DataTable/modals/CreateEditColumn.svelte | 15 ++++++++-------
packages/builder/src/constants/backend/index.js | 9 +++++++++
2 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index 574b15de1a..642dde5db1 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -186,7 +186,10 @@
// don't make field IDs for auto types
if (type === AUTO_TYPE || autocolumn) {
return type.toUpperCase()
- } else if (type === FieldType.BB_REFERENCE) {
+ } else if (
+ type === FieldType.BB_REFERENCE ||
+ type === FieldType.BB_REFERENCE_SINGLE
+ ) {
return `${type}${subtype || ""}`.toUpperCase()
} else {
return type.toUpperCase()
@@ -363,13 +366,11 @@
function getAllowedTypes() {
if (originalName) {
- const possibleTypes = (
- SWITCHABLE_TYPES[field.type] || [editableColumn.type]
- ).map(t => t.toLowerCase())
+ const possibleTypes = SWITCHABLE_TYPES[field.type] || [
+ editableColumn.type,
+ ]
return Object.entries(FIELDS)
- .filter(([fieldType]) =>
- possibleTypes.includes(fieldType.toLowerCase())
- )
+ .filter(([_, field]) => possibleTypes.includes(field.type))
.map(([_, fieldDefinition]) => fieldDefinition)
}
diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.js
index 30f6e20b45..1996395420 100644
--- a/packages/builder/src/constants/backend/index.js
+++ b/packages/builder/src/constants/backend/index.js
@@ -166,6 +166,15 @@ export const FIELDS = {
FieldTypeSubtypes.BB_REFERENCE.USER
],
},
+ // Used for display of editing existing columns
+ OLD_USER: {
+ name: "User",
+ type: FieldType.BB_REFERENCE,
+ subtype: FieldSubtype.USER,
+ icon: TypeIconMap[FieldType.BB_REFERENCE_SINGLE][
+ FieldTypeSubtypes.BB_REFERENCE.USER
+ ],
+ },
USERS: {
name: "Users",
type: FieldType.BB_REFERENCE,
From 7133fc0a1379893f15fbe2cdfe508254d0a91700 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 22 Apr 2024 15:31:38 +0200
Subject: [PATCH 107/338] Display renderer
---
.../grid/cells/BBReferenceSingleCell.svelte | 21 +++++++++++++++++++
.../src/components/grid/lib/renderers.js | 2 ++
2 files changed, 23 insertions(+)
create mode 100644 packages/frontend-core/src/components/grid/cells/BBReferenceSingleCell.svelte
diff --git a/packages/frontend-core/src/components/grid/cells/BBReferenceSingleCell.svelte b/packages/frontend-core/src/components/grid/cells/BBReferenceSingleCell.svelte
new file mode 100644
index 0000000000..36a783f584
--- /dev/null
+++ b/packages/frontend-core/src/components/grid/cells/BBReferenceSingleCell.svelte
@@ -0,0 +1,21 @@
+
+
+
diff --git a/packages/frontend-core/src/components/grid/lib/renderers.js b/packages/frontend-core/src/components/grid/lib/renderers.js
index c3ee276ff9..8911d68cba 100644
--- a/packages/frontend-core/src/components/grid/lib/renderers.js
+++ b/packages/frontend-core/src/components/grid/lib/renderers.js
@@ -13,6 +13,7 @@ import JSONCell from "../cells/JSONCell.svelte"
import AttachmentCell from "../cells/AttachmentCell.svelte"
import AttachmentSingleCell from "../cells/AttachmentSingleCell.svelte"
import BBReferenceCell from "../cells/BBReferenceCell.svelte"
+import BBReferenceSingleCell from "../cells/BBReferenceSingleCell.svelte"
const TypeComponentMap = {
[FieldType.STRING]: TextCell,
@@ -29,6 +30,7 @@ const TypeComponentMap = {
[FieldType.FORMULA]: FormulaCell,
[FieldType.JSON]: JSONCell,
[FieldType.BB_REFERENCE]: BBReferenceCell,
+ [FieldType.BB_REFERENCE_SINGLE]: BBReferenceSingleCell,
}
export const getCellRenderer = column => {
return TypeComponentMap[column?.schema?.type] || TextCell
From d63c5830e5671beadfc6150426d19e2f2eaaafd8 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 22 Apr 2024 17:26:32 +0200
Subject: [PATCH 108/338] Typings
---
packages/backend-core/src/cache/user.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/backend-core/src/cache/user.ts b/packages/backend-core/src/cache/user.ts
index ecfa20f99e..d319c5dcb6 100644
--- a/packages/backend-core/src/cache/user.ts
+++ b/packages/backend-core/src/cache/user.ts
@@ -69,7 +69,7 @@ async function populateUsersFromDB(
export async function getUser(
userId: string,
tenantId?: string,
- populateUser?: any
+ populateUser?: (userId: string, tenantId: string) => Promise
) {
if (!populateUser) {
populateUser = populateFromDB
@@ -83,7 +83,7 @@ export async function getUser(
}
const client = await redis.getUserClient()
// try cache
- let user = await client.get(userId)
+ let user: User = await client.get(userId)
if (!user) {
user = await populateUser(userId, tenantId)
await client.store(userId, user, EXPIRY_SECONDS)
From 808aba0c34ccfaba6d6ab23bd437da2bbe6c5348 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 22 Apr 2024 17:27:26 +0200
Subject: [PATCH 109/338] Input processing
---
.../rowProcessor/bbReferenceProcessor.ts | 150 ++++++++++++++----
.../src/utilities/rowProcessor/index.ts | 13 +-
2 files changed, 130 insertions(+), 33 deletions(-)
diff --git a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
index a5fbfa981d..706fc34903 100644
--- a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
+++ b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
@@ -1,13 +1,43 @@
import { cache, db as dbCore } from "@budibase/backend-core"
import { utils } from "@budibase/shared-core"
-import { FieldSubtype, DocumentType, SEPARATOR } from "@budibase/types"
+import {
+ FieldType,
+ FieldSubtype,
+ DocumentType,
+ SEPARATOR,
+ User,
+} from "@budibase/types"
import { InvalidBBRefError } from "./errors"
const ROW_PREFIX = DocumentType.ROW + SEPARATOR
-export async function processInputBBReferences(
+export async function processInputBBReferences<
+ T = FieldType.BB_REFERENCE_SINGLE
+>(value: string, type: T): Promise
+export async function processInputBBReferences<
+ T = FieldType.BB_REFERENCE,
+ TS = FieldSubtype.USER
+>(
value: string | string[] | { _id: string } | { _id: string }[],
- subtype: FieldSubtype.USER | FieldSubtype.USERS
+ type: T,
+ subtype: TS
+): Promise
+export async function processInputBBReferences<
+ T = FieldType.BB_REFERENCE,
+ TS = FieldSubtype.USERS
+>(
+ value: string | string[] | { _id: string } | { _id: string }[],
+ type: T,
+ subtype: TS
+): Promise
+
+export async function processInputBBReferences<
+ T extends FieldType.BB_REFERENCE | FieldType.BB_REFERENCE_SINGLE,
+ TS extends FieldSubtype.USER | FieldSubtype.USERS
+>(
+ value: string | string[] | { _id: string } | { _id: string }[],
+ type: T,
+ subtype?: TS
): Promise {
let referenceIds: string[] = []
@@ -39,55 +69,113 @@ export async function processInputBBReferences(
}
})
- switch (subtype) {
- case FieldSubtype.USER:
- case FieldSubtype.USERS: {
- const { notFoundIds } = await cache.user.getUsers(referenceIds)
+ switch (type) {
+ case FieldType.BB_REFERENCE:
+ switch (subtype) {
+ case undefined:
+ throw "Subtype must be defined"
+ case FieldSubtype.USER:
+ case FieldSubtype.USERS:
+ const { notFoundIds } = await cache.user.getUsers(referenceIds)
- if (notFoundIds?.length) {
- throw new InvalidBBRefError(notFoundIds[0], FieldSubtype.USER)
+ if (notFoundIds?.length) {
+ throw new InvalidBBRefError(notFoundIds[0], FieldSubtype.USER)
+ }
+
+ if (subtype === FieldSubtype.USERS) {
+ return referenceIds
+ }
+
+ return referenceIds.join(",") || null
+ default:
+ throw utils.unreachable(subtype)
}
- if (subtype === FieldSubtype.USERS) {
- return referenceIds
+ case FieldType.BB_REFERENCE_SINGLE:
+ const user = await cache.user.getUser(referenceIds[0])
+
+ if (!user) {
+ throw new InvalidBBRefError(referenceIds[0], FieldSubtype.USER)
}
- return referenceIds.join(",") || null
- }
+ return referenceIds[0] || null
+
default:
- throw utils.unreachable(subtype)
+ throw utils.unreachable(type)
}
}
+interface UserReferenceInfo {
+ _id: string
+ primaryDisplay: string
+ email: string
+ firstName: string
+ lastName: string
+}
+
+export async function processOutputBBReferences<
+ T = FieldType.BB_REFERENCE_SINGLE
+>(value: string, type: T): Promise
+export async function processOutputBBReferences<
+ T = FieldType.BB_REFERENCE,
+ TS = FieldSubtype.USER
+>(value: string, type: T, subtype: TS): Promise
+export async function processOutputBBReferences<
+ T = FieldType.BB_REFERENCE,
+ TS = FieldSubtype.USERS
+>(value: string[], type: T, subtype: TS): Promise
+
export async function processOutputBBReferences(
value: string | string[],
- subtype: FieldSubtype.USER | FieldSubtype.USERS
+ type: FieldType.BB_REFERENCE | FieldType.BB_REFERENCE_SINGLE,
+ subtype?: FieldSubtype.USER | FieldSubtype.USERS
) {
if (value === null || value === undefined) {
// Already processed or nothing to process
return value || undefined
}
- const ids =
- typeof value === "string" ? value.split(",").filter(id => !!id) : value
+ switch (type) {
+ case FieldType.BB_REFERENCE:
+ const ids =
+ typeof value === "string" ? value.split(",").filter(id => !!id) : value
- switch (subtype) {
- case FieldSubtype.USER:
- case FieldSubtype.USERS: {
- const { users } = await cache.user.getUsers(ids)
- if (!users.length) {
+ switch (subtype) {
+ case undefined:
+ throw "Subtype must be defined"
+ case FieldSubtype.USER:
+ case FieldSubtype.USERS:
+ const { users } = await cache.user.getUsers(ids)
+ if (!users.length) {
+ return undefined
+ }
+
+ return users.map(u => ({
+ _id: u._id,
+ primaryDisplay: u.email,
+ email: u.email,
+ firstName: u.firstName,
+ lastName: u.lastName,
+ }))
+ default:
+ throw utils.unreachable(subtype)
+ }
+
+ case FieldType.BB_REFERENCE_SINGLE:
+ const user = await cache.user.getUser(value as string)
+ if (!user) {
return undefined
}
- return users.map(u => ({
- _id: u._id,
- primaryDisplay: u.email,
- email: u.email,
- firstName: u.firstName,
- lastName: u.lastName,
- }))
- }
+ return {
+ _id: user._id,
+ primaryDisplay: user.email,
+ email: user.email,
+ firstName: user.firstName,
+ lastName: user.lastName,
+ }
+
default:
- throw utils.unreachable(subtype)
+ throw utils.unreachable(type)
}
}
diff --git a/packages/server/src/utilities/rowProcessor/index.ts b/packages/server/src/utilities/rowProcessor/index.ts
index 418b38bc38..57cafef8ad 100644
--- a/packages/server/src/utilities/rowProcessor/index.ts
+++ b/packages/server/src/utilities/rowProcessor/index.ts
@@ -162,8 +162,16 @@ export async function inputProcessing(
}
}
- if (field.type === FieldType.BB_REFERENCE && value) {
- clonedRow[key] = await processInputBBReferences(value, field.subtype)
+ if (
+ (field.type === FieldType.BB_REFERENCE ||
+ field.type === FieldType.BB_REFERENCE_SINGLE) &&
+ value
+ ) {
+ clonedRow[key] = await processInputBBReferences(
+ value,
+ field.type,
+ field.subtype
+ )
}
}
@@ -250,6 +258,7 @@ export async function outputProcessing(
for (let row of enriched) {
row[property] = await processOutputBBReferences(
row[property],
+ column.type,
column.subtype
)
}
From a794fb406d81c7c8d4d814751afd65f9bfc1d5c7 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 22 Apr 2024 17:35:41 +0200
Subject: [PATCH 110/338] Remove relationshipType from column type
---
.../backend/DataTable/modals/CreateEditColumn.svelte | 9 ---------
1 file changed, 9 deletions(-)
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index 642dde5db1..9f7b7bb57c 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -41,8 +41,6 @@
const NUMBER_TYPE = FieldType.NUMBER
const JSON_TYPE = FieldType.JSON
const DATE_TYPE = FieldType.DATETIME
- const USER_TYPE = FieldSubtype.USER
- const USERS_TYPE = FieldSubtype.USERS
const dispatch = createEventDispatcher()
const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"]
@@ -265,13 +263,6 @@
if (saveColumn.type !== LINK_TYPE) {
delete saveColumn.fieldName
}
- if (isUsersColumn(saveColumn)) {
- if (saveColumn.subtype === USER_TYPE) {
- saveColumn.relationshipType = RelationshipType.ONE_TO_MANY
- } else if (saveColumn.subtype === USERS_TYPE) {
- saveColumn.relationshipType = RelationshipType.MANY_TO_MANY
- }
- }
try {
await tables.saveField({
From 20a39f53c6a3f4797ef20ce44a0dd5d4d7b00c24 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 22 Apr 2024 17:45:56 +0200
Subject: [PATCH 111/338] Hide counter for single references
---
.../src/components/grid/cells/BBReferenceCell.svelte | 2 ++
.../src/components/grid/cells/BBReferenceSingleCell.svelte | 1 +
.../src/components/grid/cells/RelationshipCell.svelte | 3 ++-
3 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/packages/frontend-core/src/components/grid/cells/BBReferenceCell.svelte b/packages/frontend-core/src/components/grid/cells/BBReferenceCell.svelte
index 48b1279346..056ad0aa39 100644
--- a/packages/frontend-core/src/components/grid/cells/BBReferenceCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/BBReferenceCell.svelte
@@ -4,6 +4,7 @@
import { FieldSubtype, RelationshipType } from "@budibase/types"
export let api
+ export let hideCounter = false
const { API } = getContext("grid")
const { subtype } = $$props.schema
@@ -45,4 +46,5 @@
{schema}
{searchFunction}
primaryDisplay={"email"}
+ {hideCounter}
/>
diff --git a/packages/frontend-core/src/components/grid/cells/BBReferenceSingleCell.svelte b/packages/frontend-core/src/components/grid/cells/BBReferenceSingleCell.svelte
index 36a783f584..ed98e2ee08 100644
--- a/packages/frontend-core/src/components/grid/cells/BBReferenceSingleCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/BBReferenceSingleCell.svelte
@@ -18,4 +18,5 @@
{...$$restProps}
value={arrayValue}
onChange={onValueChange}
+ hideCounter={true}
/>
diff --git a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte
index bf1fe92ef0..d181fd72e9 100644
--- a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte
@@ -17,6 +17,7 @@
export let contentLines = 1
export let searchFunction = API.searchTable
export let primaryDisplay
+ export let hideCounter = false
const color = getColor(0)
@@ -276,7 +277,7 @@
{/if}
- {#if value?.length}
+ {#if !hideCounter && value?.length}
{value?.length || 0}
From b56c86e1b009f280ca453b3120a8cab02a16ef02 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 22 Apr 2024 17:50:37 +0200
Subject: [PATCH 112/338] Lint
---
.../backend/DataTable/modals/CreateEditColumn.svelte | 7 -------
.../src/utilities/rowProcessor/bbReferenceProcessor.ts | 1 -
2 files changed, 8 deletions(-)
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index 9f7b7bb57c..02e3d40362 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -472,13 +472,6 @@
return newError
}
- function isUsersColumn(column) {
- return (
- column.type === FieldType.BB_REFERENCE &&
- [FieldSubtype.USER, FieldSubtype.USERS].includes(column.subtype)
- )
- }
-
onMount(() => {
mounted = true
})
diff --git a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
index 706fc34903..23fea68b99 100644
--- a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
+++ b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
@@ -5,7 +5,6 @@ import {
FieldSubtype,
DocumentType,
SEPARATOR,
- User,
} from "@budibase/types"
import { InvalidBBRefError } from "./errors"
From 53badf926391904b62943fd2ab6320ca39a5dc7d Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 22 Apr 2024 20:58:35 +0200
Subject: [PATCH 113/338] Lint
---
.../rowProcessor/bbReferenceProcessor.ts | 31 +++++++++++--------
1 file changed, 18 insertions(+), 13 deletions(-)
diff --git a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
index 23fea68b99..5d651b0faa 100644
--- a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
+++ b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
@@ -10,10 +10,11 @@ import { InvalidBBRefError } from "./errors"
const ROW_PREFIX = DocumentType.ROW + SEPARATOR
-export async function processInputBBReferences<
- T = FieldType.BB_REFERENCE_SINGLE
->(value: string, type: T): Promise
-export async function processInputBBReferences<
+export function processInputBBReferences(
+ value: string,
+ type: T
+): Promise
+export function processInputBBReferences<
T = FieldType.BB_REFERENCE,
TS = FieldSubtype.USER
>(
@@ -21,7 +22,7 @@ export async function processInputBBReferences<
type: T,
subtype: TS
): Promise
-export async function processInputBBReferences<
+export function processInputBBReferences<
T = FieldType.BB_REFERENCE,
TS = FieldSubtype.USERS
>(
@@ -74,7 +75,7 @@ export async function processInputBBReferences<
case undefined:
throw "Subtype must be defined"
case FieldSubtype.USER:
- case FieldSubtype.USERS:
+ case FieldSubtype.USERS: {
const { notFoundIds } = await cache.user.getUsers(referenceIds)
if (notFoundIds?.length) {
@@ -86,11 +87,12 @@ export async function processInputBBReferences<
}
return referenceIds.join(",") || null
+ }
default:
throw utils.unreachable(subtype)
}
- case FieldType.BB_REFERENCE_SINGLE:
+ case FieldType.BB_REFERENCE_SINGLE: {
const user = await cache.user.getUser(referenceIds[0])
if (!user) {
@@ -98,6 +100,7 @@ export async function processInputBBReferences<
}
return referenceIds[0] || null
+ }
default:
throw utils.unreachable(type)
@@ -112,14 +115,15 @@ interface UserReferenceInfo {
lastName: string
}
-export async function processOutputBBReferences<
- T = FieldType.BB_REFERENCE_SINGLE
->(value: string, type: T): Promise
-export async function processOutputBBReferences<
+export function processOutputBBReferences(
+ value: string,
+ type: T
+): Promise
+export function processOutputBBReferences<
T = FieldType.BB_REFERENCE,
TS = FieldSubtype.USER
>(value: string, type: T, subtype: TS): Promise
-export async function processOutputBBReferences<
+export function processOutputBBReferences<
T = FieldType.BB_REFERENCE,
TS = FieldSubtype.USERS
>(value: string[], type: T, subtype: TS): Promise
@@ -143,7 +147,7 @@ export async function processOutputBBReferences(
case undefined:
throw "Subtype must be defined"
case FieldSubtype.USER:
- case FieldSubtype.USERS:
+ case FieldSubtype.USERS: {
const { users } = await cache.user.getUsers(ids)
if (!users.length) {
return undefined
@@ -156,6 +160,7 @@ export async function processOutputBBReferences(
firstName: u.firstName,
lastName: u.lastName,
}))
+ }
default:
throw utils.unreachable(subtype)
}
From e0216bb00ccefc09fd81a2d18655a6a6b3357073 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Wed, 24 Apr 2024 09:50:54 +0200
Subject: [PATCH 114/338] Lint
---
.../src/middleware/authenticated.ts | 27 ++++++++++++-------
.../DataTable/modals/CreateEditColumn.svelte | 2 +-
2 files changed, 19 insertions(+), 10 deletions(-)
diff --git a/packages/backend-core/src/middleware/authenticated.ts b/packages/backend-core/src/middleware/authenticated.ts
index 69dba27c43..17aadbcaea 100644
--- a/packages/backend-core/src/middleware/authenticated.ts
+++ b/packages/backend-core/src/middleware/authenticated.ts
@@ -13,7 +13,7 @@ import { getGlobalDB, doInTenant } from "../context"
import { decrypt } from "../security/encryption"
import * as identity from "../context/identity"
import env from "../environment"
-import { Ctx, EndpointMatcher, SessionCookie } from "@budibase/types"
+import { Ctx, EndpointMatcher, SessionCookie, User } from "@budibase/types"
import { InvalidAPIKeyError, ErrorCode } from "../errors"
import tracer from "dd-trace"
@@ -41,7 +41,10 @@ function finalise(ctx: any, opts: FinaliseOpts = {}) {
ctx.version = opts.version
}
-async function checkApiKey(apiKey: string, populateUser?: Function) {
+async function checkApiKey(
+ apiKey: string,
+ populateUser?: (userId: string, tenantId: string) => Promise
+) {
// check both the primary and the fallback internal api keys
// this allows for rotation
if (isValidInternalAPIKey(apiKey)) {
@@ -128,7 +131,7 @@ export default function (
} else {
user = await getUser(userId, session.tenantId)
}
- user.csrfToken = session.csrfToken
+ ;(user as any).csrfToken = session.csrfToken
if (session?.lastAccessedAt < timeMinusOneMinute()) {
// make sure we denote that the session is still in use
@@ -167,19 +170,25 @@ export default function (
authenticated = false
}
- if (user) {
+ const isUser = (
+ user: any
+ ): user is User & { budibaseAccess?: string } => {
+ return user && user.email
+ }
+
+ if (isUser(user)) {
tracer.setUser({
- id: user?._id,
- tenantId: user?.tenantId,
- budibaseAccess: user?.budibaseAccess,
- status: user?.status,
+ id: user._id!,
+ tenantId: user.tenantId,
+ budibaseAccess: user.budibaseAccess,
+ status: user.status,
})
}
// isAuthenticated is a function, so use a variable to be able to check authed state
finalise(ctx, { authenticated, user, internal, version, publicEndpoint })
- if (user && user.email) {
+ if (isUser(user)) {
return identity.doInUserContext(user, ctx, next)
} else {
return next()
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index 02e3d40362..905abaa9e3 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -29,7 +29,7 @@
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
import { getBindings } from "components/backend/DataTable/formula"
import JSONSchemaModal from "./JSONSchemaModal.svelte"
- import { FieldType, FieldSubtype, SourceName } from "@budibase/types"
+ import { FieldType, SourceName } from "@budibase/types"
import RelationshipSelector from "components/common/RelationshipSelector.svelte"
import { RowUtils } from "@budibase/frontend-core"
import ServerBindingPanel from "components/common/bindings/ServerBindingPanel.svelte"
From 558c43bb77928e582307a8ee2cbf1782152f6757 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Wed, 24 Apr 2024 10:08:03 +0200
Subject: [PATCH 115/338] Rename
---
packages/builder/src/constants/backend/index.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.js
index 1996395420..3c9569579e 100644
--- a/packages/builder/src/constants/backend/index.js
+++ b/packages/builder/src/constants/backend/index.js
@@ -176,7 +176,7 @@ export const FIELDS = {
],
},
USERS: {
- name: "Users",
+ name: "User List",
type: FieldType.BB_REFERENCE,
subtype: FieldSubtype.USERS,
icon: TypeIconMap[FieldType.BB_REFERENCE][
From 6999758105a8587f703619badeef1914085ce369 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Wed, 24 Apr 2024 10:28:33 +0200
Subject: [PATCH 116/338] Display single user form
---
packages/client/manifest.json | 108 ++++++++++++++++++
.../app/blocks/FormBlockComponent.svelte | 1 +
.../app/forms/BBReferenceField.svelte | 5 +-
.../app/forms/BBReferenceSingleField.svelte | 6 +
.../app/forms/RelationshipField.svelte | 10 +-
.../client/src/components/app/forms/index.js | 1 +
6 files changed, 125 insertions(+), 6 deletions(-)
create mode 100644 packages/client/src/components/app/forms/BBReferenceSingleField.svelte
diff --git a/packages/client/manifest.json b/packages/client/manifest.json
index bf73286933..f970e9f7c1 100644
--- a/packages/client/manifest.json
+++ b/packages/client/manifest.json
@@ -7123,5 +7123,113 @@
]
}
]
+ },
+ "bbreferencesinglefield": {
+ "devComment": "As bb reference is only used for user subtype for now, we are using user for icon and labels",
+ "name": "User Field",
+ "icon": "User",
+ "styles": ["size"],
+ "requiredAncestors": ["form"],
+ "editable": true,
+ "size": {
+ "width": 400,
+ "height": 50
+ },
+ "settings": [
+ {
+ "type": "field/bb_reference_single",
+ "label": "Field",
+ "key": "field",
+ "required": true
+ },
+ {
+ "type": "text",
+ "label": "Label",
+ "key": "label"
+ },
+ {
+ "type": "text",
+ "label": "Placeholder",
+ "key": "placeholder"
+ },
+ {
+ "type": "text",
+ "label": "Default value",
+ "key": "defaultValue"
+ },
+ {
+ "type": "text",
+ "label": "Help text",
+ "key": "helpText"
+ },
+ {
+ "type": "event",
+ "label": "On change",
+ "key": "onChange",
+ "context": [
+ {
+ "label": "Field Value",
+ "key": "value"
+ }
+ ]
+ },
+ {
+ "type": "validation/link",
+ "label": "Validation",
+ "key": "validation"
+ },
+ {
+ "type": "boolean",
+ "label": "Search",
+ "key": "autocomplete",
+ "defaultValue": true
+ },
+ {
+ "type": "boolean",
+ "label": "Disabled",
+ "key": "disabled",
+ "defaultValue": false
+ },
+ {
+ "type": "boolean",
+ "label": "Read only",
+ "key": "readonly",
+ "defaultValue": false,
+ "dependsOn": {
+ "setting": "disabled",
+ "value": true,
+ "invert": true
+ }
+ },
+ {
+ "type": "select",
+ "label": "Layout",
+ "key": "span",
+ "defaultValue": 6,
+ "hidden": true,
+ "showInBar": true,
+ "barStyle": "buttons",
+ "options": [
+ {
+ "label": "1 column",
+ "value": 6,
+ "barIcon": "Stop",
+ "barTitle": "1 column"
+ },
+ {
+ "label": "2 columns",
+ "value": 3,
+ "barIcon": "ColumnTwoA",
+ "barTitle": "2 columns"
+ },
+ {
+ "label": "3 columns",
+ "value": 2,
+ "barIcon": "ViewColumn",
+ "barTitle": "3 columns"
+ }
+ ]
+ }
+ ]
}
}
diff --git a/packages/client/src/components/app/blocks/FormBlockComponent.svelte b/packages/client/src/components/app/blocks/FormBlockComponent.svelte
index 968ed36b8b..31610f79ac 100644
--- a/packages/client/src/components/app/blocks/FormBlockComponent.svelte
+++ b/packages/client/src/components/app/blocks/FormBlockComponent.svelte
@@ -21,6 +21,7 @@
[FieldType.JSON]: "jsonfield",
[FieldType.BARCODEQR]: "codescanner",
[FieldType.BB_REFERENCE]: "bbreferencefield",
+ [FieldType.BB_REFERENCE_SINGLE]: "bbreferencesinglefield",
}
const getFieldSchema = field => {
diff --git a/packages/client/src/components/app/forms/BBReferenceField.svelte b/packages/client/src/components/app/forms/BBReferenceField.svelte
index 5f00c503c2..c266268275 100644
--- a/packages/client/src/components/app/forms/BBReferenceField.svelte
+++ b/packages/client/src/components/app/forms/BBReferenceField.svelte
@@ -1,8 +1,10 @@
+
+
diff --git a/packages/client/src/components/app/forms/RelationshipField.svelte b/packages/client/src/components/app/forms/RelationshipField.svelte
index 1fbd0df522..39109f368a 100644
--- a/packages/client/src/components/app/forms/RelationshipField.svelte
+++ b/packages/client/src/components/app/forms/RelationshipField.svelte
@@ -1,9 +1,9 @@
Date: Thu, 25 Apr 2024 13:38:31 +0200
Subject: [PATCH 120/338] Refactor
---
.../server/src/utilities/rowProcessor/bbReferenceProcessor.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
index 758769113c..dcefd9c032 100644
--- a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
+++ b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
@@ -109,7 +109,7 @@ export async function processInputBBReferences<
throw new InvalidBBRefError(id, FieldSubtype.USER)
}
- return user._id || null
+ return user._id!
}
default:
From bdb3e3056d2a6e29369780528bef9322b2888c84 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Thu, 25 Apr 2024 12:49:25 +0100
Subject: [PATCH 121/338] Final updates to position dropdown rewrite with
support for wrapping when being responsive to screen width
---
.../bbui/src/Actions/position_dropdown.js | 209 +++++++++++++-----
.../Form/Core/DatePicker/DatePicker.svelte | 2 +-
packages/bbui/src/Popover/Popover.svelte | 6 +-
.../components/grid/cells/HeaderCell.svelte | 8 +-
.../grid/layout/NewColumnButton.svelte | 1 +
.../grid/overlays/GridPopover.svelte | 5 +-
6 files changed, 169 insertions(+), 62 deletions(-)
diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js
index 5ce35d15e0..8630c22770 100644
--- a/packages/bbui/src/Actions/position_dropdown.js
+++ b/packages/bbui/src/Actions/position_dropdown.js
@@ -1,3 +1,24 @@
+/**
+ * Valid alignment options are
+ * - left
+ * - right
+ * - left-outside
+ * - right-outside
+ **/
+
+import { off } from "process"
+
+// Strategies are defined as [Popover]To[Anchor].
+// They can apply for both horizontal and vertical alignment.
+const Strategies = {
+ StartToStart: "StartToStart", // e.g. left alignment
+ EndToEnd: "EndToEnd", // e.g. right alignment
+ StartToEnd: "StartToEnd", // e.g. right-outside alignment
+ EndToStart: "EndToStart", // e.g. left-outside alignment
+ MidPoint: "MidPoint", // centers relative to midpoints
+ ScreenEdge: "ScreenEdge", // locks to screen edge
+}
+
export default function positionDropdown(element, opts) {
let resizeObserver
let latestOpts = opts
@@ -19,7 +40,8 @@ export default function positionDropdown(element, opts) {
useAnchorWidth,
offset = 5,
customUpdate,
- noShrink,
+ fitToScreen,
+ wrap,
} = opts
if (!anchor) {
return
@@ -28,85 +50,158 @@ export default function positionDropdown(element, opts) {
// Compute bounds
const anchorBounds = anchor.getBoundingClientRect()
const elementBounds = element.getBoundingClientRect()
- const padding = 8
+ const winWidth = window.innerWidth
+ const winHeight = window.innerHeight
+ const screenOffset = 8
let styles = {
- maxHeight: null,
- minWidth,
- maxWidth,
+ maxHeight,
+ minWidth: useAnchorWidth ? anchorBounds.width : minWidth,
+ maxWidth: useAnchorWidth ? anchorBounds.width : maxWidth,
left: null,
top: null,
}
+ // Ignore all our logic for custom logic
if (typeof customUpdate === "function") {
styles = customUpdate(anchorBounds, elementBounds, {
...styles,
offset: opts.offset,
})
- } else {
- // "outside" alignment (popover vertical center = anchor vertical center)
- if (align === "right-outside" || align === "left-outside") {
- styles.top =
- anchorBounds.top + anchorBounds.height / 2 - elementBounds.height / 2
- styles.maxHeight = maxHeight
+ }
+
+ // Otherwise position ourselves as normal
+ else {
+ // Checks if we overflow off the screen. We only report that we overflow
+ // when the alternative dimension is larger than the one we are checking.
+ const doesXOverflow = () => {
+ const overflows = styles.left + elementBounds.width > winWidth
+ return overflows && anchorBounds.left > winWidth - anchorBounds.right
+ }
+ const doesYOverflow = () => {
+ const overflows = styles.top + elementBounds.height > winHeight
+ return overflows && anchorBounds.top > winHeight - anchorBounds.bottom
}
- // Normal left/right alignment (top popover edge = botom anchor edge)
- else {
- styles.top = anchorBounds.bottom + offset
- styles.maxHeight = maxHeight || window.innerHeight - styles.top
+ // Applies a dynamic max height constraint if appropriate
+ const applyMaxHeight = height => {
+ if (!styles.maxHeight && fitToScreen) {
+ styles.maxHeight = height
+ }
}
- // Determine horizontal styles
- // Use anchor width if required
- if (!maxWidth && useAnchorWidth) {
- styles.maxWidth = anchorBounds.width
- }
- if (useAnchorWidth) {
- styles.minWidth = anchorBounds.width
+ // Applies the X strategy to our styles
+ const applyXStrategy = strategy => {
+ switch (strategy) {
+ case Strategies.StartToStart:
+ default:
+ styles.left = anchorBounds.left
+ break
+ case Strategies.EndToEnd:
+ styles.left = anchorBounds.right - elementBounds.width
+ break
+ case Strategies.StartToEnd:
+ styles.left = anchorBounds.right + offset
+ break
+ case Strategies.EndToStart:
+ styles.left = anchorBounds.left - elementBounds.width - offset
+ break
+ case Strategies.MidPoint:
+ styles.left =
+ anchorBounds.left +
+ anchorBounds.width / 2 -
+ elementBounds.width / 2
+ break
+ case Strategies.ScreenEdge:
+ styles.left = winWidth - elementBounds.width - screenOffset
+ break
+ }
}
- // Right alignment (right popover edge = right anchor edge)
+ // Applies the Y strategy to our styles
+ const applyYStrategy = strategy => {
+ switch (strategy) {
+ case Strategies.StartToStart:
+ styles.top = anchorBounds.top
+ applyMaxHeight(winHeight - anchorBounds.top - screenOffset)
+ break
+ case Strategies.EndToEnd:
+ styles.top = anchorBounds.bottom - elementBounds.height
+ applyMaxHeight(anchorBounds.bottom - screenOffset)
+ break
+ case Strategies.StartToEnd:
+ default:
+ styles.top = anchorBounds.bottom + offset
+ applyMaxHeight(winHeight - anchorBounds.bottom - screenOffset)
+ break
+ case Strategies.EndToStart:
+ styles.top = anchorBounds.top - elementBounds.height - offset
+ applyMaxHeight(anchorBounds.top - screenOffset)
+ break
+ case Strategies.MidPoint:
+ styles.top =
+ anchorBounds.top +
+ anchorBounds.height / 2 -
+ elementBounds.height / 2
+ break
+ case Strategies.ScreenEdge:
+ styles.top = winHeight - elementBounds.height - screenOffset
+ applyMaxHeight(winHeight - 2 * screenOffset)
+ break
+ }
+ }
+
+ // Determine X strategy
if (align === "right") {
- styles.left =
- anchorBounds.left + anchorBounds.width - elementBounds.width
+ applyXStrategy(Strategies.EndToEnd)
+ } else if (align === "right-outside") {
+ applyXStrategy(Strategies.StartToEnd)
+ } else if (align === "left-outside") {
+ applyXStrategy(Strategies.EndToStart)
+ } else {
+ applyXStrategy(Strategies.StartToStart)
}
- // Right outside alignment (left popover edge = right anchor edge)
- else if (align === "right-outside") {
- styles.left = anchorBounds.right + offset
- }
-
- // Left outside alignment (right popover edge = left anchor edge)
- else if (align === "left-outside") {
- styles.left = anchorBounds.left - elementBounds.width - offset
- }
-
- // Left alignment by default (left popover edge = left anchor edge)
- else {
- styles.left = anchorBounds.left
- }
-
- // Remove max height restriction if we don't want to shrink
- if (noShrink) {
- delete styles.maxHeight
+ // Determine Y strategy
+ if (align === "right-outside" || align === "left-outside") {
+ applyYStrategy(Strategies.MidPoint)
+ } else {
+ applyYStrategy(Strategies.StartToEnd)
}
// Handle screen overflow
- // Check right overflow
- if (styles.left + elementBounds.width > window.innerWidth) {
- styles.left = window.innerWidth - elementBounds.width - padding
+ if (doesXOverflow()) {
+ // Swap left to right
+ if (align === "left") {
+ applyXStrategy(Strategies.EndToEnd)
+ }
+ // Swap right-outside to left-outside
+ else if (align === "right-outside") {
+ applyXStrategy(Strategies.EndToStart)
+ }
}
- // Check bottom overflow
- if (styles.top + elementBounds.height > window.innerHeight) {
- styles.top = window.innerHeight - elementBounds.height - padding
-
- // If we overflowed off the bottom and therefore locked to the bottom
- // edge, we might now be covering the anchor. Therefore we can try
- // moving left or right to reveal the full anchor again.
- if (anchorBounds.right + elementBounds.width < window.innerWidth) {
- styles.left = anchorBounds.right
- } else if (anchorBounds.left - elementBounds.width > 0) {
- styles.left = anchorBounds.left - elementBounds.width
+ if (doesYOverflow()) {
+ // If wrapping, lock to the bottom of the screen and also reposition to
+ // the side to not block the anchor
+ if (wrap) {
+ applyYStrategy(Strategies.MidPoint)
+ if (doesYOverflow()) {
+ applyYStrategy(Strategies.ScreenEdge)
+ }
+ applyXStrategy(Strategies.StartToEnd)
+ if (doesXOverflow()) {
+ applyXStrategy(Strategies.EndToStart)
+ }
+ }
+ // Othewise invert as normal
+ else {
+ // If using an outside strategy then lock to the bottom of the screen
+ if (align === "left-outside" || align === "right-outside") {
+ applyYStrategy(Strategies.ScreenEdge)
+ }
+ // Otherwise flip above
+ else {
+ applyYStrategy(Strategies.EndToStart)
+ }
}
}
}
diff --git a/packages/bbui/src/Form/Core/DatePicker/DatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker/DatePicker.svelte
index 4587195865..28f1b1c9e0 100644
--- a/packages/bbui/src/Form/Core/DatePicker/DatePicker.svelte
+++ b/packages/bbui/src/Form/Core/DatePicker/DatePicker.svelte
@@ -66,9 +66,9 @@
on:open={onOpen}
on:close={onClose}
portalTarget={appendTo}
- maxHeight={null}
{anchor}
{align}
+ fitToScreen={false}
>
{#if isOpen}
{},
diff --git a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
index da9d179904..6cc21fb6f5 100644
--- a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
@@ -303,7 +303,13 @@
{#if open}
-
+
{#if editIsOpen}
diff --git a/packages/frontend-core/src/components/grid/layout/NewColumnButton.svelte b/packages/frontend-core/src/components/grid/layout/NewColumnButton.svelte
index 970a645a74..3e2a3cf128 100644
--- a/packages/frontend-core/src/components/grid/layout/NewColumnButton.svelte
+++ b/packages/frontend-core/src/components/grid/layout/NewColumnButton.svelte
@@ -39,6 +39,7 @@
align={$visibleColumns.length ? "right" : "left"}
on:close={close}
maxHeight={null}
+ fitToScreen
>
diff --git a/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte b/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte
index d8b8b0fdae..06fb4f04a5 100644
--- a/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte
+++ b/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte
@@ -13,6 +13,8 @@
export let maxHeight = PopoverMaxHeight
export let align = "left"
export let open = true
+ export let fitToScreen = false
+ export let wrap = true
const { gridID } = getContext("grid")
const dispatch = createEventDispatcher()
@@ -38,7 +40,8 @@
{open}
{anchor}
{align}
- noShrink
+ {fitToScreen}
+ {wrap}
portalTarget="#{gridID} .grid-popover-container"
offset={0}
>
From 4615d9c61ce8e267fa4dd874150d716e10ba9dad Mon Sep 17 00:00:00 2001
From: jvcalderon
Date: Thu, 25 Apr 2024 13:51:52 +0200
Subject: [PATCH 122/338] Restore yarn.lock
---
yarn.lock | 8 --------
1 file changed, 8 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index 8580ef9657..30b275c434 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5930,14 +5930,6 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
-"@types/readable-stream@^4.0.0":
- version "4.0.11"
- resolved "https://registry.yarnpkg.com/@types/readable-stream/-/readable-stream-4.0.11.tgz#684f1e947c90cb6a8ad3904523d650bb66cdbb84"
- integrity sha512-R3eUMUTTKoIoaz7UpYLxvZCrOmCRPRbAmoDDHKcimTEySltaJhF8hLzj4+EzyDifiX5eK6oDQGSfmNnXjxZzYQ==
- dependencies:
- "@types/node" "*"
- safe-buffer "~5.1.1"
-
"@types/readdir-glob@*":
version "1.1.5"
resolved "https://registry.yarnpkg.com/@types/readdir-glob/-/readdir-glob-1.1.5.tgz#21a4a98898fc606cb568ad815f2a0eedc24d412a"
From 5f0a7c5aac705dc22f631ff8d6385bacbd285262 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 25 Apr 2024 13:58:56 +0200
Subject: [PATCH 123/338] Cleans
---
.../app/forms/RelationshipField.svelte | 25 ++++++++-----------
1 file changed, 11 insertions(+), 14 deletions(-)
diff --git a/packages/client/src/components/app/forms/RelationshipField.svelte b/packages/client/src/components/app/forms/RelationshipField.svelte
index e59a77c8c2..a85b636851 100644
--- a/packages/client/src/components/app/forms/RelationshipField.svelte
+++ b/packages/client/src/components/app/forms/RelationshipField.svelte
@@ -51,7 +51,6 @@
? flatten(fieldState?.value) ?? []
: flatten(fieldState?.value)?.[0]
$: component = multiselect ? CoreMultiselect : CoreSelect
- $: expandedDefaultValue = expand(defaultValue)
$: primaryDisplay = primaryDisplay || tableDefinition?.primaryDisplay
let optionsObj
@@ -115,7 +114,6 @@
const forceFetchRows = async () => {
fieldApi?.setValue([])
- selectedValue = []
debouncedFetchRows(searchTerm, primaryDisplay, defaultValue)
}
const fetchRows = async (searchTerm, primaryDisplay, defaultVal) => {
@@ -157,6 +155,7 @@
if (!values) {
return []
}
+
if (!Array.isArray(values)) {
values = [values]
}
@@ -170,22 +169,20 @@
return row?.[primaryDisplay] || "-"
}
- const expand = values => {
- if (!values) {
- return []
- }
- if (Array.isArray(values)) {
- return values
- }
- return values.split(",").map(value => value.trim())
- }
-
const handleChange = e => {
let value = e.detail
- if (!multiselect && type !== FieldType.BB_REFERENCE_SINGLE) {
+ if (!multiselect) {
value = value == null ? [] : [value]
}
+ if (
+ type === FieldType.BB_REFERENCE_SINGLE &&
+ value &&
+ Array.isArray(value)
+ ) {
+ value = value[0]
+ }
+
const changed = fieldApi.setValue(value)
if (onChange && changed) {
onChange({
@@ -207,7 +204,7 @@
{disabled}
{readonly}
{validation}
- defaultValue={expandedDefaultValue}
+ {defaultValue}
{type}
{span}
{helpText}
From 9259385adefd4661efc8bc6371cd76a1670fb708 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 25 Apr 2024 14:03:04 +0200
Subject: [PATCH 124/338] Fix clean
---
.../client/src/components/app/forms/RelationshipField.svelte | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/client/src/components/app/forms/RelationshipField.svelte b/packages/client/src/components/app/forms/RelationshipField.svelte
index a85b636851..9439fd2239 100644
--- a/packages/client/src/components/app/forms/RelationshipField.svelte
+++ b/packages/client/src/components/app/forms/RelationshipField.svelte
@@ -180,7 +180,7 @@
value &&
Array.isArray(value)
) {
- value = value[0]
+ value = value[0] || null
}
const changed = fieldApi.setValue(value)
From 8ac58fc0e7dc6c66a1edb3b038b88f9a6d6b2586 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Thu, 25 Apr 2024 13:09:08 +0100
Subject: [PATCH 125/338] Lint
---
packages/bbui/src/Actions/position_dropdown.js | 2 --
1 file changed, 2 deletions(-)
diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js
index 8630c22770..ef82387a23 100644
--- a/packages/bbui/src/Actions/position_dropdown.js
+++ b/packages/bbui/src/Actions/position_dropdown.js
@@ -6,8 +6,6 @@
* - right-outside
**/
-import { off } from "process"
-
// Strategies are defined as [Popover]To[Anchor].
// They can apply for both horizontal and vertical alignment.
const Strategies = {
From 7f4ffc46d7c66462553463b601a0e89122aa585d Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 25 Apr 2024 14:27:06 +0200
Subject: [PATCH 126/338] "Add component" config
---
packages/client/manifest.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/client/manifest.json b/packages/client/manifest.json
index f970e9f7c1..9364e4b3f6 100644
--- a/packages/client/manifest.json
+++ b/packages/client/manifest.json
@@ -4226,7 +4226,7 @@
]
},
"attachmentfield": {
- "name": "Attachment list",
+ "name": "Attachment List",
"icon": "Attach",
"styles": ["size"],
"requiredAncestors": ["form"],
@@ -7018,8 +7018,8 @@
},
"bbreferencefield": {
"devComment": "As bb reference is only used for user subtype for now, we are using user for icon and labels",
- "name": "User Field",
- "icon": "User",
+ "name": "User List Field",
+ "icon": "UserGroup",
"styles": ["size"],
"requiredAncestors": ["form"],
"editable": true,
From 2a690c3cdf23cc51ff3dd19dcf86f3c41b97843e Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 25 Apr 2024 14:31:46 +0200
Subject: [PATCH 127/338] Don't allow users on spreadsheets
---
.../backend/DataTable/modals/CreateEditColumn.svelte | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index 905abaa9e3..69a7417ef4 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -226,7 +226,7 @@
editableColumn.autocolumn
)
- allowedTypes = getAllowedTypes().map(t => ({
+ allowedTypes = getAllowedTypes(datasource).map(t => ({
fieldId: makeFieldId(t.type, t.subtype),
...t,
}))
@@ -355,7 +355,7 @@
deleteColName = ""
}
- function getAllowedTypes() {
+ function getAllowedTypes(datasource) {
if (originalName) {
const possibleTypes = SWITCHABLE_TYPES[field.type] || [
editableColumn.type,
@@ -397,8 +397,11 @@
FIELDS.FORMULA,
FIELDS.BIGINT,
FIELDS.USER,
- FIELDS.USERS,
]
+
+ if (datasource && datasource.source !== SourceName.GOOGLE_SHEETS) {
+ fields.push(FIELDS.USERS)
+ }
// no-sql or a spreadsheet
if (!externalTable || table.sql) {
fields = [...fields, FIELDS.LINK, FIELDS.ARRAY]
From 2edf3392a10ff3f685f8dfa75b7a9d1c940a3ade Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 25 Apr 2024 14:31:56 +0200
Subject: [PATCH 128/338] Fix design
---
.../design/settings/controls/FieldConfiguration/utils.js | 1 +
.../[componentId]/new/_components/componentStructure.json | 1 +
2 files changed, 2 insertions(+)
diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/utils.js b/packages/builder/src/components/design/settings/controls/FieldConfiguration/utils.js
index d0f9afda40..d41967061c 100644
--- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/utils.js
+++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/utils.js
@@ -47,4 +47,5 @@ export const FieldTypeToComponentMap = {
[FieldType.JSON]: "jsonfield",
[FieldType.BARCODEQR]: "codescanner",
[FieldType.BB_REFERENCE]: "bbreferencefield",
+ [FieldType.BB_REFERENCE_SINGLE]: "bbreferencesinglefield",
}
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json
index 03e22acd4d..0f85d2e3e3 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json
@@ -71,6 +71,7 @@
"multifieldselect",
"s3upload",
"codescanner",
+ "bbreferencesinglefield",
"bbreferencefield"
]
},
From 55014e304ed5dae3f9c306e8b10109b2f9e4355f Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Thu, 25 Apr 2024 14:26:01 +0100
Subject: [PATCH 129/338] Fix issue with dates and blur grids when interacting
with header cells
---
packages/bbui/src/helpers.js | 5 +++++
.../src/components/grid/cells/HeaderCell.svelte | 1 +
.../components/grid/layout/NewColumnButton.svelte | 15 ++++++++++-----
.../src/components/grid/stores/reorder.js | 2 --
4 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/packages/bbui/src/helpers.js b/packages/bbui/src/helpers.js
index 4448527fea..78bd14fe13 100644
--- a/packages/bbui/src/helpers.js
+++ b/packages/bbui/src/helpers.js
@@ -179,6 +179,11 @@ export const stringifyDate = (
const day = `${value.date()}`.padStart(2, "0")
return `${year}-${month}-${day}T00:00:00.000`
}
+
+ // Otherwise use a normal ISO string with time and timezone
+ else {
+ return value.toISOString()
+ }
}
// Formats a dayjs date according to schema flags
diff --git a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
index ed742530ba..51b520a4ea 100644
--- a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
@@ -104,6 +104,7 @@
}
const onMouseDown = e => {
+ ui.actions.blur()
if ((e.touches?.length || e.button === 0) && orderable) {
timeout = setTimeout(() => {
reorder.actions.startReordering(column.name, e)
diff --git a/packages/frontend-core/src/components/grid/layout/NewColumnButton.svelte b/packages/frontend-core/src/components/grid/layout/NewColumnButton.svelte
index 3e2a3cf128..84d45dc3ed 100644
--- a/packages/frontend-core/src/components/grid/layout/NewColumnButton.svelte
+++ b/packages/frontend-core/src/components/grid/layout/NewColumnButton.svelte
@@ -3,10 +3,10 @@
import { Icon } from "@budibase/bbui"
import GridPopover from "../overlays/GridPopover.svelte"
- const { visibleColumns, scroll, width, subscribe } = getContext("grid")
+ const { visibleColumns, scroll, width, subscribe, ui } = getContext("grid")
let anchor
- let open = false
+ let isOpen = false
$: columnsWidth = $visibleColumns.reduce(
(total, col) => (total += col.width),
@@ -15,8 +15,13 @@
$: end = columnsWidth - 1 - $scroll.left
$: left = Math.min($width - 40, end)
+ const open = () => {
+ ui.actions.blur()
+ isOpen = true
+ }
+
const close = () => {
- open = false
+ isOpen = false
}
onMount(() => subscribe("close-edit-column", close))
@@ -29,11 +34,11 @@
bind:this={anchor}
class="add"
style="left:{left}px"
- on:click={() => (open = true)}
+ on:click={open}
>
-{#if open}
+{#if isOpen}
{
scroll,
bounds,
stickyColumn,
- ui,
maxScrollLeft,
width,
} = context
@@ -45,7 +44,6 @@ export const createActions = context => {
const $visibleColumns = get(visibleColumns)
const $bounds = get(bounds)
const $stickyColumn = get(stickyColumn)
- ui.actions.blur()
// Generate new breakpoints for the current columns
let breakpoints = $visibleColumns.map(col => ({
From 6e4baf72159e15559f803454ac99fadf641a9544 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 25 Apr 2024 15:50:28 +0200
Subject: [PATCH 130/338] Fix import validation
---
.../ExistingTableDataImport.svelte | 4 +++
.../rowProcessor/bbReferenceProcessor.ts | 13 +++++++-
packages/server/src/utilities/schema.ts | 30 ++++++++++++-------
3 files changed, 36 insertions(+), 11 deletions(-)
diff --git a/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte b/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte
index 6901503071..3323a4122a 100644
--- a/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte
+++ b/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte
@@ -66,6 +66,10 @@
label: "Users",
value: `${FieldType.BB_REFERENCE}${FieldSubtype.USERS}`,
},
+ {
+ label: "User",
+ value: `${FieldType.BB_REFERENCE_SINGLE}${FieldSubtype.USER}`,
+ },
]
$: {
diff --git a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
index dcefd9c032..1a4cd831b1 100644
--- a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
+++ b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
@@ -176,7 +176,18 @@ export async function processOutputBBReferences(
}
case FieldType.BB_REFERENCE_SINGLE:
- const user = await cache.user.getUser(value as string)
+ if (!value) {
+ return undefined
+ }
+
+ let user
+ try {
+ user = await cache.user.getUser(value as string)
+ } catch (err: any) {
+ if (err.code !== 404) {
+ throw err
+ }
+ }
if (!user) {
return undefined
}
diff --git a/packages/server/src/utilities/schema.ts b/packages/server/src/utilities/schema.ts
index 156ea5ea7b..ae77abd2e4 100644
--- a/packages/server/src/utilities/schema.ts
+++ b/packages/server/src/utilities/schema.ts
@@ -94,7 +94,7 @@ export function validate(rows: Rows, schema: TableSchema): ValidationResults {
} else if (
(columnType === FieldType.BB_REFERENCE ||
columnType === FieldType.BB_REFERENCE_SINGLE) &&
- !isValidBBReference(columnData, columnSubtype)
+ !isValidBBReference(columnData, columnType, columnSubtype)
) {
results.schemaValidation[columnName] = false
} else {
@@ -164,21 +164,31 @@ export function parse(rows: Rows, schema: TableSchema): Rows {
}
function isValidBBReference(
- columnData: any,
- columnSubtype: FieldSubtype.USER | FieldSubtype.USERS
+ data: any,
+ type: FieldType.BB_REFERENCE | FieldType.BB_REFERENCE_SINGLE,
+ subtype: FieldSubtype.USER | FieldSubtype.USERS
): boolean {
- switch (columnSubtype) {
+ if (typeof data !== "string") {
+ return false
+ }
+
+ if (type === FieldType.BB_REFERENCE_SINGLE) {
+ if (!data) {
+ return true
+ }
+ const user = parseCsvExport<{ _id: string }>(data)
+ return db.isGlobalUserID(user._id)
+ }
+
+ switch (subtype) {
case FieldSubtype.USER:
case FieldSubtype.USERS: {
- if (typeof columnData !== "string") {
- return false
- }
- const userArray = parseCsvExport<{ _id: string }[]>(columnData)
+ const userArray = parseCsvExport<{ _id: string }[]>(data)
if (!Array.isArray(userArray)) {
return false
}
- if (columnSubtype === FieldSubtype.USER && userArray.length > 1) {
+ if (subtype === FieldSubtype.USER && userArray.length > 1) {
return false
}
@@ -188,6 +198,6 @@ function isValidBBReference(
return !constainsWrongId
}
default:
- throw utils.unreachable(columnSubtype)
+ throw utils.unreachable(subtype)
}
}
From b3e7080215a17ba3dd199ed8872c4cc0faab0a74 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 25 Apr 2024 16:31:15 +0200
Subject: [PATCH 131/338] Fix import single user
---
packages/server/src/utilities/schema.ts | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/packages/server/src/utilities/schema.ts b/packages/server/src/utilities/schema.ts
index ae77abd2e4..150d6cf74a 100644
--- a/packages/server/src/utilities/schema.ts
+++ b/packages/server/src/utilities/schema.ts
@@ -148,6 +148,10 @@ export function parse(rows: Rows, schema: TableSchema): Rows {
utils.unreachable(columnSubtype)
}
}
+ } else if (columnType === FieldType.BB_REFERENCE_SINGLE) {
+ const parsedValue =
+ columnData && parseCsvExport<{ _id: string }>(columnData)
+ parsedRow[columnName] = parsedValue?._id
} else if (
(columnType === FieldType.ATTACHMENTS ||
columnType === FieldType.ATTACHMENT_SINGLE) &&
From 1c4fc2187075848c70ee1b3bb553958522a894e1 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 25 Apr 2024 16:36:47 +0200
Subject: [PATCH 132/338] Lint
---
packages/backend-core/src/middleware/authenticated.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/backend-core/src/middleware/authenticated.ts b/packages/backend-core/src/middleware/authenticated.ts
index 17aadbcaea..51cd4ec2af 100644
--- a/packages/backend-core/src/middleware/authenticated.ts
+++ b/packages/backend-core/src/middleware/authenticated.ts
@@ -131,7 +131,8 @@ export default function (
} else {
user = await getUser(userId, session.tenantId)
}
- ;(user as any).csrfToken = session.csrfToken
+ // @ts-ignore
+ user.csrfToken = session.csrfToken
if (session?.lastAccessedAt < timeMinusOneMinute()) {
// make sure we denote that the session is still in use
From 4b0e3895264ef8e737206d9b1409c6d6c80c0954 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 25 Apr 2024 16:40:13 +0200
Subject: [PATCH 133/338] Lint
---
.eslintrc.json | 3 ++-
.../utilities/rowProcessor/bbReferenceProcessor.ts | 11 ++++++-----
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/.eslintrc.json b/.eslintrc.json
index 2c810eecc5..c2b7cbb836 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -53,7 +53,8 @@
"ignoreRestSiblings": true
}
],
- "local-rules/no-budibase-imports": "error"
+ "no-redeclare": "off",
+ "@typescript-eslint/no-redeclare": "error"
}
},
{
diff --git a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
index 1a4cd831b1..e028359d60 100644
--- a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
+++ b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
@@ -40,7 +40,7 @@ export async function processInputBBReferences<
subtype?: TS
): Promise {
switch (type) {
- case FieldType.BB_REFERENCE:
+ case FieldType.BB_REFERENCE: {
let referenceIds: string[] = []
if (Array.isArray(value)) {
@@ -95,7 +95,7 @@ export async function processInputBBReferences<
default:
throw utils.unreachable(subtype)
}
-
+ }
case FieldType.BB_REFERENCE_SINGLE: {
if (value && Array.isArray(value)) {
throw "BB_REFERENCE_SINGLE cannot be an array"
@@ -149,7 +149,7 @@ export async function processOutputBBReferences(
}
switch (type) {
- case FieldType.BB_REFERENCE:
+ case FieldType.BB_REFERENCE: {
const ids =
typeof value === "string" ? value.split(",").filter(id => !!id) : value
@@ -174,8 +174,8 @@ export async function processOutputBBReferences(
default:
throw utils.unreachable(subtype)
}
-
- case FieldType.BB_REFERENCE_SINGLE:
+ }
+ case FieldType.BB_REFERENCE_SINGLE: {
if (!value) {
return undefined
}
@@ -199,6 +199,7 @@ export async function processOutputBBReferences(
firstName: user.firstName,
lastName: user.lastName,
}
+ }
default:
throw utils.unreachable(type)
From 92b3cb2d92ae857f6597c1d6ad62abbea1e07dc9 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 25 Apr 2024 16:57:47 +0200
Subject: [PATCH 134/338] Add comments
---
packages/types/src/documents/app/row.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/types/src/documents/app/row.ts b/packages/types/src/documents/app/row.ts
index ca9b11ffe5..e85400804b 100644
--- a/packages/types/src/documents/app/row.ts
+++ b/packages/types/src/documents/app/row.ts
@@ -100,14 +100,16 @@ export enum FieldType {
*/
BIGINT = "bigint",
/**
- * a JSON type, called User within Budibase. This type is used to represent a link to an internal Budibase
+ * an JSON type, called Users within Budibase. It will hold an array of strings. This type is used to represent a link to an internal Budibase
* resource, like a user or group, today only users are supported. This type will be represented as an
* array of internal resource IDs (e.g. user IDs) within the row - this ID list will be enriched with
* the full resources when rows are returned from the API. The full resources can be input to the API, or
* an array of resource IDs, the API will squash these down and validate them before saving the row.
*/
BB_REFERENCE = "bb_reference",
- // TODO
+ /**
+ * a string type, called User within Budibase. Same logic as `bb_reference`, storing a single id as string instead of an array
+ */
BB_REFERENCE_SINGLE = "bb_reference_single",
}
From ddbb6765efae5d769ba24372b321c6b005384a67 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Thu, 25 Apr 2024 16:38:54 +0100
Subject: [PATCH 135/338] Remove leftover prop
---
packages/bbui/src/Form/DatePicker.svelte | 2 --
1 file changed, 2 deletions(-)
diff --git a/packages/bbui/src/Form/DatePicker.svelte b/packages/bbui/src/Form/DatePicker.svelte
index cf249e5bb8..ec3dfdfd2c 100644
--- a/packages/bbui/src/Form/DatePicker.svelte
+++ b/packages/bbui/src/Form/DatePicker.svelte
@@ -11,7 +11,6 @@
export let error = null
export let enableTime = true
export let timeOnly = false
- export let time24hr = false
export let placeholder = null
export let appendTo = undefined
export let ignoreTimezones = false
@@ -34,7 +33,6 @@
{placeholder}
{enableTime}
{timeOnly}
- {time24hr}
{appendTo}
{ignoreTimezones}
on:change={onChange}
From 8b2156ed08e1a4c38c383d2d4344fbbdb004072b Mon Sep 17 00:00:00 2001
From: Sam Rose
Date: Thu, 25 Apr 2024 16:41:02 +0100
Subject: [PATCH 136/338] Add more AUTO tests.
---
.../src/api/routes/tests/search.spec.ts | 82 ++++++++++++++++---
.../server/src/sdk/app/tables/internal/sqs.ts | 2 +-
2 files changed, 71 insertions(+), 13 deletions(-)
diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts
index cc3bde3a07..0e52879022 100644
--- a/packages/server/src/api/routes/tests/search.spec.ts
+++ b/packages/server/src/api/routes/tests/search.spec.ts
@@ -19,15 +19,16 @@ import _ from "lodash"
jest.unmock("mssql")
describe.each([
- // ["internal", undefined],
- // ["internal-sqs", undefined],
- [DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
+ // ["lucene", undefined],
+ ["sqs", undefined],
+ // [DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
// [DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)],
// [DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)],
// [DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)],
])("/api/:sourceId/search (%s)", (name, dsProvider) => {
- const isSqs = name === "internal-sqs"
- const isInternal = name === "internal"
+ const isSqs = name === "sqs"
+ const isLucene = name === "lucene"
+ const isInternal = isSqs || isLucene
const config = setup.getConfig()
let envCleanup: (() => void) | undefined
@@ -60,7 +61,7 @@ describe.each([
}
async function createRows(rows: Record[]) {
- await Promise.all(rows.map(r => config.api.row.save(table._id!, r)))
+ await config.api.row.bulkImport(table._id!, { rows })
}
class SearchAssertion {
@@ -340,14 +341,14 @@ describe.each([
}).toFindNothing())
// We never implemented half-open ranges in Lucene.
- !isInternal &&
+ !isLucene &&
it("can search using just a low value", () =>
expectQuery({
range: { age: { low: 5 } },
}).toContainExactly([{ age: 10 }]))
// We never implemented half-open ranges in Lucene.
- !isInternal &&
+ !isLucene &&
it("can search using just a high value", () =>
expectQuery({
range: { age: { high: 5 } },
@@ -458,14 +459,14 @@ describe.each([
}).toFindNothing())
// We never implemented half-open ranges in Lucene.
- !isInternal &&
+ !isLucene &&
it("can search using just a low value", () =>
expectQuery({
range: { dob: { low: JAN_5TH } },
}).toContainExactly([{ dob: JAN_10TH }]))
// We never implemented half-open ranges in Lucene.
- !isInternal &&
+ !isLucene &&
it("can search using just a high value", () =>
expectQuery({
range: { dob: { high: JAN_5TH } },
@@ -643,7 +644,7 @@ describe.each([
// Range searches against bigints don't seem to work at all in Lucene, and I
// couldn't figure out why. Given that we're replacing Lucene with SQS,
// we've decided not to spend time on it.
- !isInternal &&
+ !isLucene &&
describe("range", () => {
it("successfully finds a row", () =>
expectQuery({
@@ -678,7 +679,7 @@ describe.each([
})
isInternal &&
- describe.only("auto", () => {
+ describe("auto", () => {
beforeAll(async () => {
await createTable({
auto: {
@@ -698,5 +699,62 @@ describe.each([
it("fails to find nonexistent row", () =>
expectQuery({ equal: { auto: 0 } }).toFindNothing())
})
+
+ describe("not equal", () => {
+ it("successfully finds a row", () =>
+ expectQuery({ notEqual: { auto: 1 } }).toContainExactly([
+ { auto: 2 },
+ { auto: 3 },
+ ]))
+
+ it("fails to find nonexistent row", () =>
+ expectQuery({ notEqual: { auto: 0 } }).toContainExactly([
+ { auto: 1 },
+ { auto: 2 },
+ { auto: 3 },
+ ]))
+ })
+
+ describe("oneOf", () => {
+ it("successfully finds a row", () =>
+ expectQuery({ oneOf: { auto: [1] } }).toContainExactly([{ auto: 1 }]))
+
+ it("fails to find nonexistent row", () =>
+ expectQuery({ oneOf: { auto: [0] } }).toFindNothing())
+ })
+
+ describe("range", () => {
+ it("successfully finds a row", () =>
+ expectQuery({
+ range: { auto: { low: 1, high: 1 } },
+ }).toContainExactly([{ auto: 1 }]))
+
+ it("successfully finds multiple rows", () =>
+ expectQuery({
+ range: { auto: { low: 1, high: 2 } },
+ }).toContainExactly([{ auto: 1 }, { auto: 2 }]))
+
+ it("successfully finds a row with a high bound", () =>
+ expectQuery({
+ range: { auto: { low: 2, high: 2 } },
+ }).toContainExactly([{ auto: 2 }]))
+
+ it("successfully finds no rows", () =>
+ expectQuery({
+ range: { auto: { low: 0, high: 0 } },
+ }).toFindNothing())
+
+ isSqs &&
+ it("can search using just a low value", () =>
+ expectQuery({
+ range: { auto: { low: 2 } },
+ }).toContainExactly([{ auto: 2 }, { auto: 3 }]))
+
+ isSqs &&
+ it("can search using just a high value", () =>
+ expectQuery({
+ range: { auto: { high: 2 } },
+ }).toContainExactly([{ auto: 1 }, { auto: 2 }]))
+ })
})
})
diff --git a/packages/server/src/sdk/app/tables/internal/sqs.ts b/packages/server/src/sdk/app/tables/internal/sqs.ts
index 0726c94962..65d44b9af4 100644
--- a/packages/server/src/sdk/app/tables/internal/sqs.ts
+++ b/packages/server/src/sdk/app/tables/internal/sqs.ts
@@ -33,7 +33,7 @@ const FieldTypeMap: Record = {
[FieldType.LONGFORM]: SQLiteType.TEXT,
[FieldType.NUMBER]: SQLiteType.REAL,
[FieldType.STRING]: SQLiteType.TEXT,
- [FieldType.AUTO]: SQLiteType.TEXT,
+ [FieldType.AUTO]: SQLiteType.REAL,
[FieldType.OPTIONS]: SQLiteType.TEXT,
[FieldType.JSON]: SQLiteType.BLOB,
[FieldType.INTERNAL]: SQLiteType.BLOB,
From 8f058756c319ca73513c32aa979331c21e42eb9d Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Thu, 25 Apr 2024 16:43:51 +0100
Subject: [PATCH 137/338] Rename date picker flag to be more meaningful and
ensure action menus never shrink
---
packages/bbui/src/ActionMenu/ActionMenu.svelte | 10 +++++++++-
packages/bbui/src/Actions/position_dropdown.js | 4 ++--
.../bbui/src/Form/Core/DatePicker/DatePicker.svelte | 2 +-
packages/bbui/src/Popover/Popover.svelte | 4 ++--
.../src/components/grid/cells/HeaderCell.svelte | 2 +-
.../src/components/grid/layout/NewColumnButton.svelte | 2 +-
.../src/components/grid/overlays/GridPopover.svelte | 4 ++--
7 files changed, 18 insertions(+), 10 deletions(-)
diff --git a/packages/bbui/src/ActionMenu/ActionMenu.svelte b/packages/bbui/src/ActionMenu/ActionMenu.svelte
index c55d1cb43d..75ddd679da 100644
--- a/packages/bbui/src/ActionMenu/ActionMenu.svelte
+++ b/packages/bbui/src/ActionMenu/ActionMenu.svelte
@@ -38,7 +38,15 @@
-
+
diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js
index ef82387a23..6c4fcab757 100644
--- a/packages/bbui/src/Actions/position_dropdown.js
+++ b/packages/bbui/src/Actions/position_dropdown.js
@@ -38,7 +38,7 @@ export default function positionDropdown(element, opts) {
useAnchorWidth,
offset = 5,
customUpdate,
- fitToScreen,
+ resizable,
wrap,
} = opts
if (!anchor) {
@@ -82,7 +82,7 @@ export default function positionDropdown(element, opts) {
// Applies a dynamic max height constraint if appropriate
const applyMaxHeight = height => {
- if (!styles.maxHeight && fitToScreen) {
+ if (!styles.maxHeight && resizable) {
styles.maxHeight = height
}
}
diff --git a/packages/bbui/src/Form/Core/DatePicker/DatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker/DatePicker.svelte
index 28f1b1c9e0..f5189c9edd 100644
--- a/packages/bbui/src/Form/Core/DatePicker/DatePicker.svelte
+++ b/packages/bbui/src/Form/Core/DatePicker/DatePicker.svelte
@@ -68,7 +68,7 @@
portalTarget={appendTo}
{anchor}
{align}
- fitToScreen={false}
+ resizable={false}
>
{#if isOpen}
{#if editIsOpen}
diff --git a/packages/frontend-core/src/components/grid/layout/NewColumnButton.svelte b/packages/frontend-core/src/components/grid/layout/NewColumnButton.svelte
index 84d45dc3ed..de68dea644 100644
--- a/packages/frontend-core/src/components/grid/layout/NewColumnButton.svelte
+++ b/packages/frontend-core/src/components/grid/layout/NewColumnButton.svelte
@@ -44,7 +44,7 @@
align={$visibleColumns.length ? "right" : "left"}
on:close={close}
maxHeight={null}
- fitToScreen
+ resizable
>
diff --git a/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte b/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte
index 06fb4f04a5..ac2a998428 100644
--- a/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte
+++ b/packages/frontend-core/src/components/grid/overlays/GridPopover.svelte
@@ -13,7 +13,7 @@
export let maxHeight = PopoverMaxHeight
export let align = "left"
export let open = true
- export let fitToScreen = false
+ export let resizable = false
export let wrap = true
const { gridID } = getContext("grid")
@@ -40,7 +40,7 @@
{open}
{anchor}
{align}
- {fitToScreen}
+ {resizable}
{wrap}
portalTarget="#{gridID} .grid-popover-container"
offset={0}
From b07db7b0980bc260d3b053b61783de6247a25e77 Mon Sep 17 00:00:00 2001
From: Sam Rose
Date: Thu, 25 Apr 2024 16:51:42 +0100
Subject: [PATCH 138/338] Make sure we're treating AUTO as numbers.
---
.../src/api/routes/tests/search.spec.ts | 63 +++++++++++++++++--
1 file changed, 59 insertions(+), 4 deletions(-)
diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts
index 0e52879022..c20e4e36c8 100644
--- a/packages/server/src/api/routes/tests/search.spec.ts
+++ b/packages/server/src/api/routes/tests/search.spec.ts
@@ -61,7 +61,10 @@ describe.each([
}
async function createRows(rows: Record[]) {
- await config.api.row.bulkImport(table._id!, { rows })
+ // await config.api.row.bulkImport(table._id!, { rows })
+ for (const row of rows) {
+ await config.api.row.save(table._id!, row)
+ }
}
class SearchAssertion {
@@ -689,7 +692,7 @@ describe.each([
subtype: AutoFieldSubType.AUTO_ID,
},
})
- await createRows([{}, {}, {}])
+ await createRows(new Array(10).fill({}))
})
describe("equal", () => {
@@ -705,6 +708,13 @@ describe.each([
expectQuery({ notEqual: { auto: 1 } }).toContainExactly([
{ auto: 2 },
{ auto: 3 },
+ { auto: 4 },
+ { auto: 5 },
+ { auto: 6 },
+ { auto: 7 },
+ { auto: 8 },
+ { auto: 9 },
+ { auto: 10 },
]))
it("fails to find nonexistent row", () =>
@@ -712,6 +722,13 @@ describe.each([
{ auto: 1 },
{ auto: 2 },
{ auto: 3 },
+ { auto: 4 },
+ { auto: 5 },
+ { auto: 6 },
+ { auto: 7 },
+ { auto: 8 },
+ { auto: 9 },
+ { auto: 10 },
]))
})
@@ -747,8 +764,8 @@ describe.each([
isSqs &&
it("can search using just a low value", () =>
expectQuery({
- range: { auto: { low: 2 } },
- }).toContainExactly([{ auto: 2 }, { auto: 3 }]))
+ range: { auto: { low: 9 } },
+ }).toContainExactly([{ auto: 9 }, { auto: 10 }]))
isSqs &&
it("can search using just a high value", () =>
@@ -756,5 +773,43 @@ describe.each([
range: { auto: { high: 2 } },
}).toContainExactly([{ auto: 1 }, { auto: 2 }]))
})
+
+ describe("sort", () => {
+ it("sorts ascending", () =>
+ expectSearch({
+ query: {},
+ sort: "auto",
+ sortOrder: SortOrder.ASCENDING,
+ }).toMatchExactly([
+ { auto: 1 },
+ { auto: 2 },
+ { auto: 3 },
+ { auto: 4 },
+ { auto: 5 },
+ { auto: 6 },
+ { auto: 7 },
+ { auto: 8 },
+ { auto: 9 },
+ { auto: 10 },
+ ]))
+
+ it("sorts descending", () =>
+ expectSearch({
+ query: {},
+ sort: "auto",
+ sortOrder: SortOrder.DESCENDING,
+ }).toMatchExactly([
+ { auto: 10 },
+ { auto: 9 },
+ { auto: 8 },
+ { auto: 7 },
+ { auto: 6 },
+ { auto: 5 },
+ { auto: 4 },
+ { auto: 3 },
+ { auto: 2 },
+ { auto: 1 },
+ ]))
+ })
})
})
From e15345030fdaaad8409303a8d8e086aa2ef7ba85 Mon Sep 17 00:00:00 2001
From: Sam Rose
Date: Thu, 25 Apr 2024 16:53:21 +0100
Subject: [PATCH 139/338] Uncomment tests.
---
.../src/api/routes/tests/search.spec.ts | 83 ++++++++++---------
1 file changed, 42 insertions(+), 41 deletions(-)
diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts
index c20e4e36c8..d323019f8e 100644
--- a/packages/server/src/api/routes/tests/search.spec.ts
+++ b/packages/server/src/api/routes/tests/search.spec.ts
@@ -19,12 +19,12 @@ import _ from "lodash"
jest.unmock("mssql")
describe.each([
- // ["lucene", undefined],
+ ["lucene", undefined],
["sqs", undefined],
- // [DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
- // [DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)],
- // [DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)],
- // [DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)],
+ [DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
+ [DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)],
+ [DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)],
+ [DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)],
])("/api/:sourceId/search (%s)", (name, dsProvider) => {
const isSqs = name === "sqs"
const isLucene = name === "lucene"
@@ -774,42 +774,43 @@ describe.each([
}).toContainExactly([{ auto: 1 }, { auto: 2 }]))
})
- describe("sort", () => {
- it("sorts ascending", () =>
- expectSearch({
- query: {},
- sort: "auto",
- sortOrder: SortOrder.ASCENDING,
- }).toMatchExactly([
- { auto: 1 },
- { auto: 2 },
- { auto: 3 },
- { auto: 4 },
- { auto: 5 },
- { auto: 6 },
- { auto: 7 },
- { auto: 8 },
- { auto: 9 },
- { auto: 10 },
- ]))
+ isSqs &&
+ describe("sort", () => {
+ it("sorts ascending", () =>
+ expectSearch({
+ query: {},
+ sort: "auto",
+ sortOrder: SortOrder.ASCENDING,
+ }).toMatchExactly([
+ { auto: 1 },
+ { auto: 2 },
+ { auto: 3 },
+ { auto: 4 },
+ { auto: 5 },
+ { auto: 6 },
+ { auto: 7 },
+ { auto: 8 },
+ { auto: 9 },
+ { auto: 10 },
+ ]))
- it("sorts descending", () =>
- expectSearch({
- query: {},
- sort: "auto",
- sortOrder: SortOrder.DESCENDING,
- }).toMatchExactly([
- { auto: 10 },
- { auto: 9 },
- { auto: 8 },
- { auto: 7 },
- { auto: 6 },
- { auto: 5 },
- { auto: 4 },
- { auto: 3 },
- { auto: 2 },
- { auto: 1 },
- ]))
- })
+ it("sorts descending", () =>
+ expectSearch({
+ query: {},
+ sort: "auto",
+ sortOrder: SortOrder.DESCENDING,
+ }).toMatchExactly([
+ { auto: 10 },
+ { auto: 9 },
+ { auto: 8 },
+ { auto: 7 },
+ { auto: 6 },
+ { auto: 5 },
+ { auto: 4 },
+ { auto: 3 },
+ { auto: 2 },
+ { auto: 1 },
+ ]))
+ })
})
})
From cefaa228e100c1131f12b5c37e5224dbbf562396 Mon Sep 17 00:00:00 2001
From: Sam Rose
Date: Thu, 25 Apr 2024 17:19:25 +0100
Subject: [PATCH 140/338] Delete commented out line.
---
packages/server/src/api/routes/tests/search.spec.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts
index d323019f8e..931510a88f 100644
--- a/packages/server/src/api/routes/tests/search.spec.ts
+++ b/packages/server/src/api/routes/tests/search.spec.ts
@@ -61,7 +61,6 @@ describe.each([
}
async function createRows(rows: Record[]) {
- // await config.api.row.bulkImport(table._id!, { rows })
for (const row of rows) {
await config.api.row.save(table._id!, row)
}
From 22a67eb7690c4a2344f5d6c92e3b901a7b7f7392 Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Thu, 25 Apr 2024 18:43:54 +0100
Subject: [PATCH 141/338] Matching rollup version across all packages, there
was a lot of mis-match and I was running into some build issues due to this,
getting everything on the same version so that they can all use the same
package - this also requires updating all .js extensions to .mjs to correctly
support the imports.
---
packages/bbui/package.json | 2 +-
.../bbui/{rollup.config.js => rollup.config.mjs} | 0
.../{rollup.config.js => rollup.config.mjs} | 0
packages/pro | 2 +-
.../sdk/{rollup.config.js => rollup.config.mjs} | 0
packages/string-templates/package.json | 2 +-
.../{rollup.config.js => rollup.config.mjs} | 0
yarn.lock | 16 +---------------
8 files changed, 4 insertions(+), 18 deletions(-)
rename packages/bbui/{rollup.config.js => rollup.config.mjs} (100%)
rename packages/client/{rollup.config.js => rollup.config.mjs} (100%)
rename packages/sdk/{rollup.config.js => rollup.config.mjs} (100%)
rename packages/string-templates/{rollup.config.js => rollup.config.mjs} (100%)
diff --git a/packages/bbui/package.json b/packages/bbui/package.json
index a1baa2a38b..2dbce9668d 100644
--- a/packages/bbui/package.json
+++ b/packages/bbui/package.json
@@ -21,7 +21,7 @@
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^11.2.1",
"postcss": "^8.2.9",
- "rollup": "^2.45.2",
+ "rollup": "^4.9.6",
"rollup-plugin-postcss": "^4.0.0",
"rollup-plugin-svelte": "^7.1.0",
"rollup-plugin-terser": "^7.0.2"
diff --git a/packages/bbui/rollup.config.js b/packages/bbui/rollup.config.mjs
similarity index 100%
rename from packages/bbui/rollup.config.js
rename to packages/bbui/rollup.config.mjs
diff --git a/packages/client/rollup.config.js b/packages/client/rollup.config.mjs
similarity index 100%
rename from packages/client/rollup.config.js
rename to packages/client/rollup.config.mjs
diff --git a/packages/pro b/packages/pro
index dff7b5a9dd..1dccc9edf1 160000
--- a/packages/pro
+++ b/packages/pro
@@ -1 +1 @@
-Subproject commit dff7b5a9dd1fd770f8a48fb8e6df1740be605f18
+Subproject commit 1dccc9edf111d5880548aed2294219eccce37aea
diff --git a/packages/sdk/rollup.config.js b/packages/sdk/rollup.config.mjs
similarity index 100%
rename from packages/sdk/rollup.config.js
rename to packages/sdk/rollup.config.mjs
diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json
index 8cf8d92692..5d99313b39 100644
--- a/packages/string-templates/package.json
+++ b/packages/string-templates/package.json
@@ -38,7 +38,7 @@
"doctrine": "^3.0.0",
"jest": "29.7.0",
"marked": "^4.0.10",
- "rollup": "^2.36.2",
+ "rollup": "^4.9.6",
"rollup-plugin-inject-process-env": "^1.3.1",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-globals": "^1.4.0",
diff --git a/packages/string-templates/rollup.config.js b/packages/string-templates/rollup.config.mjs
similarity index 100%
rename from packages/string-templates/rollup.config.js
rename to packages/string-templates/rollup.config.mjs
diff --git a/yarn.lock b/yarn.lock
index 727a927df4..8fb958b76b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11590,7 +11590,7 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
-fsevents@^2.3.2, fsevents@~2.3.1, fsevents@~2.3.2:
+fsevents@^2.3.2, fsevents@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
@@ -19714,20 +19714,6 @@ rollup-pluginutils@^2.3.1, rollup-pluginutils@^2.5.0, rollup-pluginutils@^2.8.1,
dependencies:
estree-walker "^0.6.1"
-rollup@2.45.2:
- version "2.45.2"
- resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.45.2.tgz#8fb85917c9f35605720e92328f3ccbfba6f78b48"
- integrity sha512-kRRU7wXzFHUzBIv0GfoFFIN3m9oteY4uAsKllIpQDId5cfnkWF2J130l+27dzDju0E6MScKiV0ZM5Bw8m4blYQ==
- optionalDependencies:
- fsevents "~2.3.1"
-
-rollup@^2.36.2, rollup@^2.45.2:
- version "2.79.1"
- resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7"
- integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==
- optionalDependencies:
- fsevents "~2.3.2"
-
rollup@^3.27.1:
version "3.29.4"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981"
From fe68abe6de6c2d5eeb650eae5ce58953672b6d47 Mon Sep 17 00:00:00 2001
From: jvcalderon
Date: Fri, 26 Apr 2024 08:55:49 +0200
Subject: [PATCH 142/338] Upgrade pro and account-portal submodules
---
packages/account-portal | 2 +-
packages/pro | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/account-portal b/packages/account-portal
index a9508c96ef..44437254b3 160000
--- a/packages/account-portal
+++ b/packages/account-portal
@@ -1 +1 @@
-Subproject commit a9508c96efb7bf056c3988d9a71e763c6e4a7b83
+Subproject commit 44437254b3340781d8c2af95d9f0bbac0a83b2b7
diff --git a/packages/pro b/packages/pro
index e3e8578a48..01bec5657e 160000
--- a/packages/pro
+++ b/packages/pro
@@ -1 +1 @@
-Subproject commit e3e8578a480a31365f11fdfbf7b0c383c056f9b4
+Subproject commit 01bec5657e0c3c3bb29e883e6ac71258fee8710b
From 34d97ab16af0924d64da5027db246c8de590f8a5 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 26 Apr 2024 09:15:00 +0200
Subject: [PATCH 143/338] Fix switch
---
packages/server/src/integrations/base/sqlTable.ts | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/packages/server/src/integrations/base/sqlTable.ts b/packages/server/src/integrations/base/sqlTable.ts
index 087f068386..18c9a0c189 100644
--- a/packages/server/src/integrations/base/sqlTable.ts
+++ b/packages/server/src/integrations/base/sqlTable.ts
@@ -54,7 +54,8 @@ function generateSchema(
) {
continue
}
- switch (column.type) {
+ const columnType = column.type
+ switch (columnType) {
case FieldType.STRING:
case FieldType.OPTIONS:
case FieldType.LONGFORM:
@@ -128,8 +129,18 @@ function generateSchema(
.references(`${tableName}.${relatedPrimary}`)
}
break
+ case FieldType.FORMULA:
+ // This is allowed, but nothing to do on the external datasource
+ break
+ case FieldType.ATTACHMENTS:
+ case FieldType.ATTACHMENT_SINGLE:
+ case FieldType.AUTO:
+ case FieldType.JSON:
+ case FieldType.INTERNAL:
+ throw `${column.type} is not a valid SQL type`
+
default:
- utils.unreachable(column.type)
+ utils.unreachable(columnType)
}
}
From d32623c340dff9014cf9b116aa2cacdc33d5ddff Mon Sep 17 00:00:00 2001
From: jvcalderon
Date: Fri, 26 Apr 2024 09:15:36 +0200
Subject: [PATCH 144/338] Restore account portal submodule
---
packages/account-portal | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/account-portal b/packages/account-portal
index 44437254b3..c167c331ff 160000
--- a/packages/account-portal
+++ b/packages/account-portal
@@ -1 +1 @@
-Subproject commit 44437254b3340781d8c2af95d9f0bbac0a83b2b7
+Subproject commit c167c331ff9b8161fc18e2ecbaaf1ea5815ba964
From ad44b7ab819f2f105b50d2ce41e9dc279657067b Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 26 Apr 2024 09:47:46 +0200
Subject: [PATCH 145/338] Fix types
---
.../rowProcessor/bbReferenceProcessor.ts | 51 +++++----------
.../src/utilities/rowProcessor/index.ts | 23 ++++---
.../tests/bbReferenceProcessor.spec.ts | 63 +++++++++++++++----
3 files changed, 83 insertions(+), 54 deletions(-)
diff --git a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
index e028359d60..d9464df3ef 100644
--- a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
+++ b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts
@@ -10,34 +10,20 @@ import { InvalidBBRefError } from "./errors"
const ROW_PREFIX = DocumentType.ROW + SEPARATOR
-export function processInputBBReferences(
- value: string,
- type: T
+export function processInputBBReferences(
+ value: string | { _id: string },
+ type: FieldType.BB_REFERENCE_SINGLE
): Promise
-export function processInputBBReferences<
- T = FieldType.BB_REFERENCE,
- TS = FieldSubtype.USER
->(
+export function processInputBBReferences(
value: string | string[] | { _id: string } | { _id: string }[],
- type: T,
- subtype: TS
+ type: FieldType.BB_REFERENCE,
+ subtype: FieldSubtype.USER | FieldSubtype.USERS
): Promise
-export function processInputBBReferences<
- T = FieldType.BB_REFERENCE,
- TS = FieldSubtype.USERS
->(
- value: string | string[] | { _id: string } | { _id: string }[],
- type: T,
- subtype: TS
-): Promise
-export async function processInputBBReferences<
- T extends FieldType.BB_REFERENCE | FieldType.BB_REFERENCE_SINGLE,
- TS extends FieldSubtype.USER | FieldSubtype.USERS
->(
+export async function processInputBBReferences(
value: string | string[] | { _id: string } | { _id: string }[],
- type: T,
- subtype?: TS
+ type: FieldType.BB_REFERENCE | FieldType.BB_REFERENCE_SINGLE,
+ subtype?: FieldSubtype
): Promise {
switch (type) {
case FieldType.BB_REFERENCE: {
@@ -125,23 +111,20 @@ interface UserReferenceInfo {
lastName: string
}
-export function processOutputBBReferences(
+export function processOutputBBReferences(
value: string,
- type: T
+ type: FieldType.BB_REFERENCE_SINGLE
): Promise
-export function processOutputBBReferences<
- T = FieldType.BB_REFERENCE,
- TS = FieldSubtype.USER
->(value: string, type: T, subtype: TS): Promise
-export function processOutputBBReferences<
- T = FieldType.BB_REFERENCE,
- TS = FieldSubtype.USERS
->(value: string[], type: T, subtype: TS): Promise
+export function processOutputBBReferences(
+ value: string,
+ type: FieldType.BB_REFERENCE,
+ subtype: FieldSubtype.USER | FieldSubtype.USERS
+): Promise
export async function processOutputBBReferences(
value: string | string[],
type: FieldType.BB_REFERENCE | FieldType.BB_REFERENCE_SINGLE,
- subtype?: FieldSubtype.USER | FieldSubtype.USERS
+ subtype?: FieldSubtype
) {
if (value === null || value === undefined) {
// Already processed or nothing to process
diff --git a/packages/server/src/utilities/rowProcessor/index.ts b/packages/server/src/utilities/rowProcessor/index.ts
index 57cafef8ad..499ea0e4c5 100644
--- a/packages/server/src/utilities/rowProcessor/index.ts
+++ b/packages/server/src/utilities/rowProcessor/index.ts
@@ -160,18 +160,14 @@ export async function inputProcessing(
if (attachment?.url) {
delete clonedRow[key].url
}
- }
-
- if (
- (field.type === FieldType.BB_REFERENCE ||
- field.type === FieldType.BB_REFERENCE_SINGLE) &&
- value
- ) {
+ } else if (field.type === FieldType.BB_REFERENCE && value) {
clonedRow[key] = await processInputBBReferences(
value,
field.type,
field.subtype
)
+ } else if (field.type === FieldType.BB_REFERENCE_SINGLE && value) {
+ clonedRow[key] = await processInputBBReferences(value, field.type)
}
}
@@ -252,8 +248,7 @@ export async function outputProcessing(
}
} else if (
!opts.skipBBReferences &&
- (column.type == FieldType.BB_REFERENCE ||
- column.type == FieldType.BB_REFERENCE_SINGLE)
+ column.type == FieldType.BB_REFERENCE
) {
for (let row of enriched) {
row[property] = await processOutputBBReferences(
@@ -262,6 +257,16 @@ export async function outputProcessing(
column.subtype
)
}
+ } else if (
+ !opts.skipBBReferences &&
+ column.type == FieldType.BB_REFERENCE_SINGLE
+ ) {
+ for (let row of enriched) {
+ row[property] = await processOutputBBReferences(
+ row[property],
+ column.type
+ )
+ }
}
}
diff --git a/packages/server/src/utilities/rowProcessor/tests/bbReferenceProcessor.spec.ts b/packages/server/src/utilities/rowProcessor/tests/bbReferenceProcessor.spec.ts
index e6ceaae323..c0090ab4e0 100644
--- a/packages/server/src/utilities/rowProcessor/tests/bbReferenceProcessor.spec.ts
+++ b/packages/server/src/utilities/rowProcessor/tests/bbReferenceProcessor.spec.ts
@@ -1,6 +1,6 @@
import _ from "lodash"
import * as backendCore from "@budibase/backend-core"
-import { FieldSubtype, User } from "@budibase/types"
+import { FieldSubtype, FieldType, User } from "@budibase/types"
import {
processInputBBReferences,
processOutputBBReferences,
@@ -63,7 +63,11 @@ describe("bbReferenceProcessor", () => {
const userId = user!._id!
const result = await config.doInTenant(() =>
- processInputBBReferences(userId, FieldSubtype.USER)
+ processInputBBReferences(
+ userId,
+ FieldType.BB_REFERENCE,
+ FieldSubtype.USER
+ )
)
expect(result).toEqual(userId)
@@ -76,7 +80,11 @@ describe("bbReferenceProcessor", () => {
await expect(
config.doInTenant(() =>
- processInputBBReferences(userId, FieldSubtype.USER)
+ processInputBBReferences(
+ userId,
+ FieldType.BB_REFERENCE,
+ FieldSubtype.USER
+ )
)
).rejects.toThrow(new InvalidBBRefError(userId, FieldSubtype.USER))
expect(cacheGetUsersSpy).toHaveBeenCalledTimes(1)
@@ -88,7 +96,11 @@ describe("bbReferenceProcessor", () => {
const userIdCsv = userIds.join(" , ")
const result = await config.doInTenant(() =>
- processInputBBReferences(userIdCsv, FieldSubtype.USER)
+ processInputBBReferences(
+ userIdCsv,
+ FieldType.BB_REFERENCE,
+ FieldSubtype.USER
+ )
)
expect(result).toEqual(userIds.join(","))
@@ -108,7 +120,11 @@ describe("bbReferenceProcessor", () => {
await expect(
config.doInTenant(() =>
- processInputBBReferences(userIdCsv, FieldSubtype.USER)
+ processInputBBReferences(
+ userIdCsv,
+ FieldType.BB_REFERENCE,
+ FieldSubtype.USER
+ )
)
).rejects.toThrow(new InvalidBBRefError(wrongId, FieldSubtype.USER))
})
@@ -117,7 +133,11 @@ describe("bbReferenceProcessor", () => {
const userId = _.sample(users)!._id!
const result = await config.doInTenant(() =>
- processInputBBReferences({ _id: userId }, FieldSubtype.USER)
+ processInputBBReferences(
+ { _id: userId },
+ FieldType.BB_REFERENCE,
+ FieldSubtype.USER
+ )
)
expect(result).toEqual(userId)
@@ -129,7 +149,11 @@ describe("bbReferenceProcessor", () => {
const userIds = _.sampleSize(users, 3).map(x => x._id!)
const result = await config.doInTenant(() =>
- processInputBBReferences(userIds, FieldSubtype.USER)
+ processInputBBReferences(
+ userIds,
+ FieldType.BB_REFERENCE,
+ FieldSubtype.USER
+ )
)
expect(result).toEqual(userIds.join(","))
@@ -139,7 +163,11 @@ describe("bbReferenceProcessor", () => {
it("empty strings will return null", async () => {
const result = await config.doInTenant(() =>
- processInputBBReferences("", FieldSubtype.USER)
+ processInputBBReferences(
+ "",
+ FieldType.BB_REFERENCE,
+ FieldSubtype.USER
+ )
)
expect(result).toEqual(null)
@@ -147,7 +175,11 @@ describe("bbReferenceProcessor", () => {
it("empty arrays will return null", async () => {
const result = await config.doInTenant(() =>
- processInputBBReferences([], FieldSubtype.USER)
+ processInputBBReferences(
+ [],
+ FieldType.BB_REFERENCE,
+ FieldSubtype.USER
+ )
)
expect(result).toEqual(null)
@@ -157,7 +189,11 @@ describe("bbReferenceProcessor", () => {
const userId = _.sample(users)!._id!
const userMetadataId = backendCore.db.generateUserMetadataID(userId)
const result = await config.doInTenant(() =>
- processInputBBReferences(userMetadataId, FieldSubtype.USER)
+ processInputBBReferences(
+ userMetadataId,
+ FieldType.BB_REFERENCE,
+ FieldSubtype.USER
+ )
)
expect(result).toBe(userId)
})
@@ -171,7 +207,11 @@ describe("bbReferenceProcessor", () => {
const userId = user._id!
const result = await config.doInTenant(() =>
- processOutputBBReferences(userId, FieldSubtype.USER)
+ processOutputBBReferences(
+ userId,
+ FieldType.BB_REFERENCE,
+ FieldSubtype.USER
+ )
)
expect(result).toEqual([
@@ -195,6 +235,7 @@ describe("bbReferenceProcessor", () => {
const result = await config.doInTenant(() =>
processOutputBBReferences(
[userId1, userId2].join(","),
+ FieldType.BB_REFERENCE,
FieldSubtype.USER
)
)
From 8314685033a6391b2713a4128bd07563bd583ae4 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 26 Apr 2024 09:58:41 +0200
Subject: [PATCH 146/338] Fix allowed types
---
.../backend/DataTable/modals/CreateEditColumn.svelte | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index 69a7417ef4..8a27e81cd9 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -63,7 +63,6 @@
let savingColumn
let deleteColName
let jsonSchemaModal
- let allowedTypes = []
let editableColumn = {
type: FIELDS.STRING.type,
constraints: FIELDS.STRING.constraints,
@@ -171,6 +170,11 @@
SWITCHABLE_TYPES[field.type] &&
!editableColumn?.autocolumn)
+ $: allowedTypes = getAllowedTypes(datasource).map(t => ({
+ fieldId: makeFieldId(t.type, t.subtype),
+ ...t,
+ }))
+
const fieldDefinitions = Object.values(FIELDS).reduce(
// Storing the fields by complex field id
(acc, field) => ({
@@ -225,11 +229,6 @@
editableColumn.subtype,
editableColumn.autocolumn
)
-
- allowedTypes = getAllowedTypes(datasource).map(t => ({
- fieldId: makeFieldId(t.type, t.subtype),
- ...t,
- }))
}
}
From d3425a856fc9d39f9885ae83656a24ffed27b783 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 26 Apr 2024 10:07:08 +0200
Subject: [PATCH 147/338] Fix test
---
.../src/utilities/rowProcessor/tests/outputProcessing.spec.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts b/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts
index 74d55aff36..c891e88629 100644
--- a/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts
+++ b/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts
@@ -69,6 +69,7 @@ describe("rowProcessor - outputProcessing", () => {
).toHaveBeenCalledTimes(1)
expect(bbReferenceProcessor.processOutputBBReferences).toHaveBeenCalledWith(
"123",
+ FieldType.BB_REFERENCE,
FieldSubtype.USER
)
})
From ba63760f0fd4f12e61c425aa4dfcc6d9aefcd0c5 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Fri, 26 Apr 2024 09:39:17 +0100
Subject: [PATCH 148/338] Display dates in users locale
---
packages/bbui/src/helpers.js | 29 +++++++++++++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/packages/bbui/src/helpers.js b/packages/bbui/src/helpers.js
index 78bd14fe13..fa29d140a0 100644
--- a/packages/bbui/src/helpers.js
+++ b/packages/bbui/src/helpers.js
@@ -186,6 +186,31 @@ export const stringifyDate = (
}
}
+// Gets the correct format for a date in a specific locale
+const getDateFormatPattern = locale => {
+ const getPatternForPart = part => {
+ switch (part.type) {
+ case "day":
+ return "D".repeat(part.value.length)
+ case "month":
+ return "M".repeat(part.value.length)
+ case "year":
+ return "Y".repeat(part.value.length)
+ case "literal":
+ return part.value
+ default:
+ console.log("Unsupported date part", part)
+ return ""
+ }
+ }
+ return new Intl.DateTimeFormat(locale)
+ .formatToParts(new Date("2021-01-01"))
+ .map(getPatternForPart)
+ .join("")
+}
+
+const localeDateFormat = getDateFormatPattern(navigator.language || "en-US")
+
// Formats a dayjs date according to schema flags
export const getDateDisplayValue = (
value,
@@ -197,8 +222,8 @@ export const getDateDisplayValue = (
if (timeOnly) {
return value.format("HH:mm")
} else if (!enableTime) {
- return value.format("MMMM D YYYY")
+ return value.format(localeDateFormat)
} else {
- return value.format("MMMM D YYYY, HH:mm")
+ return value.format(`${localeDateFormat}, HH:mm`)
}
}
From 063bdb1d7b2277480b211098a2c0d1018da0f291 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 26 Apr 2024 10:43:44 +0200
Subject: [PATCH 149/338] Fix type name on formula bindings
---
packages/builder/src/components/backend/DataTable/formula.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/builder/src/components/backend/DataTable/formula.js b/packages/builder/src/components/backend/DataTable/formula.js
index b339729391..c59fb9c536 100644
--- a/packages/builder/src/components/backend/DataTable/formula.js
+++ b/packages/builder/src/components/backend/DataTable/formula.js
@@ -55,7 +55,7 @@ export function getBindings({
)
}
const field = Object.values(FIELDS).find(
- field => field.type === schema.type
+ field => field.type === schema.type && field.subtype === schema.subtype
)
const label = path == null ? column : `${path}.0.${column}`
From 9f3e01ef78865c9109aefe25a3799a3e203bb284 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 26 Apr 2024 11:13:56 +0200
Subject: [PATCH 150/338] Fix binding display types
---
.../automation/SetupPanel/AutomationBlockSetup.svelte | 8 +++++++-
packages/builder/src/dataBinding.js | 8 ++++++++
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
index 2d2022299c..60300434f8 100644
--- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
+++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
@@ -48,6 +48,7 @@
import { TriggerStepID, ActionStepID } from "constants/backend/automations"
import { onMount } from "svelte"
import { cloneDeep } from "lodash/fp"
+ import { FIELDS } from "constants/backend"
export let block
export let testData
@@ -228,6 +229,10 @@
categoryName,
bindingName
) => {
+ const field = Object.values(FIELDS).find(
+ field => field.type === value.type && field.subtype === value.subtype
+ )
+
return {
readableBinding: bindingName
? `${bindingName}.${name}`
@@ -238,7 +243,7 @@
icon,
category: categoryName,
display: {
- type: value.type,
+ type: field?.name || value.type,
name,
rank: isLoopBlock ? idx + 1 : idx - loopBlockCount,
},
@@ -282,6 +287,7 @@
for (const key in table?.schema) {
schema[key] = {
type: table.schema[key].type,
+ subtype: table.schema[key].subtype,
}
}
// remove the original binding
diff --git a/packages/builder/src/dataBinding.js b/packages/builder/src/dataBinding.js
index 5efbb79611..50eb2c1b66 100644
--- a/packages/builder/src/dataBinding.js
+++ b/packages/builder/src/dataBinding.js
@@ -29,6 +29,7 @@ import { JSONUtils, Constants } from "@budibase/frontend-core"
import ActionDefinitions from "components/design/settings/controls/ButtonActionEditor/manifest.json"
import { environment, licensing } from "stores/portal"
import { convertOldFieldFormat } from "components/design/settings/controls/FieldConfiguration/utils"
+import { FIELDS } from "constants/backend"
const { ContextScopes } = Constants
@@ -1019,6 +1020,12 @@ export const getSchemaForDatasource = (asset, datasource, options) => {
// are objects
let fixedSchema = {}
Object.entries(schema || {}).forEach(([fieldName, fieldSchema]) => {
+ const field = Object.values(FIELDS).find(
+ field =>
+ field.type === fieldSchema.type &&
+ field.subtype === fieldSchema.subtype
+ )
+
if (typeof fieldSchema === "string") {
fixedSchema[fieldName] = {
type: fieldSchema,
@@ -1027,6 +1034,7 @@ export const getSchemaForDatasource = (asset, datasource, options) => {
} else {
fixedSchema[fieldName] = {
...fieldSchema,
+ type: field?.name || fieldSchema.name,
name: fieldName,
}
}
From fe226ae54b57a533a8771601facf823d0a4f7f2b Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 26 Apr 2024 11:26:24 +0200
Subject: [PATCH 151/338] Fix test
---
.../src/utilities/rowProcessor/tests/inputProcessing.spec.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts b/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts
index 859a203133..90053a5658 100644
--- a/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts
+++ b/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts
@@ -67,6 +67,7 @@ describe("rowProcessor - inputProcessing", () => {
)
expect(bbReferenceProcessor.processInputBBReferences).toHaveBeenCalledWith(
"123",
+ "bb_reference",
"user"
)
From 61bb40d0d386862e502994cb0482a72e505d3ab3 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 26 Apr 2024 12:09:32 +0200
Subject: [PATCH 152/338] Remove FieldTypeSubtypes
---
packages/builder/src/constants/backend/index.js | 13 +++----------
packages/frontend-core/src/constants.js | 8 ++++----
packages/server/src/api/routes/tests/row.spec.ts | 6 +++---
.../src/sdk/app/rows/search/tests/utils.spec.ts | 6 +++---
.../rowProcessor/tests/inputProcessing.spec.ts | 8 ++++----
.../rowProcessor/tests/outputProcessing.spec.ts | 7 +++----
packages/types/src/documents/app/row.ts | 9 +--------
7 files changed, 21 insertions(+), 36 deletions(-)
diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.js
index 3c9569579e..b726864489 100644
--- a/packages/builder/src/constants/backend/index.js
+++ b/packages/builder/src/constants/backend/index.js
@@ -4,7 +4,6 @@ import {
INTERNAL_TABLE_SOURCE_ID,
AutoFieldSubType,
Hosting,
- FieldTypeSubtypes,
} from "@budibase/types"
import { Constants } from "@budibase/frontend-core"
@@ -162,26 +161,20 @@ export const FIELDS = {
name: "User",
type: FieldType.BB_REFERENCE_SINGLE,
subtype: FieldSubtype.USER,
- icon: TypeIconMap[FieldType.BB_REFERENCE_SINGLE][
- FieldTypeSubtypes.BB_REFERENCE.USER
- ],
+ icon: TypeIconMap[FieldType.BB_REFERENCE_SINGLE][FieldSubtype.USER],
},
// Used for display of editing existing columns
OLD_USER: {
name: "User",
type: FieldType.BB_REFERENCE,
subtype: FieldSubtype.USER,
- icon: TypeIconMap[FieldType.BB_REFERENCE_SINGLE][
- FieldTypeSubtypes.BB_REFERENCE.USER
- ],
+ icon: TypeIconMap[FieldType.BB_REFERENCE_SINGLE][FieldSubtype.USER],
},
USERS: {
name: "User List",
type: FieldType.BB_REFERENCE,
subtype: FieldSubtype.USERS,
- icon: TypeIconMap[FieldType.BB_REFERENCE][
- FieldTypeSubtypes.BB_REFERENCE.USERS
- ],
+ icon: TypeIconMap[FieldType.BB_REFERENCE][FieldSubtype.USERS],
constraints: {
type: "array",
},
diff --git a/packages/frontend-core/src/constants.js b/packages/frontend-core/src/constants.js
index b3b1edac65..87ec88bac1 100644
--- a/packages/frontend-core/src/constants.js
+++ b/packages/frontend-core/src/constants.js
@@ -4,7 +4,7 @@
export { OperatorOptions, SqlNumberTypeRangeMap } from "@budibase/shared-core"
export { Feature as Features } from "@budibase/types"
import { BpmCorrelationKey } from "@budibase/shared-core"
-import { FieldType, FieldTypeSubtypes } from "@budibase/types"
+import { FieldType, FieldSubtype } from "@budibase/types"
// Cookie names
export const Cookies = {
@@ -132,10 +132,10 @@ export const TypeIconMap = {
[FieldType.BIGINT]: "TagBold",
[FieldType.AUTO]: "MagicWand",
[FieldType.BB_REFERENCE]: {
- [FieldTypeSubtypes.BB_REFERENCE.USER]: "User",
- [FieldTypeSubtypes.BB_REFERENCE.USERS]: "UserGroup",
+ [FieldSubtype.USER]: "User",
+ [FieldSubtype.USERS]: "UserGroup",
},
[FieldType.BB_REFERENCE_SINGLE]: {
- [FieldTypeSubtypes.BB_REFERENCE.USER]: "User",
+ [FieldSubtype.USER]: "User",
},
}
diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts
index 8e1014f825..f2ace8f795 100644
--- a/packages/server/src/api/routes/tests/row.spec.ts
+++ b/packages/server/src/api/routes/tests/row.spec.ts
@@ -13,7 +13,7 @@ import {
DeleteRow,
FieldSchema,
FieldType,
- FieldTypeSubtypes,
+ FieldSubtype,
FormulaType,
INTERNAL_TABLE_SOURCE_ID,
NumberFieldMetadata,
@@ -1015,12 +1015,12 @@ describe.each([
user: {
name: "user",
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USER,
+ subtype: FieldSubtype.USER,
},
users: {
name: "users",
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USERS,
+ subtype: FieldSubtype.USERS,
},
}),
() => config.createUser(),
diff --git a/packages/server/src/sdk/app/rows/search/tests/utils.spec.ts b/packages/server/src/sdk/app/rows/search/tests/utils.spec.ts
index bf7799402d..413b5e28cf 100644
--- a/packages/server/src/sdk/app/rows/search/tests/utils.spec.ts
+++ b/packages/server/src/sdk/app/rows/search/tests/utils.spec.ts
@@ -2,7 +2,7 @@ import { searchInputMapping } from "../utils"
import { db as dbCore } from "@budibase/backend-core"
import {
FieldType,
- FieldTypeSubtypes,
+ FieldSubtype,
INTERNAL_TABLE_SOURCE_ID,
RowSearchParams,
Table,
@@ -20,7 +20,7 @@ const tableWithUserCol: Table = {
user: {
name: "user",
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USER,
+ subtype: FieldSubtype.USER,
},
},
}
@@ -35,7 +35,7 @@ const tableWithUsersCol: Table = {
user: {
name: "user",
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USERS,
+ subtype: FieldSubtype.USERS,
},
},
}
diff --git a/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts b/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts
index 90053a5658..715c066cd3 100644
--- a/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts
+++ b/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts
@@ -2,7 +2,7 @@ import { inputProcessing } from ".."
import { generator, structures } from "@budibase/backend-core/tests"
import {
FieldType,
- FieldTypeSubtypes,
+ FieldSubtype,
INTERNAL_TABLE_SOURCE_ID,
Table,
TableSourceType,
@@ -39,7 +39,7 @@ describe("rowProcessor - inputProcessing", () => {
},
user: {
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USER,
+ subtype: FieldSubtype.USER,
name: "user",
constraints: {
presence: true,
@@ -94,7 +94,7 @@ describe("rowProcessor - inputProcessing", () => {
},
user: {
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USER,
+ subtype: FieldSubtype.USER,
name: "user",
constraints: {
presence: false,
@@ -136,7 +136,7 @@ describe("rowProcessor - inputProcessing", () => {
},
user: {
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USER,
+ subtype: FieldSubtype.USER,
name: "user",
constraints: {
presence: false,
diff --git a/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts b/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts
index c891e88629..f4f07d9148 100644
--- a/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts
+++ b/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts
@@ -1,7 +1,6 @@
import {
- FieldSubtype,
FieldType,
- FieldTypeSubtypes,
+ FieldSubtype,
INTERNAL_TABLE_SOURCE_ID,
RowAttachment,
Table,
@@ -42,7 +41,7 @@ describe("rowProcessor - outputProcessing", () => {
},
user: {
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USER,
+ subtype: FieldSubtype.USER,
name: "user",
constraints: {
presence: false,
@@ -176,7 +175,7 @@ describe("rowProcessor - outputProcessing", () => {
},
user: {
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USER,
+ subtype: FieldSubtype.USER,
name: "user",
constraints: {
presence: false,
diff --git a/packages/types/src/documents/app/row.ts b/packages/types/src/documents/app/row.ts
index e85400804b..ab6835bf81 100644
--- a/packages/types/src/documents/app/row.ts
+++ b/packages/types/src/documents/app/row.ts
@@ -131,13 +131,6 @@ export interface Row extends Document {
export enum FieldSubtype {
USER = "user",
+ /** @deprecated this should not be used anymore */
USERS = "users",
}
-
-// The 'as' are required for typescript not to type the outputs as generic FieldSubtype
-export const FieldTypeSubtypes = {
- BB_REFERENCE: {
- USER: FieldSubtype.USER as FieldSubtype.USER,
- USERS: FieldSubtype.USERS as FieldSubtype.USERS,
- },
-}
From 6cf2da794437503c79dbafe9f566db9387ca80bd Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Fri, 26 Apr 2024 11:10:13 +0100
Subject: [PATCH 153/338] Pro version to master.
---
packages/pro | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/pro b/packages/pro
index 7acb40a7b1..5d4ab38fb1 160000
--- a/packages/pro
+++ b/packages/pro
@@ -1 +1 @@
-Subproject commit 7acb40a7b146016571725bd3f272d9587d9a73c5
+Subproject commit 5d4ab38fb197615a5c7bdc14f05f0fccd72766fe
From 637f7f514ae69f1ac9a9c39710ce31c27dc4f573 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 26 Apr 2024 12:18:50 +0200
Subject: [PATCH 154/338] Remove FieldTypeSubtypes
---
packages/frontend-core/src/constants.js | 6 +++---
packages/server/src/api/routes/tests/row.spec.ts | 6 +++---
.../server/src/sdk/app/rows/search/tests/utils.spec.ts | 6 +++---
.../utilities/rowProcessor/tests/inputProcessing.spec.ts | 8 ++++----
.../utilities/rowProcessor/tests/outputProcessing.spec.ts | 7 +++----
packages/types/src/documents/app/row.ts | 8 --------
6 files changed, 16 insertions(+), 25 deletions(-)
diff --git a/packages/frontend-core/src/constants.js b/packages/frontend-core/src/constants.js
index 95228c3bdc..2a9b0379f8 100644
--- a/packages/frontend-core/src/constants.js
+++ b/packages/frontend-core/src/constants.js
@@ -4,7 +4,7 @@
export { OperatorOptions, SqlNumberTypeRangeMap } from "@budibase/shared-core"
export { Feature as Features } from "@budibase/types"
import { BpmCorrelationKey } from "@budibase/shared-core"
-import { FieldType, FieldTypeSubtypes } from "@budibase/types"
+import { FieldType, FieldSubtype } from "@budibase/types"
// Cookie names
export const Cookies = {
@@ -134,7 +134,7 @@ export const TypeIconMap = {
[FieldType.USER]: "User",
[FieldType.USERS]: "UserGroup",
[FieldType.BB_REFERENCE]: {
- [FieldTypeSubtypes.BB_REFERENCE.USER]: "User",
- [FieldTypeSubtypes.BB_REFERENCE.USERS]: "UserGroup",
+ [FieldSubtype.USER]: "User",
+ [FieldSubtype.USERS]: "UserGroup",
},
}
diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts
index 8e1014f825..f2ace8f795 100644
--- a/packages/server/src/api/routes/tests/row.spec.ts
+++ b/packages/server/src/api/routes/tests/row.spec.ts
@@ -13,7 +13,7 @@ import {
DeleteRow,
FieldSchema,
FieldType,
- FieldTypeSubtypes,
+ FieldSubtype,
FormulaType,
INTERNAL_TABLE_SOURCE_ID,
NumberFieldMetadata,
@@ -1015,12 +1015,12 @@ describe.each([
user: {
name: "user",
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USER,
+ subtype: FieldSubtype.USER,
},
users: {
name: "users",
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USERS,
+ subtype: FieldSubtype.USERS,
},
}),
() => config.createUser(),
diff --git a/packages/server/src/sdk/app/rows/search/tests/utils.spec.ts b/packages/server/src/sdk/app/rows/search/tests/utils.spec.ts
index bf7799402d..413b5e28cf 100644
--- a/packages/server/src/sdk/app/rows/search/tests/utils.spec.ts
+++ b/packages/server/src/sdk/app/rows/search/tests/utils.spec.ts
@@ -2,7 +2,7 @@ import { searchInputMapping } from "../utils"
import { db as dbCore } from "@budibase/backend-core"
import {
FieldType,
- FieldTypeSubtypes,
+ FieldSubtype,
INTERNAL_TABLE_SOURCE_ID,
RowSearchParams,
Table,
@@ -20,7 +20,7 @@ const tableWithUserCol: Table = {
user: {
name: "user",
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USER,
+ subtype: FieldSubtype.USER,
},
},
}
@@ -35,7 +35,7 @@ const tableWithUsersCol: Table = {
user: {
name: "user",
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USERS,
+ subtype: FieldSubtype.USERS,
},
},
}
diff --git a/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts b/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts
index 859a203133..47f930803b 100644
--- a/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts
+++ b/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts
@@ -2,7 +2,7 @@ import { inputProcessing } from ".."
import { generator, structures } from "@budibase/backend-core/tests"
import {
FieldType,
- FieldTypeSubtypes,
+ FieldSubtype,
INTERNAL_TABLE_SOURCE_ID,
Table,
TableSourceType,
@@ -39,7 +39,7 @@ describe("rowProcessor - inputProcessing", () => {
},
user: {
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USER,
+ subtype: FieldSubtype.USER,
name: "user",
constraints: {
presence: true,
@@ -93,7 +93,7 @@ describe("rowProcessor - inputProcessing", () => {
},
user: {
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USER,
+ subtype: FieldSubtype.USER,
name: "user",
constraints: {
presence: false,
@@ -135,7 +135,7 @@ describe("rowProcessor - inputProcessing", () => {
},
user: {
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USER,
+ subtype: FieldSubtype.USER,
name: "user",
constraints: {
presence: false,
diff --git a/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts b/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts
index 74d55aff36..7a7c319bb8 100644
--- a/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts
+++ b/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts
@@ -1,7 +1,6 @@
import {
- FieldSubtype,
FieldType,
- FieldTypeSubtypes,
+ FieldSubtype,
INTERNAL_TABLE_SOURCE_ID,
RowAttachment,
Table,
@@ -42,7 +41,7 @@ describe("rowProcessor - outputProcessing", () => {
},
user: {
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USER,
+ subtype: FieldSubtype.USER,
name: "user",
constraints: {
presence: false,
@@ -175,7 +174,7 @@ describe("rowProcessor - outputProcessing", () => {
},
user: {
type: FieldType.BB_REFERENCE,
- subtype: FieldTypeSubtypes.BB_REFERENCE.USER,
+ subtype: FieldSubtype.USER,
name: "user",
constraints: {
presence: false,
diff --git a/packages/types/src/documents/app/row.ts b/packages/types/src/documents/app/row.ts
index 865ab4ba64..b98bd8dc32 100644
--- a/packages/types/src/documents/app/row.ts
+++ b/packages/types/src/documents/app/row.ts
@@ -129,11 +129,3 @@ export enum FieldSubtype {
USER = "user",
USERS = "users",
}
-
-// The 'as' are required for typescript not to type the outputs as generic FieldSubtype
-export const FieldTypeSubtypes = {
- BB_REFERENCE: {
- USER: FieldSubtype.USER as FieldSubtype.USER,
- USERS: FieldSubtype.USERS as FieldSubtype.USERS,
- },
-}
From b1b5b49687b2773f404c0ac61ef0a84622424a4f Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 26 Apr 2024 12:21:53 +0200
Subject: [PATCH 155/338] Move and rename BBReferenceFieldSubType
---
packages/types/src/documents/app/row.ts | 5 -----
packages/types/src/documents/app/table/constants.ts | 5 +++++
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/types/src/documents/app/row.ts b/packages/types/src/documents/app/row.ts
index b98bd8dc32..d6a9bb7842 100644
--- a/packages/types/src/documents/app/row.ts
+++ b/packages/types/src/documents/app/row.ts
@@ -124,8 +124,3 @@ export interface Row extends Document {
_viewId?: string
[key: string]: any
}
-
-export enum FieldSubtype {
- USER = "user",
- USERS = "users",
-}
diff --git a/packages/types/src/documents/app/table/constants.ts b/packages/types/src/documents/app/table/constants.ts
index 1d9d14695a..ddf8b5e6ea 100644
--- a/packages/types/src/documents/app/table/constants.ts
+++ b/packages/types/src/documents/app/table/constants.ts
@@ -24,3 +24,8 @@ export enum FormulaType {
STATIC = "static",
DYNAMIC = "dynamic",
}
+
+export enum BBReferenceFieldSubType {
+ USER = "user",
+ USERS = "users",
+}
From 1aad2ee6d29b5ab7910662621af0f5f28cfa542a Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 26 Apr 2024 12:23:11 +0200
Subject: [PATCH 156/338] Fix after renames
---
.../DataTable/modals/CreateEditColumn.svelte | 24 ++++++++-----
.../ExistingTableDataImport.svelte | 6 ++--
.../builder/src/constants/backend/index.js | 6 ++--
.../grid/cells/BBReferenceCell.svelte | 9 +++--
.../grid/controls/MigrationModal.svelte | 10 ++++--
packages/frontend-core/src/constants.js | 6 ++--
.../src/api/routes/tests/datasource.spec.ts | 4 +--
.../server/src/api/routes/tests/row.spec.ts | 6 ++--
.../server/src/api/routes/tests/table.spec.ts | 20 +++++------
packages/server/src/integrations/base/sql.ts | 4 +--
.../server/src/integrations/base/sqlTable.ts | 6 ++--
.../sdk/app/rows/search/tests/utils.spec.ts | 6 ++--
.../server/src/sdk/app/rows/search/utils.ts | 6 ++--
.../server/src/sdk/app/tables/migration.ts | 6 ++--
.../rowProcessor/bbReferenceProcessor.ts | 25 ++++++++-----
.../src/utilities/rowProcessor/errors.ts | 4 +--
.../tests/bbReferenceProcessor.spec.ts | 35 +++++++++++--------
.../tests/inputProcessing.spec.ts | 8 ++---
.../tests/outputProcessing.spec.ts | 8 ++---
packages/server/src/utilities/schema.ts | 17 +++++----
packages/shared-core/src/filters.ts | 14 +++++---
.../types/src/documents/app/table/schema.ts | 5 +--
22 files changed, 136 insertions(+), 99 deletions(-)
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index d271462f3e..f3ae207cb4 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -29,7 +29,11 @@
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
import { getBindings } from "components/backend/DataTable/formula"
import JSONSchemaModal from "./JSONSchemaModal.svelte"
- import { FieldType, FieldSubtype, SourceName } from "@budibase/types"
+ import {
+ FieldType,
+ BBReferenceFieldSubType,
+ SourceName,
+ } from "@budibase/types"
import RelationshipSelector from "components/common/RelationshipSelector.svelte"
import { RowUtils } from "@budibase/frontend-core"
import ServerBindingPanel from "components/common/bindings/ServerBindingPanel.svelte"
@@ -41,8 +45,6 @@
const NUMBER_TYPE = FieldType.NUMBER
const JSON_TYPE = FieldType.JSON
const DATE_TYPE = FieldType.DATETIME
- const USER_TYPE = FieldSubtype.USER
- const USERS_TYPE = FieldSubtype.USERS
const dispatch = createEventDispatcher()
const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"]
@@ -263,9 +265,9 @@
delete saveColumn.fieldName
}
if (isUsersColumn(saveColumn)) {
- if (saveColumn.subtype === USER_TYPE) {
+ if (saveColumn.subtype === BBReferenceFieldSubType.USER) {
saveColumn.relationshipType = RelationshipType.ONE_TO_MANY
- } else if (saveColumn.subtype === USERS_TYPE) {
+ } else if (saveColumn.subtype === BBReferenceFieldSubType.USERS) {
saveColumn.relationshipType = RelationshipType.MANY_TO_MANY
}
}
@@ -375,7 +377,7 @@
const isUsers =
editableColumn.type === FieldType.BB_REFERENCE &&
- editableColumn.subtype === FieldSubtype.USERS
+ editableColumn.subtype === BBReferenceFieldSubType.USERS
if (!externalTable) {
return [
@@ -485,7 +487,9 @@
function isUsersColumn(column) {
return (
column.type === FieldType.BB_REFERENCE &&
- [FieldSubtype.USER, FieldSubtype.USERS].includes(column.subtype)
+ [BBReferenceFieldSubType.USER, BBReferenceFieldSubType.USERS].includes(
+ column.subtype
+ )
)
}
@@ -688,12 +692,14 @@
>
{:else if isUsersColumn(editableColumn) && datasource?.source !== SourceName.GOOGLE_SHEETS}
handleTypeChange(
makeFieldId(
FieldType.BB_REFERENCE,
- e.detail ? FieldSubtype.USERS : FieldSubtype.USER
+ e.detail
+ ? BBReferenceFieldSubType.USERS
+ : BBReferenceFieldSubType.USER
)
)}
disabled={!isCreating}
diff --git a/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte b/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte
index 6901503071..b7fa243c07 100644
--- a/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte
+++ b/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte
@@ -1,5 +1,5 @@
+
+
+ {
+ if (get(auth).user) {
+ try {
+ await API.updateSelf({
+ freeTrialConfirmedAt: new Date().toISOString(),
+ })
+ // Update the cached user
+ await auth.getSelf()
+ } finally {
+ freeTrialModal.hide()
+ }
+ }
+ }}
+ >
+ Experience all of Budibase with a free 14-day trial
+
+ We've upgraded you to a free 14-day trial that allows you to try all our
+ features before deciding which plan is right for you.
+
+ At the end of your trial, we'll automatically downgrade you to the Free
+ plan unless you choose to upgrade.
+
+
+
+
+
+
+
diff --git a/packages/builder/src/helpers/planTitle.js b/packages/builder/src/helpers/planTitle.js
index 79f2bc2382..c08b8bf3fe 100644
--- a/packages/builder/src/helpers/planTitle.js
+++ b/packages/builder/src/helpers/planTitle.js
@@ -20,6 +20,9 @@ export function getFormattedPlanName(userPlanType) {
case PlanType.ENTERPRISE:
planName = "Enterprise"
break
+ case PlanType.ENTERPRISE_BASIC_TRIAL:
+ planName = "Trial"
+ break
default:
planName = "Free" // Default to "Free" if the type is not explicitly handled
}
diff --git a/packages/builder/src/pages/builder/app/[application]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/_layout.svelte
index fd6a97560d..60c45fd2e4 100644
--- a/packages/builder/src/pages/builder/app/[application]/_layout.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/_layout.svelte
@@ -32,6 +32,7 @@
import { UserAvatars } from "@budibase/frontend-core"
import { TOUR_KEYS } from "components/portal/onboarding/tours.js"
import PreviewOverlay from "./_components/PreviewOverlay.svelte"
+ import EnterpriseBasicTrialModal from "components/portal/onboarding/EnterpriseBasicTrialModal.svelte"
export let application
@@ -192,6 +193,8 @@
+
+
diff --git a/packages/builder/src/components/portal/licensing/EnterpriseBasicTrialBanner.svelte b/packages/builder/src/components/portal/licensing/EnterpriseBasicTrialBanner.svelte
new file mode 100644
index 0000000000..111f0481b9
--- /dev/null
+++ b/packages/builder/src/components/portal/licensing/EnterpriseBasicTrialBanner.svelte
@@ -0,0 +1,43 @@
+
+
+
+
+ {#if show}
+
+
+ Your free trial will end in {daysUntilCancel()} days.
+
+
+ {/if}
+
+
+
+
diff --git a/packages/builder/src/components/portal/licensing/licensingBanners.js b/packages/builder/src/components/portal/licensing/licensingBanners.js
index 34558e98e2..34b22c934b 100644
--- a/packages/builder/src/components/portal/licensing/licensingBanners.js
+++ b/packages/builder/src/components/portal/licensing/licensingBanners.js
@@ -12,7 +12,7 @@ const defaultCacheFn = key => {
const upgradeAction = key => {
return defaultNavigateAction(
key,
- "Upgrade Plan",
+ "Upgrade",
`${get(admin).accountPortalUrl}/portal/upgrade`
)
}
diff --git a/packages/builder/src/components/portal/onboarding/EnterpriseBasicTrialModal.svelte b/packages/builder/src/components/portal/onboarding/EnterpriseBasicTrialModal.svelte
index fa789311d1..6652bd4104 100644
--- a/packages/builder/src/components/portal/onboarding/EnterpriseBasicTrialModal.svelte
+++ b/packages/builder/src/components/portal/onboarding/EnterpriseBasicTrialModal.svelte
@@ -5,6 +5,7 @@
import { auth, licensing } from "stores/portal"
import { API } from "api"
import { PlanType } from "@budibase/types"
+ import { sdk } from "@budibase/shared-core"
let freeTrialModal
@@ -14,7 +15,8 @@
const showFreeTrialModal = (planType, freeTrialModal) => {
if (
planType === PlanType.ENTERPRISE_BASIC_TRIAL &&
- !$auth.user?.freeTrialConfirmedAt
+ !$auth.user?.freeTrialConfirmedAt &&
+ sdk.users.isAdmin($auth.user)
) {
freeTrialModal?.show()
}
diff --git a/packages/builder/src/pages/builder/portal/_components/UpgradeButton.svelte b/packages/builder/src/pages/builder/portal/_components/UpgradeButton.svelte
index 59a791538a..1e67bc2140 100644
--- a/packages/builder/src/pages/builder/portal/_components/UpgradeButton.svelte
+++ b/packages/builder/src/pages/builder/portal/_components/UpgradeButton.svelte
@@ -6,7 +6,7 @@
import { sdk } from "@budibase/shared-core"
-{#if isEnabled(TENANT_FEATURE_FLAGS.LICENSING) && !$licensing.isEnterprisePlan}
+{#if isEnabled(TENANT_FEATURE_FLAGS.LICENSING) && !$licensing.isEnterprisePlan && !$licensing.isEnterpriseTrial}
{#if $admin.cloud && $auth?.user?.accountPortalAccess}
import { isActive, redirect, goto, url } from "@roxi/routify"
import { Icon, notifications, Tabs, Tab } from "@budibase/bbui"
- import { organisation, auth, menu, appsStore } from "stores/portal"
+ import { organisation, auth, menu, appsStore, licensing } from "stores/portal"
import { onMount } from "svelte"
import UpgradeButton from "./_components/UpgradeButton.svelte"
import MobileMenu from "./_components/MobileMenu.svelte"
@@ -10,6 +10,8 @@
import HelpMenu from "components/common/HelpMenu.svelte"
import VerificationPromptBanner from "components/common/VerificationPromptBanner.svelte"
import { sdk } from "@budibase/shared-core"
+ import EnterpriseBasicTrialBanner from "components/portal/licensing/EnterpriseBasicTrialBanner.svelte"
+ import { Constants } from "@budibase/frontend-core"
let loaded = false
let mobileMenuVisible = false
@@ -33,6 +35,14 @@
const showMobileMenu = () => (mobileMenuVisible = true)
const hideMobileMenu = () => (mobileMenuVisible = false)
+ const showFreeTrialBanner = () => {
+ return (
+ $licensing.license?.plan?.type ===
+ Constants.PlanType.ENTERPRISE_BASIC_TRIAL &&
+ sdk.users.isAdmin($auth.user)
+ )
+ }
+
onMount(async () => {
// Prevent non-builders from accessing the portal
if ($auth.user) {
@@ -58,6 +68,7 @@
+
diff --git a/packages/builder/src/pages/builder/portal/account/usage.svelte b/packages/builder/src/pages/builder/portal/account/usage.svelte
index 8eb26c20d6..7a1b1f8e14 100644
--- a/packages/builder/src/pages/builder/portal/account/usage.svelte
+++ b/packages/builder/src/pages/builder/portal/account/usage.svelte
@@ -29,6 +29,7 @@
const manageUrl = `${$admin.accountPortalUrl}/portal/billing`
const WARN_USAGE = ["Queries", "Automations", "Rows", "Day Passes", "Users"]
+ const oneDayInSeconds = 86400
const EXCLUDE_QUOTAS = {
Queries: () => true,
@@ -104,24 +105,17 @@
if (!timestamp) {
return
}
- const now = new Date()
- now.setHours(0)
- now.setMinutes(0)
-
- const thenDate = new Date(timestamp)
- thenDate.setHours(0)
- thenDate.setMinutes(0)
-
- const difference = thenDate.getTime() - now
- // return the difference in days
- return (difference / (1000 * 3600 * 24)).toFixed(0)
+ const diffTime = Math.abs(timestamp - new Date().getTime()) / 1000
+ return Math.floor(diffTime / oneDayInSeconds)
}
const setTextRows = () => {
textRows = []
if (cancelAt && !usesInvoicing) {
- textRows.push({ message: "Subscription has been cancelled" })
+ if (plan?.type !== Constants.PlanType.ENTERPRISE_BASIC_TRIAL) {
+ textRows.push({ message: "Subscription has been cancelled" })
+ }
textRows.push({
message: `${getDaysRemaining(cancelAt)} days remaining`,
tooltip: new Date(cancelAt),
From 1d300c257760d5f09a7f12762f11f80f6b93d40e Mon Sep 17 00:00:00 2001
From: Peter Clement
Date: Wed, 8 May 2024 14:08:34 +0100
Subject: [PATCH 267/338] Remove aws sdk global mock and update tests (#13637)
* Remove aws sdk global mock and update tests
* add awaits
* Minio healthcheck in tests.
* Bind to 127.0.0.1 instead of 0.0.0.0
* Fix port fetching for minio container.
* Actually fix port mapping this time.
* Pull minio container before running tests.
* Enable testcontainers debug logging.
* Promote minio container to always running in tests, like CouchDB.
* Remove testcontainers debug logging.
---------
Co-authored-by: Sam Rose
---
.github/workflows/budibase_ci.yml | 3 +-
globalSetup.ts | 17 ++++++++--
.../src/objectStore/objectStore.ts | 2 +-
.../tests/core/utilities/index.ts | 3 --
.../tests/core/utilities/minio.ts | 34 -------------------
.../core/utilities/testContainerUtils.ts | 8 +++++
.../src/api/routes/tests/attachment.spec.ts | 7 ++--
.../src/api/routes/tests/backup.spec.ts | 6 ++--
.../server/src/api/routes/tests/row.spec.ts | 4 +--
.../src/api/routes/tests/static.spec.js | 10 ++++++
.../integrations/tests/aws-sdk.mock.ts} | 7 +---
.../src/integrations/tests/dynamodb.spec.ts | 3 +-
.../src/integrations/tests/rest.spec.ts | 10 ------
.../server/src/integrations/tests/s3.spec.ts | 3 +-
packages/server/src/tests/jestEnv.ts | 3 ++
.../tests/outputProcessing.spec.ts | 8 ++---
.../api/routes/global/tests/realEmail.spec.ts | 3 --
17 files changed, 57 insertions(+), 74 deletions(-)
delete mode 100644 packages/backend-core/tests/core/utilities/minio.ts
rename packages/server/{__mocks__/aws-sdk.ts => src/integrations/tests/aws-sdk.mock.ts} (86%)
diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml
index 7d09451614..7f1e08601a 100644
--- a/.github/workflows/budibase_ci.yml
+++ b/.github/workflows/budibase_ci.yml
@@ -170,7 +170,8 @@ jobs:
docker pull mongo:7.0-jammy &
docker pull mariadb:lts &
docker pull testcontainers/ryuk:0.5.1 &
- docker pull budibase/couchdb:v3.2.1-sql &
+ docker pull budibase/couchdb:v3.2.1-sqs &
+ docker pull minio/minio &
docker pull redis &
wait $(jobs -p)
diff --git a/globalSetup.ts b/globalSetup.ts
index dd1a7dbaa0..dd1454b6e1 100644
--- a/globalSetup.ts
+++ b/globalSetup.ts
@@ -46,7 +46,7 @@ export default async function setup() {
await killContainers(containers)
try {
- let couchdb = new GenericContainer("budibase/couchdb:v3.2.1-sqs")
+ const couchdb = new GenericContainer("budibase/couchdb:v3.2.1-sqs")
.withExposedPorts(5984, 4984)
.withEnvironment({
COUCHDB_PASSWORD: "budibase",
@@ -69,7 +69,20 @@ export default async function setup() {
).withStartupTimeout(20000)
)
- await couchdb.start()
+ const minio = new GenericContainer("minio/minio")
+ .withExposedPorts(9000)
+ .withCommand(["server", "/data"])
+ .withEnvironment({
+ MINIO_ACCESS_KEY: "budibase",
+ MINIO_SECRET_KEY: "budibase",
+ })
+ .withLabels({ "com.budibase": "true" })
+ .withReuse()
+ .withWaitStrategy(
+ Wait.forHttp("/minio/health/ready", 9000).withStartupTimeout(10000)
+ )
+
+ await Promise.all([couchdb.start(), minio.start()])
} finally {
lockfile.unlockSync(lockPath)
}
diff --git a/packages/backend-core/src/objectStore/objectStore.ts b/packages/backend-core/src/objectStore/objectStore.ts
index aa5365c5c3..2bef91ffef 100644
--- a/packages/backend-core/src/objectStore/objectStore.ts
+++ b/packages/backend-core/src/objectStore/objectStore.ts
@@ -83,7 +83,7 @@ export function ObjectStore(
bucket: string,
opts: { presigning: boolean } = { presigning: false }
) {
- const config: any = {
+ const config: AWS.S3.ClientConfiguration = {
s3ForcePathStyle: true,
signatureVersion: "v4",
apiVersion: "2006-03-01",
diff --git a/packages/backend-core/tests/core/utilities/index.ts b/packages/backend-core/tests/core/utilities/index.ts
index b2f19a0286..787d69be2c 100644
--- a/packages/backend-core/tests/core/utilities/index.ts
+++ b/packages/backend-core/tests/core/utilities/index.ts
@@ -4,6 +4,3 @@ export { generator } from "./structures"
export * as testContainerUtils from "./testContainerUtils"
export * as utils from "./utils"
export * from "./jestUtils"
-import * as minio from "./minio"
-
-export const objectStoreTestProviders = { minio }
diff --git a/packages/backend-core/tests/core/utilities/minio.ts b/packages/backend-core/tests/core/utilities/minio.ts
deleted file mode 100644
index cef33daa91..0000000000
--- a/packages/backend-core/tests/core/utilities/minio.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { GenericContainer, Wait, StartedTestContainer } from "testcontainers"
-import { AbstractWaitStrategy } from "testcontainers/build/wait-strategies/wait-strategy"
-import env from "../../../src/environment"
-
-let container: StartedTestContainer | undefined
-
-class ObjectStoreWaitStrategy extends AbstractWaitStrategy {
- async waitUntilReady(container: any, boundPorts: any, startTime?: Date) {
- const logs = Wait.forListeningPorts()
- await logs.waitUntilReady(container, boundPorts, startTime)
- }
-}
-
-export async function start(): Promise {
- container = await new GenericContainer("minio/minio")
- .withExposedPorts(9000)
- .withCommand(["server", "/data"])
- .withEnvironment({
- MINIO_ACCESS_KEY: "budibase",
- MINIO_SECRET_KEY: "budibase",
- })
- .withWaitStrategy(new ObjectStoreWaitStrategy().withStartupTimeout(30000))
- .start()
-
- const port = container.getMappedPort(9000)
- env._set("MINIO_URL", `http://0.0.0.0:${port}`)
-}
-
-export async function stop() {
- if (container) {
- await container.stop()
- container = undefined
- }
-}
diff --git a/packages/backend-core/tests/core/utilities/testContainerUtils.ts b/packages/backend-core/tests/core/utilities/testContainerUtils.ts
index 32841e4c3a..1a25bb28f4 100644
--- a/packages/backend-core/tests/core/utilities/testContainerUtils.ts
+++ b/packages/backend-core/tests/core/utilities/testContainerUtils.ts
@@ -86,10 +86,18 @@ export function setupEnv(...envs: any[]) {
throw new Error("CouchDB SQL port not found")
}
+ const minio = getContainerByImage("minio/minio")
+
+ const minioPort = getExposedV4Port(minio, 9000)
+ if (!minioPort) {
+ throw new Error("Minio port not found")
+ }
+
const configs = [
{ key: "COUCH_DB_PORT", value: `${couchPort}` },
{ key: "COUCH_DB_URL", value: `http://127.0.0.1:${couchPort}` },
{ key: "COUCH_DB_SQL_URL", value: `http://127.0.0.1:${couchSqlPort}` },
+ { key: "MINIO_URL", value: `http://127.0.0.1:${minioPort}` },
]
for (const config of configs.filter(x => !!x.value)) {
diff --git a/packages/server/src/api/routes/tests/attachment.spec.ts b/packages/server/src/api/routes/tests/attachment.spec.ts
index aa02ea898e..7ff4e67bcc 100644
--- a/packages/server/src/api/routes/tests/attachment.spec.ts
+++ b/packages/server/src/api/routes/tests/attachment.spec.ts
@@ -4,10 +4,12 @@ import { APIError } from "@budibase/types"
describe("/api/applications/:appId/sync", () => {
let config = setup.getConfig()
- afterAll(setup.afterAll)
beforeAll(async () => {
await config.init()
})
+ afterAll(async () => {
+ setup.afterAll()
+ })
describe("/api/attachments/process", () => {
it("should accept an image file upload", async () => {
@@ -18,7 +20,8 @@ describe("/api/applications/:appId/sync", () => {
expect(resp.length).toBe(1)
let upload = resp[0]
- expect(upload.url.endsWith(".jpg")).toBe(true)
+
+ expect(upload.url.split("?")[0].endsWith(".jpg")).toBe(true)
expect(upload.extension).toBe("jpg")
expect(upload.size).toBe(1)
expect(upload.name).toBe("1px.jpg")
diff --git a/packages/server/src/api/routes/tests/backup.spec.ts b/packages/server/src/api/routes/tests/backup.spec.ts
index 7b56145f8e..78354dfe9c 100644
--- a/packages/server/src/api/routes/tests/backup.spec.ts
+++ b/packages/server/src/api/routes/tests/backup.spec.ts
@@ -1,16 +1,18 @@
+import { mocks } from "@budibase/backend-core/tests"
import tk from "timekeeper"
import * as setup from "./utilities"
import { events } from "@budibase/backend-core"
import sdk from "../../../sdk"
import { checkBuilderEndpoint } from "./utilities/TestFunctions"
-import { mocks } from "@budibase/backend-core/tests"
mocks.licenses.useBackups()
describe("/backups", () => {
let config = setup.getConfig()
- afterAll(setup.afterAll)
+ afterAll(async () => {
+ setup.afterAll()
+ })
beforeEach(async () => {
tk.reset()
diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts
index 5e30c1b4cc..fd3158a11c 100644
--- a/packages/server/src/api/routes/tests/row.spec.ts
+++ b/packages/server/src/api/routes/tests/row.spec.ts
@@ -856,7 +856,7 @@ describe.each([
await config.withEnv({ SELF_HOSTED: "true" }, async () => {
return context.doInAppContext(config.getAppId(), async () => {
const enriched = await outputProcessing(table, [row])
- expect((enriched as Row[])[0].attachment.url).toBe(
+ expect((enriched as Row[])[0].attachment.url.split("?")[0]).toBe(
`/files/signed/prod-budi-app-assets/${config.getProdAppId()}/attachments/${attachmentId}`
)
})
@@ -889,7 +889,7 @@ describe.each([
await config.withEnv({ SELF_HOSTED: "true" }, async () => {
return context.doInAppContext(config.getAppId(), async () => {
const enriched = await outputProcessing(table, [row])
- expect((enriched as Row[])[0].attachment[0].url).toBe(
+ expect((enriched as Row[])[0].attachment[0].url.split("?")[0]).toBe(
`/files/signed/prod-budi-app-assets/${config.getProdAppId()}/attachments/${attachmentId}`
)
})
diff --git a/packages/server/src/api/routes/tests/static.spec.js b/packages/server/src/api/routes/tests/static.spec.js
index 30417c3a98..1358b5418a 100644
--- a/packages/server/src/api/routes/tests/static.spec.js
+++ b/packages/server/src/api/routes/tests/static.spec.js
@@ -1,3 +1,13 @@
+// Directly mock the AWS SDK
+jest.mock("aws-sdk", () => ({
+ S3: jest.fn(() => ({
+ getSignedUrl: jest.fn(
+ (operation, params) => `http://example.com/${params.Bucket}/${params.Key}`
+ ),
+ upload: jest.fn(() => ({ Contents: {} })),
+ })),
+}))
+
const setup = require("./utilities")
const { constants } = require("@budibase/backend-core")
diff --git a/packages/server/__mocks__/aws-sdk.ts b/packages/server/src/integrations/tests/aws-sdk.mock.ts
similarity index 86%
rename from packages/server/__mocks__/aws-sdk.ts
rename to packages/server/src/integrations/tests/aws-sdk.mock.ts
index d6d33f6c46..0422adfd3c 100644
--- a/packages/server/__mocks__/aws-sdk.ts
+++ b/packages/server/src/integrations/tests/aws-sdk.mock.ts
@@ -1,6 +1,3 @@
-import fs from "fs"
-import { join } from "path"
-
const response = (body: any, extra?: any) => () => ({
promise: () => body,
...extra,
@@ -62,9 +59,7 @@ class S3 {
Body: "",
},
{
- createReadStream: jest
- .fn()
- .mockReturnValue(fs.createReadStream(join(__dirname, "aws-sdk.ts"))),
+ createReadStream: jest.fn().mockReturnValue("stream"),
}
)
)
diff --git a/packages/server/src/integrations/tests/dynamodb.spec.ts b/packages/server/src/integrations/tests/dynamodb.spec.ts
index 0215817907..c992bc8bfd 100644
--- a/packages/server/src/integrations/tests/dynamodb.spec.ts
+++ b/packages/server/src/integrations/tests/dynamodb.spec.ts
@@ -1,7 +1,6 @@
+jest.mock("aws-sdk", () => require("./aws-sdk.mock"))
import { default as DynamoDBIntegration } from "../dynamodb"
-jest.mock("aws-sdk")
-
class TestConfiguration {
integration: any
diff --git a/packages/server/src/integrations/tests/rest.spec.ts b/packages/server/src/integrations/tests/rest.spec.ts
index 4b20017939..9877a826ec 100644
--- a/packages/server/src/integrations/tests/rest.spec.ts
+++ b/packages/server/src/integrations/tests/rest.spec.ts
@@ -28,7 +28,6 @@ jest.mock("uuid", () => ({ v4: () => "00000000-0000-0000-0000-000000000000" }))
import { default as RestIntegration } from "../rest"
import { RestAuthType } from "@budibase/types"
import fetch from "node-fetch"
-import { objectStoreTestProviders } from "@budibase/backend-core/tests"
import { Readable } from "stream"
const FormData = require("form-data")
@@ -627,15 +626,6 @@ describe("REST Integration", () => {
})
describe("File Handling", () => {
- beforeAll(async () => {
- jest.unmock("aws-sdk")
- await objectStoreTestProviders.minio.start()
- })
-
- afterAll(async () => {
- await objectStoreTestProviders.minio.stop()
- })
-
it("uploads file to object store and returns signed URL", async () => {
const responseData = Buffer.from("teest file contnt")
const filename = "test.tar.gz"
diff --git a/packages/server/src/integrations/tests/s3.spec.ts b/packages/server/src/integrations/tests/s3.spec.ts
index b75340ab20..abe8fb9cf1 100644
--- a/packages/server/src/integrations/tests/s3.spec.ts
+++ b/packages/server/src/integrations/tests/s3.spec.ts
@@ -1,7 +1,6 @@
+jest.mock("aws-sdk", () => require("./aws-sdk.mock"))
import { default as S3Integration } from "../s3"
-jest.mock("aws-sdk")
-
class TestConfiguration {
integration: any
diff --git a/packages/server/src/tests/jestEnv.ts b/packages/server/src/tests/jestEnv.ts
index 3b0c2a88d9..a44ce58f81 100644
--- a/packages/server/src/tests/jestEnv.ts
+++ b/packages/server/src/tests/jestEnv.ts
@@ -14,3 +14,6 @@ process.env.WORKER_URL = "http://localhost:10000"
process.env.COUCH_DB_PASSWORD = "budibase"
process.env.COUCH_DB_USER = "budibase"
process.env.JWT_SECRET = "jwtsecret"
+process.env.MINIO_URL = "http://localhost"
+process.env.MINIO_ACCESS_KEY = "budibase"
+process.env.MINIO_SECRET_KEY = "budibase"
diff --git a/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts b/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts
index b1a20acba5..c22fd1c69d 100644
--- a/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts
+++ b/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts
@@ -100,13 +100,13 @@ describe("rowProcessor - outputProcessing", () => {
}
const output = await outputProcessing(table, row, { squash: false })
- expect(output.attach[0].url).toBe(
+ expect(output.attach[0].url?.split("?")[0]).toBe(
"/files/signed/prod-budi-app-assets/test.jpg"
)
row.attach[0].url = ""
const output2 = await outputProcessing(table, row, { squash: false })
- expect(output2.attach[0].url).toBe(
+ expect(output2.attach[0].url?.split("?")[0]).toBe(
"/files/signed/prod-budi-app-assets/test.jpg"
)
@@ -141,13 +141,13 @@ describe("rowProcessor - outputProcessing", () => {
}
const output = await outputProcessing(table, row, { squash: false })
- expect(output.attach.url).toBe(
+ expect(output.attach.url?.split("?")[0]).toBe(
"/files/signed/prod-budi-app-assets/test.jpg"
)
row.attach.url = ""
const output2 = await outputProcessing(table, row, { squash: false })
- expect(output2.attach.url).toBe(
+ expect(output2.attach?.url?.split("?")[0]).toBe(
"/files/signed/prod-budi-app-assets/test.jpg"
)
diff --git a/packages/worker/src/api/routes/global/tests/realEmail.spec.ts b/packages/worker/src/api/routes/global/tests/realEmail.spec.ts
index 8880e587c5..bda5f6334f 100644
--- a/packages/worker/src/api/routes/global/tests/realEmail.spec.ts
+++ b/packages/worker/src/api/routes/global/tests/realEmail.spec.ts
@@ -2,7 +2,6 @@ jest.unmock("node-fetch")
jest.unmock("aws-sdk")
import { TestConfiguration } from "../../../../tests"
import { EmailTemplatePurpose } from "../../../../constants"
-import { objectStoreTestProviders } from "@budibase/backend-core/tests"
import { objectStore } from "@budibase/backend-core"
import tk from "timekeeper"
import { EmailAttachment } from "@budibase/types"
@@ -19,12 +18,10 @@ describe("/api/global/email", () => {
beforeAll(async () => {
tk.reset()
- await objectStoreTestProviders.minio.start()
await config.beforeAll()
})
afterAll(async () => {
- await objectStoreTestProviders.minio.stop()
await config.afterAll()
})
From 5783ee790fecbd722e5bfc18a1b59ba8c6e220f8 Mon Sep 17 00:00:00 2001
From: Sam Rose
Date: Wed, 8 May 2024 15:36:26 +0100
Subject: [PATCH 268/338] Fix Lucene tests.
---
packages/backend-core/src/db/lucene.ts | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/packages/backend-core/src/db/lucene.ts b/packages/backend-core/src/db/lucene.ts
index 76ba257d59..f5ad7e6433 100644
--- a/packages/backend-core/src/db/lucene.ts
+++ b/packages/backend-core/src/db/lucene.ts
@@ -13,11 +13,7 @@ import { dataFilters } from "@budibase/shared-core"
export const removeKeyNumbering = dataFilters.removeKeyNumbering
function isEmpty(value: any) {
- return (
- value == null ||
- value === "" ||
- (Array.isArray(value) && value.length === 0)
- )
+ return value == null || value === ""
}
/**
From dacb0d30ce9b376b234b45e34df93f0cf4b3722a Mon Sep 17 00:00:00 2001
From: Martin McKeaveney
Date: Wed, 8 May 2024 15:55:14 +0100
Subject: [PATCH 269/338] prevent lack of start/end date from causing UI error
---
.../automation-worker-service-hpa.yaml | 2 +-
.../settings/backups/index.svelte | 20 +++++++++++++------
2 files changed, 15 insertions(+), 7 deletions(-)
diff --git a/charts/budibase/templates/automation-worker-service-hpa.yaml b/charts/budibase/templates/automation-worker-service-hpa.yaml
index f29223b61b..18f9690c00 100644
--- a/charts/budibase/templates/automation-worker-service-hpa.yaml
+++ b/charts/budibase/templates/automation-worker-service-hpa.yaml
@@ -2,7 +2,7 @@
apiVersion: {{ ternary "autoscaling/v2" "autoscaling/v2beta2" (.Capabilities.APIVersions.Has "autoscaling/v2") }}
kind: HorizontalPodAutoscaler
metadata:
- name: {{ include "budibase.fullname" . }}-apps
+ name: {{ include "budibase.fullname" . }}-automation-worker
labels:
{{- include "budibase.labels" . | nindent 4 }}
spec:
diff --git a/packages/builder/src/pages/builder/app/[application]/settings/backups/index.svelte b/packages/builder/src/pages/builder/app/[application]/settings/backups/index.svelte
index 39d61ffc31..e8a67b2ae1 100644
--- a/packages/builder/src/pages/builder/app/[application]/settings/backups/index.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/settings/backups/index.svelte
@@ -98,14 +98,22 @@
})
}
- async function fetchBackups(filters, page, dateRange) {
- const response = await backups.searchBackups({
+ async function fetchBackups(filters, page, dateRange = []) {
+ const body = {
appId: $appStore.appId,
...filters,
page,
- startDate: dateRange[0],
- endDate: dateRange[1],
- })
+ }
+
+ const [startDate, endDate] = dateRange
+ if (startDate) {
+ body.startDate = startDate
+ }
+ if (endDate) {
+ body.endDate = endDate
+ }
+
+ const response = await backups.searchBackups(body)
pageInfo.fetched(response.hasNextPage, response.nextPage)
// flatten so we have an easier structure to use for the table schema
@@ -120,7 +128,7 @@
})
await fetchBackups(filterOpt, page)
notifications.success(response.message)
- } catch {
+ } catch (err) {
notifications.error("Unable to create backup")
}
}
From b4d26ccf22ba5e8ba97595b7ff80e4a8436a39ea Mon Sep 17 00:00:00 2001
From: Budibase Staging Release Bot <>
Date: Wed, 8 May 2024 15:05:26 +0000
Subject: [PATCH 270/338] Bump version to 2.24.2
---
lerna.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lerna.json b/lerna.json
index 6ba05e19ee..9c5a6c6bab 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "version": "2.24.1",
+ "version": "2.24.2",
"npmClient": "yarn",
"packages": [
"packages/*",
From fd94e3aea92157713486ed3e8c5ae4773b34cfe4 Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Wed, 8 May 2024 16:08:29 +0100
Subject: [PATCH 271/338] Typing improvements around the REST integration to
make sure when no response is returned it doesn't break anything.
---
packages/server/src/integrations/rest.ts | 45 +++++++++++++-----------
1 file changed, 25 insertions(+), 20 deletions(-)
diff --git a/packages/server/src/integrations/rest.ts b/packages/server/src/integrations/rest.ts
index ffa91cdce9..0613c3ade8 100644
--- a/packages/server/src/integrations/rest.ts
+++ b/packages/server/src/integrations/rest.ts
@@ -26,13 +26,13 @@ import { parse } from "content-disposition"
import path from "path"
import { Builder as XmlBuilder } from "xml2js"
-const BodyTypes = {
- NONE: "none",
- FORM_DATA: "form",
- XML: "xml",
- ENCODED: "encoded",
- JSON: "json",
- TEXT: "text",
+enum BodyType {
+ NONE = "none",
+ FORM_DATA = "form",
+ XML = "xml",
+ ENCODED = "encoded",
+ JSON = "json",
+ TEXT = "text",
}
const coreFields = {
@@ -54,7 +54,7 @@ const coreFields = {
},
bodyType: {
type: DatasourceFieldType.STRING,
- enum: Object.values(BodyTypes),
+ enum: Object.values(BodyType),
},
pagination: {
type: DatasourceFieldType.OBJECT,
@@ -131,7 +131,10 @@ class RestIntegration implements IntegrationBase {
}
async parseResponse(response: any, pagination: PaginationConfig | null) {
- let data, raw, headers, filename
+ let data: any[] | string | undefined,
+ raw: string | undefined,
+ headers: Record = {},
+ filename: string | undefined
const contentType = response.headers.get("content-type") || ""
const contentDisposition = response.headers.get("content-disposition") || ""
@@ -149,7 +152,7 @@ class RestIntegration implements IntegrationBase {
} else {
if (response.status === 204) {
data = []
- raw = []
+ raw = ""
} else if (contentType.includes("application/json")) {
data = await response.json()
raw = JSON.stringify(data)
@@ -162,16 +165,18 @@ class RestIntegration implements IntegrationBase {
raw = xmlResponse.rawXml
} else {
data = await response.text()
- raw = data
+ raw = data as string
}
}
} catch (err) {
throw `Failed to parse response body: ${err}`
}
- const size = formatBytes(
- response.headers.get("content-length") || Buffer.byteLength(raw, "utf8")
- )
+ let contentLength: string = response.headers.get("content-length")
+ if (!contentLength && raw) {
+ contentLength = Buffer.byteLength(raw, "utf8").toString()
+ }
+ const size = formatBytes(contentLength || "0")
const time = `${Math.round(performance.now() - this.startTimeMs)}ms`
headers = response.headers.raw()
for (let [key, value] of Object.entries(headers)) {
@@ -255,7 +260,7 @@ class RestIntegration implements IntegrationBase {
if (!input.headers) {
input.headers = {}
}
- if (bodyType === BodyTypes.NONE) {
+ if (bodyType === BodyType.NONE) {
return input
}
let error,
@@ -283,11 +288,11 @@ class RestIntegration implements IntegrationBase {
}
switch (bodyType) {
- case BodyTypes.TEXT:
+ case BodyType.TEXT:
// content type defaults to plaintext
input.body = string
break
- case BodyTypes.ENCODED: {
+ case BodyType.ENCODED: {
const params = new URLSearchParams()
for (let [key, value] of Object.entries(object)) {
params.append(key, value as string)
@@ -298,7 +303,7 @@ class RestIntegration implements IntegrationBase {
input.body = params
break
}
- case BodyTypes.FORM_DATA: {
+ case BodyType.FORM_DATA: {
const form = new FormData()
for (let [key, value] of Object.entries(object)) {
form.append(key, value)
@@ -309,14 +314,14 @@ class RestIntegration implements IntegrationBase {
input.body = form
break
}
- case BodyTypes.XML:
+ case BodyType.XML:
if (object != null && Object.keys(object).length) {
string = new XmlBuilder().buildObject(object)
}
input.body = string
input.headers["Content-Type"] = "application/xml"
break
- case BodyTypes.JSON:
+ case BodyType.JSON:
// if JSON error, throw it
if (error) {
throw "Invalid JSON for request body"
From ed1f9f19a70ae8a4b418ebde0b34e87ff8207c0d Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Wed, 8 May 2024 16:12:24 +0100
Subject: [PATCH 272/338] Updating test case to cover this scenario properly.
---
packages/server/src/integrations/tests/rest.spec.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/server/src/integrations/tests/rest.spec.ts b/packages/server/src/integrations/tests/rest.spec.ts
index 9877a826ec..7273a2d178 100644
--- a/packages/server/src/integrations/tests/rest.spec.ts
+++ b/packages/server/src/integrations/tests/rest.spec.ts
@@ -245,13 +245,13 @@ describe("REST Integration", () => {
expect(output.extra.headers["content-type"]).toEqual("application/xml")
})
- test.each(contentTypes)(
+ test.each([...contentTypes, undefined])(
"should not throw an error on 204 no content",
async contentType => {
const input = buildInput(undefined, null, contentType, 204)
const output = await config.integration.parseResponse(input)
expect(output.data).toEqual([])
- expect(output.extra.raw).toEqual([])
+ expect(output.extra.raw).toEqual("")
expect(output.info.code).toEqual(204)
expect(output.extra.headers["content-type"]).toEqual(contentType)
}
From 033c642c12ef17a2d2311bd5c52e0e77f99759a2 Mon Sep 17 00:00:00 2001
From: Sam Rose
Date: Thu, 9 May 2024 10:15:05 +0100
Subject: [PATCH 273/338] Remove unnecessary jest.unmock calls.
---
packages/server/src/api/routes/tests/row.spec.ts | 2 --
packages/server/src/api/routes/tests/search.spec.ts | 2 --
packages/server/src/api/routes/tests/viewV2.spec.ts | 2 --
packages/worker/src/api/routes/global/tests/realEmail.spec.ts | 1 -
4 files changed, 7 deletions(-)
diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts
index fd3158a11c..c23f893900 100644
--- a/packages/server/src/api/routes/tests/row.spec.ts
+++ b/packages/server/src/api/routes/tests/row.spec.ts
@@ -32,8 +32,6 @@ import * as uuid from "uuid"
const timestamp = new Date("2023-01-26T11:48:57.597Z").toISOString()
tk.freeze(timestamp)
-jest.unmock("mssql")
-
describe.each([
["internal", undefined],
[DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts
index 173a11bb09..9e3d3c23cb 100644
--- a/packages/server/src/api/routes/tests/search.spec.ts
+++ b/packages/server/src/api/routes/tests/search.spec.ts
@@ -16,8 +16,6 @@ import {
} from "@budibase/types"
import _ from "lodash"
-jest.unmock("mssql")
-
describe.each([
["lucene", undefined],
["sqs", undefined],
diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts
index 3bc5ab722f..2ad02a8082 100644
--- a/packages/server/src/api/routes/tests/viewV2.spec.ts
+++ b/packages/server/src/api/routes/tests/viewV2.spec.ts
@@ -24,8 +24,6 @@ import merge from "lodash/merge"
import { quotas } from "@budibase/pro"
import { roles } from "@budibase/backend-core"
-jest.unmock("mssql")
-
describe.each([
["internal", undefined],
[DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
diff --git a/packages/worker/src/api/routes/global/tests/realEmail.spec.ts b/packages/worker/src/api/routes/global/tests/realEmail.spec.ts
index bda5f6334f..bea627d5b6 100644
--- a/packages/worker/src/api/routes/global/tests/realEmail.spec.ts
+++ b/packages/worker/src/api/routes/global/tests/realEmail.spec.ts
@@ -1,5 +1,4 @@
jest.unmock("node-fetch")
-jest.unmock("aws-sdk")
import { TestConfiguration } from "../../../../tests"
import { EmailTemplatePurpose } from "../../../../constants"
import { objectStore } from "@budibase/backend-core"
From b99e3794b2548988170778985f18beaefb883a95 Mon Sep 17 00:00:00 2001
From: Sam Rose
Date: Thu, 9 May 2024 10:58:52 +0100
Subject: [PATCH 274/338] Move parallel auto ID row creation test to
row.spec.ts.
---
.../server/src/api/routes/tests/row.spec.ts | 40 +++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts
index fd3158a11c..6370e21c65 100644
--- a/packages/server/src/api/routes/tests/row.spec.ts
+++ b/packages/server/src/api/routes/tests/row.spec.ts
@@ -228,6 +228,46 @@ describe.each([
await assertRowUsage(rowUsage + 10)
})
+ it("should increment auto ID correctly when creating rows in parallel", async () => {
+ const table = await config.api.table.save(
+ saveTableRequest({
+ schema: {
+ "Row ID": {
+ name: "Row ID",
+ type: FieldType.NUMBER,
+ subtype: AutoFieldSubType.AUTO_ID,
+ icon: "ri-magic-line",
+ autocolumn: true,
+ constraints: {
+ type: "number",
+ presence: true,
+ numericality: {
+ greaterThanOrEqualTo: "",
+ lessThanOrEqualTo: "",
+ },
+ },
+ },
+ },
+ })
+ )
+
+ await Promise.all(
+ Array(50)
+ .fill(0)
+ .map(() => config.api.row.save(table._id!, {}))
+ )
+
+ const rows = await config.api.row.fetch(table._id!)
+ expect(rows).toHaveLength(50)
+
+ const ids = rows.map(r => r["Row ID"])
+ expect(ids).toContain(
+ Array(50)
+ .fill(0)
+ .map((_, i) => i + 1)
+ )
+ })
+
isInternal &&
it("row values are coerced", async () => {
const str: FieldSchema = {
From 95faefcb87d9a6fd7a214abd30a9a760407348bd Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 9 May 2024 12:28:44 +0200
Subject: [PATCH 275/338] DRY
---
.../DataTable/modals/CreateEditColumn.svelte | 12 +++++-----
.../grid/cells/BBReferenceCell.svelte | 22 ++++++-------------
.../src/components/grid/lib/utils.js | 11 +++-------
.../src/api/controllers/row/utils/basic.ts | 10 +++------
packages/shared-core/src/helpers/index.ts | 1 +
packages/shared-core/src/helpers/schema.ts | 13 +++++++++++
6 files changed, 33 insertions(+), 36 deletions(-)
create mode 100644 packages/shared-core/src/helpers/schema.ts
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index b06e62613d..69aaa4cd88 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -13,7 +13,11 @@
Layout,
AbsTooltip,
} from "@budibase/bbui"
- import { SWITCHABLE_TYPES, ValidColumnNameRegex } from "@budibase/shared-core"
+ import {
+ SWITCHABLE_TYPES,
+ ValidColumnNameRegex,
+ helpers,
+ } from "@budibase/shared-core"
import { createEventDispatcher, getContext, onMount } from "svelte"
import { cloneDeep } from "lodash/fp"
import { tables, datasources } from "stores/builder"
@@ -361,11 +365,7 @@
function getAllowedTypes(datasource) {
if (originalName) {
let possibleTypes = SWITCHABLE_TYPES[field.type] || [editableColumn.type]
- if (
- editableColumn.type === FieldType.BB_REFERENCE &&
- editableColumn.subtype === BBReferenceFieldSubType.USER &&
- editableColumn.constraints?.type !== "array"
- ) {
+ if (helpers.schema.isDeprecatedSingleUserColumn(editableColumn)) {
// This will handle old single users columns
return [
{
diff --git a/packages/frontend-core/src/components/grid/cells/BBReferenceCell.svelte b/packages/frontend-core/src/components/grid/cells/BBReferenceCell.svelte
index 138ad68834..5d98ba903b 100644
--- a/packages/frontend-core/src/components/grid/cells/BBReferenceCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/BBReferenceCell.svelte
@@ -1,5 +1,6 @@
{#if schemaHasOptions(schema) && schema.type !== "array"}
@@ -77,6 +100,35 @@
on:change={e => onChange(e, field)}
useLabel={false}
/>
+{:else if schema.type === FieldType.ATTACHMENTS || schema.type === FieldType.ATTACHMENT_SINGLE}
+
+
+ onChange(
+ {
+ detail:
+ schema.type === FieldType.ATTACHMENT_SINGLE
+ ? e.detail.length > 0
+ ? { url: e.detail[0].name, filename: e.detail[0].value }
+ : {}
+ : e.detail.map(({ name, value }) => ({
+ url: name,
+ filename: value,
+ })),
+ },
+ field
+ )}
+ object={handleAttachmentParams(value[field])}
+ allowJS
+ {bindings}
+ keyBindings
+ customButtonText={"Add attachment"}
+ keyPlaceholder={"URL"}
+ valuePlaceholder={"Filename"}
+ actionButtonDisabled={schema.type === FieldType.ATTACHMENT_SINGLE &&
+ Object.keys(value[field]).length >= 1}
+ />
+
{:else if ["string", "number", "bigint", "barcodeqr", "array"].includes(schema.type)}
{/if}
+
+
diff --git a/packages/builder/src/components/common/bindings/DrawerBindableSlot.svelte b/packages/builder/src/components/common/bindings/DrawerBindableSlot.svelte
index 8ce9dda209..fb448cca8d 100644
--- a/packages/builder/src/components/common/bindings/DrawerBindableSlot.svelte
+++ b/packages/builder/src/components/common/bindings/DrawerBindableSlot.svelte
@@ -4,6 +4,7 @@
readableToRuntimeBinding,
runtimeToReadableBinding,
} from "dataBinding"
+ import { FieldType } from "@budibase/types"
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
import { createEventDispatcher, setContext } from "svelte"
@@ -102,6 +103,8 @@
longform: value => !isJSBinding(value),
json: value => !isJSBinding(value),
boolean: isValidBoolean,
+ attachment: false,
+ attachment_single: false,
}
const isValid = value => {
@@ -116,7 +119,16 @@
if (type === "json" && !isJSBinding(value)) {
return "json-slot-icon"
}
- if (!["string", "number", "bigint", "barcodeqr"].includes(type)) {
+ if (
+ ![
+ "string",
+ "number",
+ "bigint",
+ "barcodeqr",
+ "attachment",
+ "attachment_single",
+ ].includes(type)
+ ) {
return "slot-icon"
}
return ""
@@ -157,7 +169,7 @@
{updateOnChange}
/>
{/if}
- {#if !disabled && type !== "formula"}
+ {#if !disabled && type !== "formula" && !disabled && type !== FieldType.ATTACHMENTS && !disabled && type !== FieldType.ATTACHMENT_SINGLE}
{
diff --git a/packages/builder/src/components/integration/KeyValueBuilder.svelte b/packages/builder/src/components/integration/KeyValueBuilder.svelte
index 5ed18a970a..6f69e71ccb 100644
--- a/packages/builder/src/components/integration/KeyValueBuilder.svelte
+++ b/packages/builder/src/components/integration/KeyValueBuilder.svelte
@@ -37,6 +37,7 @@
export let customButtonText = null
export let keyBindings = false
export let allowJS = false
+ export let actionButtonDisabled = false
export let compare = (option, value) => option === value
let fields = Object.entries(object || {}).map(([name, value]) => ({
@@ -189,7 +190,14 @@
{/if}
{#if !readOnly && !noAddButton}
-
+
{#if customButtonText}
{customButtonText}
{:else}
diff --git a/packages/server/src/automations/automationUtils.ts b/packages/server/src/automations/automationUtils.ts
index 6730e04494..c94c166be1 100644
--- a/packages/server/src/automations/automationUtils.ts
+++ b/packages/server/src/automations/automationUtils.ts
@@ -4,8 +4,11 @@ import {
encodeJSBinding,
} from "@budibase/string-templates"
import sdk from "../sdk"
-import { Row } from "@budibase/types"
+import { AutomationAttachment, FieldType, Row } from "@budibase/types"
import { LoopInput, LoopStepType } from "../definitions/automations"
+import { objectStore, context } from "@budibase/backend-core"
+import * as uuid from "uuid"
+import path from "path"
/**
* When values are input to the system generally they will be of type string as this is required for template strings.
@@ -96,6 +99,98 @@ export function getError(err: any) {
return typeof err !== "string" ? err.toString() : err
}
+export async function sendAutomationAttachmentsToStorage(
+ tableId: string,
+ row: Row
+): Promise {
+ const table = await sdk.tables.getTable(tableId)
+ const attachmentRows: Record<
+ string,
+ AutomationAttachment[] | AutomationAttachment
+ > = {}
+
+ for (const [prop, value] of Object.entries(row)) {
+ const schema = table.schema[prop]
+ if (
+ schema?.type === FieldType.ATTACHMENTS ||
+ schema?.type === FieldType.ATTACHMENT_SINGLE
+ ) {
+ attachmentRows[prop] = value
+ }
+ }
+ for (const [prop, attachments] of Object.entries(attachmentRows)) {
+ if (Array.isArray(attachments)) {
+ if (attachments.length) {
+ row[prop] = await Promise.all(
+ attachments.map(attachment => generateAttachmentRow(attachment))
+ )
+ }
+ } else if (Object.keys(row[prop]).length > 0) {
+ row[prop] = await generateAttachmentRow(attachments)
+ }
+ }
+
+ return row
+}
+
+async function generateAttachmentRow(attachment: AutomationAttachment) {
+ const prodAppId = context.getProdAppId()
+
+ async function uploadToS3(
+ extension: string,
+ content: objectStore.StreamTypes
+ ) {
+ const fileName = `${uuid.v4()}${extension}`
+ const s3Key = `${prodAppId}/attachments/${fileName}`
+
+ await objectStore.streamUpload({
+ bucket: objectStore.ObjectStoreBuckets.APPS,
+ stream: content,
+ filename: s3Key,
+ })
+
+ return s3Key
+ }
+
+ async function getSize(s3Key: string) {
+ return (
+ await objectStore.getObjectMetadata(
+ objectStore.ObjectStoreBuckets.APPS,
+ s3Key
+ )
+ ).ContentLength
+ }
+
+ try {
+ const { filename } = attachment
+ const extension = path.extname(filename)
+ const attachmentResult = await objectStore.processAutomationAttachment(
+ attachment
+ )
+
+ let s3Key = ""
+ if (
+ "path" in attachmentResult &&
+ attachmentResult.path.startsWith(`${prodAppId}/attachments/`)
+ ) {
+ s3Key = attachmentResult.path
+ } else {
+ s3Key = await uploadToS3(extension, attachmentResult.content)
+ }
+
+ const size = await getSize(s3Key)
+
+ return {
+ size,
+ name: filename,
+ extension,
+ key: s3Key,
+ }
+ } catch (error) {
+ console.error("Failed to process attachment:", error)
+ throw error
+ }
+}
export function substituteLoopStep(hbsString: string, substitute: string) {
let checkForJS = isJSBinding(hbsString)
let substitutedHbsString = ""
diff --git a/packages/server/src/automations/steps/createRow.ts b/packages/server/src/automations/steps/createRow.ts
index d11baab5c6..5b5084b465 100644
--- a/packages/server/src/automations/steps/createRow.ts
+++ b/packages/server/src/automations/steps/createRow.ts
@@ -1,5 +1,9 @@
import { save } from "../../api/controllers/row"
-import { cleanUpRow, getError } from "../automationUtils"
+import {
+ cleanUpRow,
+ getError,
+ sendAutomationAttachmentsToStorage,
+} from "../automationUtils"
import { buildCtx } from "./utils"
import {
AutomationActionStepId,
@@ -89,6 +93,10 @@ export async function run({ inputs, appId, emitter }: AutomationStepInput) {
try {
inputs.row = await cleanUpRow(inputs.row.tableId, inputs.row)
+ inputs.row = await sendAutomationAttachmentsToStorage(
+ inputs.row.tableId,
+ inputs.row
+ )
await save(ctx)
return {
row: inputs.row,
diff --git a/packages/server/src/automations/steps/updateRow.ts b/packages/server/src/automations/steps/updateRow.ts
index fea3e981f3..348c5e8373 100644
--- a/packages/server/src/automations/steps/updateRow.ts
+++ b/packages/server/src/automations/steps/updateRow.ts
@@ -108,7 +108,15 @@ export async function run({ inputs, appId, emitter }: AutomationStepInput) {
try {
if (tableId) {
- inputs.row = await automationUtils.cleanUpRow(tableId, inputs.row)
+ inputs.row = await automationUtils.cleanUpRow(
+ inputs.row.tableId,
+ inputs.row
+ )
+
+ inputs.row = await automationUtils.sendAutomationAttachmentsToStorage(
+ inputs.row.tableId,
+ inputs.row
+ )
}
await rowController.patch(ctx)
return {
diff --git a/packages/server/src/automations/tests/createRow.spec.ts b/packages/server/src/automations/tests/createRow.spec.ts
index 0098be39a5..e78236c5ac 100644
--- a/packages/server/src/automations/tests/createRow.spec.ts
+++ b/packages/server/src/automations/tests/createRow.spec.ts
@@ -1,5 +1,18 @@
import * as setup from "./utilities"
+import { basicTableWithAttachmentField } from "../../tests/utilities/structures"
+import { objectStore } from "@budibase/backend-core"
+async function uploadTestFile(filename: string) {
+ let bucket = "testbucket"
+ await objectStore.upload({
+ bucket,
+ filename,
+ body: Buffer.from("test data"),
+ })
+ let presignedUrl = await objectStore.getPresignedUrl(bucket, filename, 60000)
+
+ return presignedUrl
+}
describe("test the create row action", () => {
let table: any
let row: any
@@ -43,4 +56,76 @@ describe("test the create row action", () => {
const res = await setup.runStep(setup.actions.CREATE_ROW.stepId, {})
expect(res.success).toEqual(false)
})
+
+ it("should check that an attachment field is sent to storage and parsed", async () => {
+ let attachmentTable = await config.createTable(
+ basicTableWithAttachmentField()
+ )
+
+ let attachmentRow: any = {
+ tableId: attachmentTable._id,
+ }
+
+ let filename = "test1.txt"
+ let presignedUrl = await uploadTestFile(filename)
+ let attachmentObject = [
+ {
+ url: presignedUrl,
+ filename,
+ },
+ ]
+
+ attachmentRow.file_attachment = attachmentObject
+ const res = await setup.runStep(setup.actions.CREATE_ROW.stepId, {
+ row: attachmentRow,
+ })
+
+ expect(res.success).toEqual(true)
+ expect(res.row.file_attachment[0]).toHaveProperty("key")
+ let s3Key = res.row.file_attachment[0].key
+
+ const client = objectStore.ObjectStore(objectStore.ObjectStoreBuckets.APPS)
+
+ const objectData = await client
+ .headObject({ Bucket: objectStore.ObjectStoreBuckets.APPS, Key: s3Key })
+ .promise()
+
+ expect(objectData).toBeDefined()
+ expect(objectData.ContentLength).toBeGreaterThan(0)
+ })
+
+ it("should check that an single attachment field is sent to storage and parsed", async () => {
+ let attachmentTable = await config.createTable(
+ basicTableWithAttachmentField()
+ )
+
+ let attachmentRow: any = {
+ tableId: attachmentTable._id,
+ }
+
+ let filename = "test2.txt"
+ let presignedUrl = await uploadTestFile(filename)
+ let attachmentObject = {
+ url: presignedUrl,
+ filename,
+ }
+
+ attachmentRow.single_file_attachment = attachmentObject
+ const res = await setup.runStep(setup.actions.CREATE_ROW.stepId, {
+ row: attachmentRow,
+ })
+
+ expect(res.success).toEqual(true)
+ expect(res.row.single_file_attachment).toHaveProperty("key")
+ let s3Key = res.row.single_file_attachment.key
+
+ const client = objectStore.ObjectStore(objectStore.ObjectStoreBuckets.APPS)
+
+ const objectData = await client
+ .headObject({ Bucket: objectStore.ObjectStoreBuckets.APPS, Key: s3Key })
+ .promise()
+
+ expect(objectData).toBeDefined()
+ expect(objectData.ContentLength).toBeGreaterThan(0)
+ })
})
diff --git a/packages/server/src/integrations/rest.ts b/packages/server/src/integrations/rest.ts
index 0613c3ade8..f5a12c2cbf 100644
--- a/packages/server/src/integrations/rest.ts
+++ b/packages/server/src/integrations/rest.ts
@@ -139,13 +139,13 @@ class RestIntegration implements IntegrationBase {
const contentType = response.headers.get("content-type") || ""
const contentDisposition = response.headers.get("content-disposition") || ""
if (
+ contentDisposition.includes("filename") ||
contentDisposition.includes("attachment") ||
contentDisposition.includes("form-data")
) {
filename =
path.basename(parse(contentDisposition).parameters?.filename) || ""
}
-
try {
if (filename) {
return handleFileResponse(response, filename, this.startTimeMs)
diff --git a/packages/server/src/tests/utilities/structures.ts b/packages/server/src/tests/utilities/structures.ts
index 2a32489c30..77a6431335 100644
--- a/packages/server/src/tests/utilities/structures.ts
+++ b/packages/server/src/tests/utilities/structures.ts
@@ -78,6 +78,32 @@ export function basicTable(
)
}
+export function basicTableWithAttachmentField(
+ datasource?: Datasource,
+ ...extra: Partial[]
+): Table {
+ return tableForDatasource(
+ datasource,
+ {
+ name: "TestTable",
+ schema: {
+ file_attachment: {
+ type: FieldType.ATTACHMENTS,
+ name: "description",
+ constraints: {
+ type: "array",
+ },
+ },
+ single_file_attachment: {
+ type: FieldType.ATTACHMENT_SINGLE,
+ name: "description",
+ },
+ },
+ },
+ ...extra
+ )
+}
+
export function basicView(tableId: string) {
return {
tableId,
diff --git a/packages/server/src/utilities/rowProcessor/index.ts b/packages/server/src/utilities/rowProcessor/index.ts
index efa1ff1bd8..8cb03de948 100644
--- a/packages/server/src/utilities/rowProcessor/index.ts
+++ b/packages/server/src/utilities/rowProcessor/index.ts
@@ -234,7 +234,7 @@ export async function outputProcessing(
}
} else if (column.type === FieldType.ATTACHMENT_SINGLE) {
for (let row of enriched) {
- if (!row[property]) {
+ if (!row[property] || Object.keys(row[property]).length === 0) {
continue
}
diff --git a/packages/types/src/documents/app/automation.ts b/packages/types/src/documents/app/automation.ts
index c3847a2c04..481a051e1c 100644
--- a/packages/types/src/documents/app/automation.ts
+++ b/packages/types/src/documents/app/automation.ts
@@ -1,6 +1,7 @@
import { Document } from "../document"
import { EventEmitter } from "events"
import { User } from "../global"
+import { ReadStream } from "fs"
export enum AutomationIOType {
OBJECT = "object",
@@ -235,3 +236,18 @@ export interface AutomationMetadata extends Document {
errorCount?: number
automationChainCount?: number
}
+
+export type AutomationAttachment = {
+ url: string
+ filename: string
+}
+
+export type AutomationAttachmentContent = {
+ filename: string
+ content: ReadStream | NodeJS.ReadableStream | ReadableStream
+}
+
+export type BucketedContent = AutomationAttachmentContent & {
+ bucket: string
+ path: string
+}
diff --git a/packages/worker/src/utilities/email.ts b/packages/worker/src/utilities/email.ts
index db9a635356..bf686f647c 100644
--- a/packages/worker/src/utilities/email.ts
+++ b/packages/worker/src/utilities/email.ts
@@ -6,8 +6,7 @@ import { processString } from "@budibase/string-templates"
import { User, SendEmailOpts, SMTPInnerConfig } from "@budibase/types"
import { configs, cache, objectStore } from "@budibase/backend-core"
import ical from "ical-generator"
-import fetch from "node-fetch"
-import path from "path"
+import _ from "lodash"
const nodemailer = require("nodemailer")
@@ -165,39 +164,12 @@ export async function sendEmail(
}),
}
if (opts?.attachments) {
- const attachments = await Promise.all(
- opts.attachments?.map(async attachment => {
- const isFullyFormedUrl =
- attachment.url.startsWith("http://") ||
- attachment.url.startsWith("https://")
- if (isFullyFormedUrl) {
- const response = await fetch(attachment.url)
- if (!response.ok) {
- throw new Error(`unexpected response ${response.statusText}`)
- }
- const fallbackFilename = path.basename(
- new URL(attachment.url).pathname
- )
- return {
- filename: attachment.filename || fallbackFilename,
- content: response?.body,
- }
- } else {
- const url = attachment.url
- const result = objectStore.extractBucketAndPath(url)
- if (result === null) {
- throw new Error("Invalid signed URL")
- }
- const { bucket, path } = result
- const readStream = await objectStore.getReadStream(bucket, path)
- const fallbackFilename = path.split("/").pop() || ""
- return {
- filename: attachment.filename || fallbackFilename,
- content: readStream,
- }
- }
- })
+ let attachments = await Promise.all(
+ opts.attachments?.map(objectStore.processAutomationAttachment)
)
+ attachments = attachments.map(attachment => {
+ return _.omit(attachment, "path")
+ })
message = { ...message, attachments }
}
From 381c33cfb54a4875dfcf789763b38c224bc802bb Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Thu, 9 May 2024 15:10:05 +0100
Subject: [PATCH 283/338] Adding support for buffers in a few places - this
helps with BYTE type columns in SQL.
---
packages/server/src/api/controllers/row/utils/basic.ts | 5 ++++-
packages/server/src/integrations/utils/utils.ts | 5 +++++
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/packages/server/src/api/controllers/row/utils/basic.ts b/packages/server/src/api/controllers/row/utils/basic.ts
index 6255e13c1c..0c2c786e18 100644
--- a/packages/server/src/api/controllers/row/utils/basic.ts
+++ b/packages/server/src/api/controllers/row/utils/basic.ts
@@ -73,12 +73,15 @@ export function basicProcessing({
// filter the row down to what is actually the row (not joined)
for (let field of Object.values(table.schema)) {
const fieldName = field.name
- const value = extractFieldValue({
+ let value = extractFieldValue({
row,
tableName: table.name,
fieldName,
isLinked,
})
+ if (value instanceof Buffer) {
+ value = value.toString()
+ }
// all responses include "select col as table.col" so that overlaps are handled
if (value != null) {
thisRow[fieldName] = value
diff --git a/packages/server/src/integrations/utils/utils.ts b/packages/server/src/integrations/utils/utils.ts
index aac3f5f74a..e2fba11a4e 100644
--- a/packages/server/src/integrations/utils/utils.ts
+++ b/packages/server/src/integrations/utils/utils.ts
@@ -192,6 +192,11 @@ export function generateRowIdField(keyProps: any[] = []) {
if (!Array.isArray(keyProps)) {
keyProps = [keyProps]
}
+ for (let index in keyProps) {
+ if (keyProps[index] instanceof Buffer) {
+ keyProps[index] = keyProps[index].toString()
+ }
+ }
// this conserves order and types
// we have to swap the double quotes to single quotes for use in HBS statements
// when using the literal helper the double quotes can break things
From e928ff2ea25d4020e3fc25690754d2c3cdc898fb Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Thu, 9 May 2024 15:26:53 +0100
Subject: [PATCH 284/338] Adding test case to confirm it works.
---
.../src/integration-test/postgres.spec.ts | 34 +++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/packages/server/src/integration-test/postgres.spec.ts b/packages/server/src/integration-test/postgres.spec.ts
index cf80f2359f..ec4cb90a86 100644
--- a/packages/server/src/integration-test/postgres.spec.ts
+++ b/packages/server/src/integration-test/postgres.spec.ts
@@ -1200,4 +1200,38 @@ describe("postgres integrations", () => {
expect(Object.keys(schema).sort()).toEqual(["id", "val1"])
})
})
+
+ describe("check custom column types", () => {
+ beforeAll(async () => {
+ await rawQuery(
+ rawDatasource,
+ `CREATE TABLE binaryTable (
+ id BYTEA PRIMARY KEY,
+ column1 TEXT,
+ column2 INT
+ );
+ `
+ )
+ })
+
+ it("should handle binary columns", async () => {
+ const response = await makeRequest(
+ "post",
+ `/api/datasources/${datasource._id}/schema`
+ )
+ expect(response.body).toBeDefined()
+ expect(response.body.datasource.entities).toBeDefined()
+ const table = response.body.datasource.entities["binarytable"]
+ expect(table).toBeDefined()
+ expect(table.schema.id.externalType).toBe("bytea")
+ const row = await config.api.row.save(table._id, {
+ id: "1111",
+ column1: "hello",
+ column2: 222,
+ })
+ expect(row._id).toBeDefined()
+ const decoded = decodeURIComponent(row._id!).replace(/'/g, '"')
+ expect(JSON.parse(decoded)[0]).toBe("1111")
+ })
+ })
})
From 60f8ce26af9f476337153b4b0d071c30ec4952b4 Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Thu, 9 May 2024 16:18:43 +0100
Subject: [PATCH 285/338] Fixing an issue with ID/Rev not being copyable from
context menu due to the way IDs were split/handled in the grid.
---
.../src/components/grid/stores/ui.js | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/stores/ui.js b/packages/frontend-core/src/components/grid/stores/ui.js
index 928f93f3e1..b75b9aeb0f 100644
--- a/packages/frontend-core/src/components/grid/stores/ui.js
+++ b/packages/frontend-core/src/components/grid/stores/ui.js
@@ -8,6 +8,15 @@ import {
NewRowID,
} from "../lib/constants"
+function splitRowId(rowId) {
+ if (!rowId) {
+ return undefined
+ }
+ const parts = rowId.split("-")
+ const field = parts.pop()
+ return { id: parts.join("-"), field }
+}
+
export const createStores = context => {
const { props } = context
const focusedCellId = writable(null)
@@ -25,7 +34,7 @@ export const createStores = context => {
const focusedRowId = derived(
focusedCellId,
$focusedCellId => {
- return $focusedCellId?.split("-")[0]
+ return splitRowId($focusedCellId)?.id
},
null
)
@@ -72,7 +81,7 @@ export const deriveStores = context => {
const focusedRow = derived(
[focusedCellId, rowLookupMap, rows],
([$focusedCellId, $rowLookupMap, $rows]) => {
- const rowId = $focusedCellId?.split("-")[0]
+ const rowId = splitRowId($focusedCellId)?.id
// Edge case for new rows
if (rowId === NewRowID) {
@@ -152,7 +161,7 @@ export const initialise = context => {
const hasRow = rows.actions.hasRow
// Check selected cell
- const selectedRowId = $focusedCellId?.split("-")[0]
+ const selectedRowId = splitRowId($focusedCellId)?.id
if (selectedRowId && !hasRow(selectedRowId)) {
focusedCellId.set(null)
}
From bfc63bd4e26a0498c382ffd4eb3f3a4f0b7fc1cf Mon Sep 17 00:00:00 2001
From: Sam Rose
Date: Thu, 9 May 2024 16:26:08 +0100
Subject: [PATCH 286/338] Remove the last internal.spec.ts file.
---
.../src/api/routes/tests/search.spec.ts | 2 +
.../app/rows/search/tests/internal.spec.ts | 117 ------------------
2 files changed, 2 insertions(+), 117 deletions(-)
delete mode 100644 packages/server/src/sdk/app/rows/search/tests/internal.spec.ts
diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts
index 7ea3f063de..d036da646e 100644
--- a/packages/server/src/api/routes/tests/search.spec.ts
+++ b/packages/server/src/api/routes/tests/search.spec.ts
@@ -402,6 +402,7 @@ describe.each([
},
])
})
+
// TODO(samwho): fix for SQS
!isSqs &&
it("should match the session user id in a multi user field", async () => {
@@ -419,6 +420,7 @@ describe.each([
])
})
+ // TODO(samwho): fix for SQS
!isSqs &&
it("should not match the session user id in a multi user field", async () => {
await expectQuery({
diff --git a/packages/server/src/sdk/app/rows/search/tests/internal.spec.ts b/packages/server/src/sdk/app/rows/search/tests/internal.spec.ts
deleted file mode 100644
index 1c5f396737..0000000000
--- a/packages/server/src/sdk/app/rows/search/tests/internal.spec.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import {
- FieldType,
- Row,
- Table,
- RowSearchParams,
- INTERNAL_TABLE_SOURCE_ID,
- TableSourceType,
-} from "@budibase/types"
-import TestConfiguration from "../../../../../tests/utilities/TestConfiguration"
-import { search } from "../internal"
-import {
- expectAnyInternalColsAttributes,
- generator,
-} from "@budibase/backend-core/tests"
-
-describe("internal", () => {
- const config = new TestConfiguration()
-
- const tableData: Table = {
- name: generator.word(),
- type: "table",
- sourceId: INTERNAL_TABLE_SOURCE_ID,
- sourceType: TableSourceType.INTERNAL,
- schema: {
- name: {
- name: "name",
- type: FieldType.STRING,
- constraints: {
- type: FieldType.STRING,
- },
- },
- surname: {
- name: "surname",
- type: FieldType.STRING,
- constraints: {
- type: FieldType.STRING,
- },
- },
- age: {
- name: "age",
- type: FieldType.NUMBER,
- constraints: {
- type: FieldType.NUMBER,
- },
- },
- address: {
- name: "address",
- type: FieldType.STRING,
- constraints: {
- type: FieldType.STRING,
- },
- },
- },
- }
-
- beforeAll(async () => {
- await config.init()
- })
-
- describe("search", () => {
- const rows: Row[] = []
- beforeAll(async () => {
- await config.createTable(tableData)
- for (let i = 0; i < 10; i++) {
- rows.push(
- await config.createRow({
- name: generator.first(),
- surname: generator.last(),
- age: generator.age(),
- address: generator.address(),
- })
- )
- }
- })
-
- it("default search returns all the data", async () => {
- await config.doInContext(config.appId, async () => {
- const tableId = config.table!._id!
-
- const searchParams: RowSearchParams = {
- tableId,
- query: {},
- }
- const result = await search(searchParams, config.table!)
-
- expect(result.rows).toHaveLength(10)
- expect(result.rows).toEqual(
- expect.arrayContaining(rows.map(r => expect.objectContaining(r)))
- )
- })
- })
-
- it("querying by fields will always return data attribute columns", async () => {
- await config.doInContext(config.appId, async () => {
- const tableId = config.table!._id!
-
- const searchParams: RowSearchParams = {
- tableId,
- query: {},
- fields: ["name", "age"],
- }
- const result = await search(searchParams, config.table!)
-
- expect(result.rows).toHaveLength(10)
- expect(result.rows).toEqual(
- expect.arrayContaining(
- rows.map(r => ({
- ...expectAnyInternalColsAttributes,
- name: r.name,
- age: r.age,
- }))
- )
- )
- })
- })
- })
-})
From 076c7db3510fb727462a87c7e9a4fb184a75cbf6 Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Thu, 9 May 2024 16:55:04 +0100
Subject: [PATCH 287/338] Updating everywhere that the combination was done, to
make sure it all goes through one location, using a new character to
join/split.
---
.../src/components/grid/layout/GridRow.svelte | 3 ++-
.../src/components/grid/layout/NewRow.svelte | 7 ++++---
.../components/grid/layout/StickyColumn.svelte | 3 ++-
.../src/components/grid/lib/utils.js | 15 +++++++++++++++
.../grid/overlays/KeyboardManager.svelte | 12 ++++++------
.../components/grid/overlays/MenuOverlay.svelte | 3 ++-
.../src/components/grid/stores/rows.js | 14 ++++++++------
.../src/components/grid/stores/scroll.js | 3 ++-
.../src/components/grid/stores/ui.js | 10 +---------
.../src/components/grid/stores/validation.js | 7 ++++---
10 files changed, 46 insertions(+), 31 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/layout/GridRow.svelte b/packages/frontend-core/src/components/grid/layout/GridRow.svelte
index d6c8c3d87b..0b1b4195b2 100644
--- a/packages/frontend-core/src/components/grid/layout/GridRow.svelte
+++ b/packages/frontend-core/src/components/grid/layout/GridRow.svelte
@@ -1,6 +1,7 @@
diff --git a/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte b/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte
index 9b4e5e36f6..1b52e35314 100644
--- a/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte
+++ b/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte
@@ -25,21 +25,21 @@
return !!schema.constraints?.inclusion?.length
}
- const handleAttachmentParams = keyValuObj => {
+ function handleAttachmentParams(keyValueObj) {
let params = {}
if (
schema.type === FieldType.ATTACHMENT_SINGLE &&
- Object.keys(keyValuObj).length === 0
+ Object.keys(keyValueObj).length === 0
) {
return []
}
- if (!Array.isArray(keyValuObj)) {
- keyValuObj = [keyValuObj]
+ if (!Array.isArray(keyValueObj) && keyValueObj) {
+ keyValueObj = [keyValueObj]
}
- if (keyValuObj.length) {
- for (let param of keyValuObj) {
+ if (keyValueObj.length) {
+ for (let param of keyValueObj) {
params[param.url] = param.filename
}
}
diff --git a/packages/server/src/automations/automationUtils.ts b/packages/server/src/automations/automationUtils.ts
index c94c166be1..88b279b771 100644
--- a/packages/server/src/automations/automationUtils.ts
+++ b/packages/server/src/automations/automationUtils.ts
@@ -163,7 +163,7 @@ async function generateAttachmentRow(attachment: AutomationAttachment) {
try {
const { filename } = attachment
- const extension = path.extname(filename)
+ const extension = path.extname(filename).replaceAll(".", "")
const attachmentResult = await objectStore.processAutomationAttachment(
attachment
)
@@ -183,7 +183,7 @@ async function generateAttachmentRow(attachment: AutomationAttachment) {
return {
size,
name: filename,
- extension,
+ extension: extension,
key: s3Key,
}
} catch (error) {
diff --git a/packages/server/src/automations/steps/updateRow.ts b/packages/server/src/automations/steps/updateRow.ts
index 348c5e8373..32c8addd7a 100644
--- a/packages/server/src/automations/steps/updateRow.ts
+++ b/packages/server/src/automations/steps/updateRow.ts
@@ -94,18 +94,6 @@ export async function run({ inputs, appId, emitter }: AutomationStepInput) {
}
}
- // have to clean up the row, remove the table from it
- const ctx: any = buildCtx(appId, emitter, {
- body: {
- ...inputs.row,
- _id: inputs.rowId,
- },
- params: {
- rowId: inputs.rowId,
- tableId: tableId,
- },
- })
-
try {
if (tableId) {
inputs.row = await automationUtils.cleanUpRow(
@@ -118,6 +106,17 @@ export async function run({ inputs, appId, emitter }: AutomationStepInput) {
inputs.row
)
}
+ // have to clean up the row, remove the table from it
+ const ctx: any = buildCtx(appId, emitter, {
+ body: {
+ ...inputs.row,
+ _id: inputs.rowId,
+ },
+ params: {
+ rowId: inputs.rowId,
+ tableId: tableId,
+ },
+ })
await rowController.patch(ctx)
return {
row: ctx.body,
diff --git a/packages/server/src/utilities/rowProcessor/attachments.ts b/packages/server/src/utilities/rowProcessor/attachments.ts
index da52d6a631..bfa216c25b 100644
--- a/packages/server/src/utilities/rowProcessor/attachments.ts
+++ b/packages/server/src/utilities/rowProcessor/attachments.ts
@@ -1,6 +1,12 @@
import { ObjectStoreBuckets } from "../../constants"
import { context, db as dbCore, objectStore } from "@budibase/backend-core"
-import { FieldType, RenameColumn, Row, Table } from "@budibase/types"
+import {
+ FieldType,
+ RenameColumn,
+ Row,
+ RowAttachment,
+ Table,
+} from "@budibase/types"
export class AttachmentCleanup {
static async coreCleanup(fileListFn: () => string[]): Promise
{
@@ -21,7 +27,7 @@ export class AttachmentCleanup {
private static extractAttachmentKeys(
type: FieldType,
- rowData: any
+ rowData: RowAttachment[] | RowAttachment
): string[] {
if (
type !== FieldType.ATTACHMENTS &&
@@ -34,10 +40,15 @@ export class AttachmentCleanup {
return []
}
- if (type === FieldType.ATTACHMENTS) {
- return rowData.map((attachment: any) => attachment.key)
+ if (type === FieldType.ATTACHMENTS && Array.isArray(rowData)) {
+ return rowData
+ .filter(attachment => attachment.key)
+ .map(attachment => attachment.key)
+ } else if ("key" in rowData) {
+ return [rowData.key]
}
- return [rowData.key]
+
+ return []
}
private static async tableChange(
diff --git a/packages/server/src/utilities/rowProcessor/index.ts b/packages/server/src/utilities/rowProcessor/index.ts
index b71f40e870..e7bc725285 100644
--- a/packages/server/src/utilities/rowProcessor/index.ts
+++ b/packages/server/src/utilities/rowProcessor/index.ts
@@ -1,11 +1,11 @@
import * as linkRows from "../../db/linkedRows"
-import { processFormulas, fixAutoColumnSubType } from "./utils"
+import { fixAutoColumnSubType, processFormulas } from "./utils"
import { objectStore, utils } from "@budibase/backend-core"
import { InternalTables } from "../../db/utils"
import { TYPE_TRANSFORM_MAP } from "./map"
import {
- FieldType,
AutoFieldSubType,
+ FieldType,
Row,
RowAttachment,
Table,
@@ -221,27 +221,31 @@ export async function outputProcessing(
opts.squash = true
}
- // process complex types: attachements, bb references...
+ // process complex types: attachments, bb references...
for (let [property, column] of Object.entries(table.schema)) {
- if (column.type === FieldType.ATTACHMENTS) {
+ if (
+ column.type === FieldType.ATTACHMENTS ||
+ column.type === FieldType.ATTACHMENT_SINGLE
+ ) {
for (let row of enriched) {
- if (row[property] == null || !Array.isArray(row[property])) {
+ if (row[property] == null) {
continue
}
- row[property].forEach((attachment: RowAttachment) => {
- if (!attachment.url) {
+ const process = (attachment: RowAttachment) => {
+ if (!attachment.url && attachment.key) {
attachment.url = objectStore.getAppFileUrl(attachment.key)
}
- })
- }
- } else if (column.type === FieldType.ATTACHMENT_SINGLE) {
- for (let row of enriched) {
- if (!row[property] || Object.keys(row[property]).length === 0) {
- continue
+ return attachment
}
-
- if (!row[property].url) {
- row[property].url = objectStore.getAppFileUrl(row[property].key)
+ if (typeof row[property] === "string") {
+ row[property] = JSON.parse(row[property])
+ }
+ if (Array.isArray(row[property])) {
+ row[property].forEach((attachment: RowAttachment) => {
+ process(attachment)
+ })
+ } else {
+ process(row[property])
}
}
} else if (
From d2e72889d63e63a3edf1746199aa8062d2a134c8 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 14 May 2024 09:39:55 +0200
Subject: [PATCH 335/338] Update
---
packages/server/package.json | 2 +-
yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/server/package.json b/packages/server/package.json
index 0e12d387a6..ab6738635d 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -79,7 +79,7 @@
"dotenv": "8.2.0",
"form-data": "4.0.0",
"global-agent": "3.0.0",
- "google-spreadsheet": "4.1.1",
+ "google-spreadsheet": "4.1.2",
"ioredis": "5.3.2",
"isolated-vm": "^4.7.2",
"jimp": "0.22.10",
diff --git a/yarn.lock b/yarn.lock
index 3120531556..cb97cfe0af 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11525,10 +11525,10 @@ google-p12-pem@^4.0.0:
dependencies:
node-forge "^1.3.1"
-google-spreadsheet@4.1.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/google-spreadsheet/-/google-spreadsheet-4.1.1.tgz#56c422c86b51a2ea9dad21c40cd69bccbd58591f"
- integrity sha512-Npk/xAMTgxEt/m/X9EXIqdY6CEYGiqUHrSuiLnNSKli5H+wiOQLSLsnfMxcdNPH6aSh6GttZm6QJhrnsxjwpZQ==
+google-spreadsheet@4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/google-spreadsheet/-/google-spreadsheet-4.1.2.tgz#92e30fdba7e0d78c55d50731528df7835d58bfee"
+ integrity sha512-HFBweDAkOcyC2qO9kmWESKbNuOcn+R7UzZN/tj5LLNxVv8FHmg113u0Ow+yaKwwIOt/NnDtPLuptAhaxTs0FYw==
dependencies:
axios "^1.4.0"
lodash "^4.17.21"
From c9d5112fcc6522e1035e2a4b8ae559b466329e53 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 14 May 2024 11:35:01 +0200
Subject: [PATCH 336/338] Change methods to private
---
.../server/src/integrations/googlesheets.ts | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/packages/server/src/integrations/googlesheets.ts b/packages/server/src/integrations/googlesheets.ts
index 6d20987ae7..dc945b454a 100644
--- a/packages/server/src/integrations/googlesheets.ts
+++ b/packages/server/src/integrations/googlesheets.ts
@@ -191,7 +191,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
* @param spreadsheetId - the URL or standard spreadsheetId of the google sheet
* @returns spreadsheet Id of the google sheet
*/
- cleanSpreadsheetUrl(spreadsheetId: string) {
+ private cleanSpreadsheetUrl(spreadsheetId: string) {
if (!spreadsheetId) {
throw new Error(
"You must set a spreadsheet ID in your configuration to fetch tables."
@@ -201,7 +201,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
return parts.length > 5 ? parts[5] : spreadsheetId
}
- async fetchAccessToken(
+ private async fetchAccessToken(
payload: AuthTokenRequest
): Promise {
const response = await fetch("https://www.googleapis.com/oauth2/v4/token", {
@@ -226,7 +226,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
return json
}
- async connect() {
+ private async connect() {
try {
await setupCreationAuth(this.config)
@@ -271,7 +271,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
return sheets.map(s => s.title)
}
- getTableSchema(
+ private getTableSchema(
title: string,
headerValues: string[],
datasourceId: string,
@@ -385,7 +385,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
}
}
- buildRowObject(
+ private buildRowObject(
headers: string[],
values: Record,
rowNumber: number
@@ -400,7 +400,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
return rowObject
}
- async createTable(name?: string) {
+ private async createTable(name?: string) {
if (!name) {
throw new Error("Must provide name for new sheet.")
}
@@ -413,7 +413,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
}
}
- async updateTable(table: TableRequest) {
+ private async updateTable(table: TableRequest) {
await this.connect()
const sheet = this.client.sheetsByTitle[table.name]
await sheet.loadHeaderRow()
@@ -460,7 +460,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
}
}
- async deleteTable(sheet: any) {
+ private async deleteTable(sheet: any) {
try {
await this.connect()
const sheetToDelete = this.client.sheetsByTitle[sheet]
@@ -487,7 +487,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
}
}
- async createBulk(query: { sheet: string; rows: Row[] }) {
+ private async createBulk(query: { sheet: string; rows: Row[] }) {
try {
await this.connect()
const sheet = this.client.sheetsByTitle[query.sheet]
From 39147b80b627e0d26359f7aa176ed5fe3354e6ab Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Tue, 14 May 2024 11:23:50 +0100
Subject: [PATCH 337/338] PR comments.
---
packages/backend-core/src/objectStore/utils.ts | 3 +++
packages/server/src/automations/automationUtils.ts | 7 +++++--
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/packages/backend-core/src/objectStore/utils.ts b/packages/backend-core/src/objectStore/utils.ts
index 2edb075b92..30c2fefbf1 100644
--- a/packages/backend-core/src/objectStore/utils.ts
+++ b/packages/backend-core/src/objectStore/utils.ts
@@ -70,6 +70,9 @@ async function processUrlAttachment(
throw new Error(`Unexpected response ${response.statusText}`)
}
const fallbackFilename = path.basename(new URL(attachment.url).pathname)
+ if (!response.body) {
+ throw new Error("No response received for attachment")
+ }
return {
filename: attachment.filename || fallbackFilename,
content: stream.Readable.fromWeb(response.body as streamWeb.ReadableStream),
diff --git a/packages/server/src/automations/automationUtils.ts b/packages/server/src/automations/automationUtils.ts
index 88b279b771..cb09f860da 100644
--- a/packages/server/src/automations/automationUtils.ts
+++ b/packages/server/src/automations/automationUtils.ts
@@ -163,7 +163,10 @@ async function generateAttachmentRow(attachment: AutomationAttachment) {
try {
const { filename } = attachment
- const extension = path.extname(filename).replaceAll(".", "")
+ let extension = path.extname(filename)
+ if (extension.startsWith(".")) {
+ extension = extension.substring(1, extension.length)
+ }
const attachmentResult = await objectStore.processAutomationAttachment(
attachment
)
@@ -182,8 +185,8 @@ async function generateAttachmentRow(attachment: AutomationAttachment) {
return {
size,
+ extension,
name: filename,
- extension: extension,
key: s3Key,
}
} catch (error) {
From 7eb03623ef45c6ee8b410aaa9866163c3907dfcc Mon Sep 17 00:00:00 2001
From: Budibase Staging Release Bot <>
Date: Tue, 14 May 2024 11:13:07 +0000
Subject: [PATCH 338/338] Bump version to 2.26.2
---
lerna.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lerna.json b/lerna.json
index b29f82c71a..1e49def198 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "version": "2.26.1",
+ "version": "2.26.2",
"npmClient": "yarn",
"packages": [
"packages/*",