Merge branch 'master' into allow-public-view-attachment-uploads

This commit is contained in:
deanhannigan 2025-03-11 11:44:16 +00:00 committed by GitHub
commit 17614e8aad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 425 additions and 530 deletions

View File

@ -1,6 +1,6 @@
{ {
"$schema": "node_modules/lerna/schemas/lerna-schema.json", "$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "3.4.24", "version": "3.5.0",
"npmClient": "yarn", "npmClient": "yarn",
"concurrency": 20, "concurrency": 20,
"command": { "command": {

View File

@ -18,7 +18,6 @@
"eslint-plugin-jest": "28.9.0", "eslint-plugin-jest": "28.9.0",
"eslint-plugin-local-rules": "3.0.2", "eslint-plugin-local-rules": "3.0.2",
"eslint-plugin-svelte": "2.46.1", "eslint-plugin-svelte": "2.46.1",
"svelte-preprocess": "^6.0.3",
"husky": "^8.0.3", "husky": "^8.0.3",
"kill-port": "^1.6.1", "kill-port": "^1.6.1",
"lerna": "7.4.2", "lerna": "7.4.2",
@ -29,7 +28,9 @@
"prettier-plugin-svelte": "^2.3.0", "prettier-plugin-svelte": "^2.3.0",
"proper-lockfile": "^4.1.2", "proper-lockfile": "^4.1.2",
"svelte": "4.2.19", "svelte": "4.2.19",
"svelte-check": "^4.1.5",
"svelte-eslint-parser": "0.43.0", "svelte-eslint-parser": "0.43.0",
"svelte-preprocess": "^6.0.3",
"typescript": "5.7.2", "typescript": "5.7.2",
"typescript-eslint": "8.17.0", "typescript-eslint": "8.17.0",
"yargs": "^17.7.2" "yargs": "^17.7.2"

View File

@ -1,14 +1,14 @@
<script> <script lang="ts">
import "@spectrum-css/textfield/dist/index-vars.css" import "@spectrum-css/textfield/dist/index-vars.css"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
export let value = "" export let value = ""
export let placeholder = null export let placeholder: string | undefined = undefined
export let disabled = false export let disabled = false
export let readonly = false export let readonly = false
export let id = null export let id: string | undefined = undefined
export let height = null export let height: string | number | undefined = undefined
export let minHeight = null export let minHeight: string | number | undefined = undefined
export const getCaretPosition = () => ({ export const getCaretPosition = () => ({
start: textarea.selectionStart, start: textarea.selectionStart,
end: textarea.selectionEnd, end: textarea.selectionEnd,
@ -16,18 +16,21 @@
export let align = null export let align = null
let focus = false let focus = false
let textarea let textarea: any
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = event => { const onChange = (event: any) => {
dispatch("change", event.target.value) dispatch("change", event.target.value)
focus = false focus = false
} }
const getStyleString = (attribute, value) => { const getStyleString = (
attribute: string,
value: string | number | undefined
) => {
if (!attribute || value == null) { if (!attribute || value == null) {
return "" return ""
} }
if (isNaN(value)) { if (typeof value === "number" && isNaN(value)) {
return `${attribute}:${value};` return `${attribute}:${value};`
} }
return `${attribute}:${value}px;` return `${attribute}:${value}px;`

View File

@ -1,21 +1,21 @@
<script> <script lang="ts">
import Field from "./Field.svelte" import Field from "./Field.svelte"
import TextArea from "./Core/TextArea.svelte" import TextArea from "./Core/TextArea.svelte"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
export let value = null export let value: string | undefined = undefined
export let label = null export let label: string | undefined = undefined
export let labelPosition = "above" export let labelPosition: string = "above"
export let placeholder = null export let placeholder: string | undefined = undefined
export let disabled = false export let disabled = false
export let error = null export let error: string | undefined = undefined
export let getCaretPosition = null export let getCaretPosition: any = undefined
export let height = null export let height: string | number | undefined = undefined
export let minHeight = null export let minHeight: string | number | undefined = undefined
export let helpText = null export let helpText: string | undefined = undefined
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = e => { const onChange = (e: any) => {
value = e.detail value = e.detail
dispatch("change", e.detail) dispatch("change", e.detail)
} }
@ -24,7 +24,6 @@
<Field {helpText} {label} {labelPosition} {error}> <Field {helpText} {label} {labelPosition} {error}>
<TextArea <TextArea
bind:getCaretPosition bind:getCaretPosition
{error}
{disabled} {disabled}
{value} {value}
{placeholder} {placeholder}

View File

@ -1,6 +1,7 @@
<script> <script>
import "@spectrum-css/inlinealert/dist/index-vars.css" import "@spectrum-css/inlinealert/dist/index-vars.css"
import Button from "../Button/Button.svelte" import Button from "../Button/Button.svelte"
import Icon from "../Icon/Icon.svelte"
export let type = "info" export let type = "info"
export let header = "" export let header = ""
@ -8,6 +9,8 @@
export let onConfirm = undefined export let onConfirm = undefined
export let buttonText = "" export let buttonText = ""
export let cta = false export let cta = false
export let link = ""
export let linkText = ""
$: icon = selectIcon(type) $: icon = selectIcon(type)
// if newlines used, convert them to different elements // if newlines used, convert them to different elements
@ -49,6 +52,19 @@
> >
</div> </div>
{/if} {/if}
{#if link && linkText}
<div id="docs-link">
<a
href={link}
target="_blank"
rel="noopener noreferrer"
class="docs-link"
>
{linkText}
<Icon name="LinkOut" size="XS" />
</a>
</div>
{/if}
</div> </div>
<style> <style>
@ -64,4 +80,21 @@
margin: 0; margin: 0;
border-width: 1px; border-width: 1px;
} }
a {
color: white;
}
#docs-link {
padding-top: 10px;
display: flex;
align-items: center;
gap: 5px;
}
#docs-link > * {
display: flex;
align-items: center;
gap: 5px;
}
</style> </style>

View File

@ -4,7 +4,7 @@
export let title export let title
export let icon = "" export let icon = ""
export let id export let id = undefined
export let href = "#" export let href = "#"
export let link = false export let link = false

View File

@ -100,7 +100,6 @@
"jest": "29.7.0", "jest": "29.7.0",
"jsdom": "^21.1.1", "jsdom": "^21.1.1",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"svelte-check": "^4.1.0",
"svelte-jester": "^1.3.2", "svelte-jester": "^1.3.2",
"vite": "^4.5.0", "vite": "^4.5.0",
"vite-plugin-static-copy": "^0.17.0", "vite-plugin-static-copy": "^0.17.0",

View File

@ -1,132 +0,0 @@
<script>
import { goto } from "@roxi/routify"
import {
keepOpen,
ModalContent,
notifications,
Body,
Layout,
Tabs,
Tab,
Heading,
TextArea,
Dropzone,
} from "@budibase/bbui"
import { datasources, queries } from "@/stores/builder"
import { writable } from "svelte/store"
export let navigateDatasource = false
export let datasourceId
export let createDatasource = false
export let onCancel
const data = writable({
url: "",
raw: "",
file: undefined,
})
let lastTouched = "url"
const getData = async () => {
let dataString
// parse the file into memory and send as string
if (lastTouched === "file") {
dataString = await $data.file.text()
} else if (lastTouched === "url") {
const response = await fetch($data.url)
dataString = await response.text()
} else if (lastTouched === "raw") {
dataString = $data.raw
}
return dataString
}
async function importQueries() {
try {
const dataString = await getData()
if (!datasourceId && !createDatasource) {
throw new Error("No datasource id")
}
const body = {
data: dataString,
datasourceId,
}
const importResult = await queries.import(body)
if (!datasourceId) {
datasourceId = importResult.datasourceId
}
// reload
await datasources.fetch()
await queries.fetch()
if (navigateDatasource) {
$goto(`./datasource/${datasourceId}`)
}
notifications.success(`Imported successfully.`)
} catch (error) {
notifications.error("Error importing queries")
return keepOpen
}
}
</script>
<ModalContent
onConfirm={() => importQueries()}
{onCancel}
confirmText={"Import"}
cancelText="Back"
size="L"
>
<Layout noPadding>
<Heading size="S">Import</Heading>
<Body size="XS"
>Import your rest collection using one of the options below</Body
>
<Tabs selected="File">
<!-- Commenting until nginx csp issue resolved -->
<!-- <Tab title="Link">
<Input
bind:value={$data.url}
on:change={() => (lastTouched = "url")}
label="Enter a URL"
placeholder="e.g. https://petstore.swagger.io/v2/swagger.json"
/>
</Tab> -->
<Tab title="File">
<Dropzone
gallery={false}
value={$data.file ? [$data.file] : []}
on:change={e => {
$data.file = e.detail?.[0]
lastTouched = "file"
}}
fileTags={[
"OpenAPI 3.0",
"OpenAPI 2.0",
"Swagger 2.0",
"cURL",
"YAML",
"JSON",
]}
maximum={1}
/>
</Tab>
<Tab title="Raw Text">
<TextArea
bind:value={$data.raw}
on:change={() => (lastTouched = "raw")}
label={"Paste raw text"}
placeholder={'e.g. curl --location --request GET "https://example.com"'}
/>
</Tab>
</Tabs>
</Layout>
</ModalContent>

View File

@ -280,16 +280,15 @@
EditorView.inputHandler.of((view, from, to, insert) => { EditorView.inputHandler.of((view, from, to, insert) => {
if (jsBindingWrapping && insert === "$") { if (jsBindingWrapping && insert === "$") {
let { text } = view.state.doc.lineAt(from) let { text } = view.state.doc.lineAt(from)
const left = from ? text.substring(0, from) : "" const left = from ? text.substring(0, from) : ""
const right = to ? text.substring(to) : "" const right = to ? text.substring(to) : ""
const wrap = !left.includes('$("') || !right.includes('")') const wrap =
(!left.includes('$("') || !right.includes('")')) &&
!(left.includes("`") && right.includes("`"))
const anchor = from + (wrap ? 3 : 1)
const tr = view.state.update( const tr = view.state.update(
{ {
changes: [{ from, insert: wrap ? '$("")' : "$" }], changes: [{ from, insert: wrap ? '$("")' : "$" }],
selection: {
anchor: from + (wrap ? 3 : 1),
},
}, },
{ {
scrollIntoView: true, scrollIntoView: true,
@ -297,6 +296,19 @@
} }
) )
view.dispatch(tr) view.dispatch(tr)
// the selection needs to fired after the dispatch - this seems
// to fix an issue with the cursor not moving when the editor is
// first loaded, the first usage of the editor is not ready
// for the anchor to move as well as perform a change
setTimeout(() => {
view.dispatch(
view.state.update({
selection: {
anchor,
},
})
)
}, 1)
return true return true
} }
return false return false
@ -421,6 +433,7 @@
.code-editor { .code-editor {
font-size: 12px; font-size: 12px;
height: 100%; height: 100%;
cursor: text;
} }
.code-editor :global(.cm-editor) { .code-editor :global(.cm-editor) {
height: 100%; height: 100%;

View File

@ -2,6 +2,7 @@
import { Label, Select, Body } from "@budibase/bbui" import { Label, Select, Body } from "@budibase/bbui"
import { findAllMatchingComponents } from "@/helpers/components" import { findAllMatchingComponents } from "@/helpers/components"
import { selectedScreen } from "@/stores/builder" import { selectedScreen } from "@/stores/builder"
import { InlineAlert } from "@budibase/bbui"
export let parameters export let parameters
@ -27,6 +28,12 @@
<Label small>Table</Label> <Label small>Table</Label>
<Select bind:value={parameters.componentId} options={componentOptions} /> <Select bind:value={parameters.componentId} options={componentOptions} />
</div> </div>
<InlineAlert
header="Legacy action"
message="This action is only compatible with the (deprecated) Table Block. Please see the documentation for further info."
link="https://docs.budibase.com/docs/data-actions#clear-row-selection"
linkText="Budibase Documentation"
/>
</div> </div>
<style> <style>

View File

@ -1,4 +1,4 @@
<script> <script lang="ts">
import { goto } from "@roxi/routify" import { goto } from "@roxi/routify"
import { import {
keepOpen, keepOpen,
@ -14,13 +14,14 @@
} from "@budibase/bbui" } from "@budibase/bbui"
import { datasources, queries } from "@/stores/builder" import { datasources, queries } from "@/stores/builder"
import { writable } from "svelte/store" import { writable } from "svelte/store"
import type { Datasource } from "@budibase/types"
export let navigateDatasource = false export let navigateDatasource = false
export let datasourceId export let datasourceId: string | undefined = undefined
export let createDatasource = false export let createDatasource = false
export let onCancel export let onCancel: (() => void) | undefined = undefined
const data = writable({ const data = writable<{ url: string; raw: string; file?: any }>({
url: "", url: "",
raw: "", raw: "",
file: undefined, file: undefined,
@ -28,12 +29,14 @@
let lastTouched = "url" let lastTouched = "url"
const getData = async () => { $: datasource = $datasources.selected as Datasource
const getData = async (): Promise<string> => {
let dataString let dataString
// parse the file into memory and send as string // parse the file into memory and send as string
if (lastTouched === "file") { if (lastTouched === "file") {
dataString = await $data.file.text() dataString = await $data.file?.text()
} else if (lastTouched === "url") { } else if (lastTouched === "url") {
const response = await fetch($data.url) const response = await fetch($data.url)
dataString = await response.text() dataString = await response.text()
@ -55,9 +58,9 @@
const body = { const body = {
data: dataString, data: dataString,
datasourceId, datasourceId,
datasource,
} }
const importResult = await queries.importQueries(body)
const importResult = await queries.import(body)
if (!datasourceId) { if (!datasourceId) {
datasourceId = importResult.datasourceId datasourceId = importResult.datasourceId
} }
@ -71,8 +74,8 @@
} }
notifications.success("Imported successfully") notifications.success("Imported successfully")
} catch (error) { } catch (error: any) {
notifications.error("Error importing queries") notifications.error(`Error importing queries - ${error.message}`)
return keepOpen return keepOpen
} }

View File

@ -16,7 +16,8 @@
}, },
"scripts": { "scripts": {
"build": "vite build", "build": "vite build",
"dev": "vite build --watch --mode=dev" "dev": "vite build --watch --mode=dev",
"check:types": "yarn svelte-check"
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "*", "@budibase/bbui": "*",

View File

@ -10,7 +10,9 @@ export const API = createAPIClient({
// Attach client specific headers // Attach client specific headers
attachHeaders: headers => { attachHeaders: headers => {
// Attach app ID header // Attach app ID header
if (window["##BUDIBASE_APP_ID##"]) {
headers["x-budibase-app-id"] = window["##BUDIBASE_APP_ID##"] headers["x-budibase-app-id"] = window["##BUDIBASE_APP_ID##"]
}
// Attach client header if not inside the builder preview // Attach client header if not inside the builder preview
if (!window["##BUDIBASE_IN_BUILDER##"]) { if (!window["##BUDIBASE_IN_BUILDER##"]) {

View File

@ -141,6 +141,7 @@
var(--spectrum-global-dimension-size-300) var(--spectrum-global-dimension-size-300)
); );
display: -webkit-box; display: -webkit-box;
line-clamp: 2;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;

View File

@ -7,6 +7,7 @@
import { isGridEvent } from "@/utils/grid" import { isGridEvent } from "@/utils/grid"
import { DNDPlaceholderID } from "@/constants" import { DNDPlaceholderID } from "@/constants"
import type { Component } from "@budibase/types" import type { Component } from "@budibase/types"
import { DropPosition } from "@budibase/types"
type ChildCoords = { type ChildCoords = {
placeholder: boolean placeholder: boolean
@ -287,7 +288,7 @@
} }
// Convert parent + index into target + mode // Convert parent + index into target + mode
let legacyDropTarget, legacyDropMode let legacyDropTarget, legacyDropMode: DropPosition
const parent: Component | null = findComponentById( const parent: Component | null = findComponentById(
get(screenStore).activeScreen?.props, get(screenStore).activeScreen?.props,
drop.parent drop.parent
@ -309,16 +310,16 @@
// Use inside if no existing children // Use inside if no existing children
if (!children?.length) { if (!children?.length) {
legacyDropTarget = parent._id legacyDropTarget = parent._id
legacyDropMode = "inside" legacyDropMode = DropPosition.INSIDE
} else if (drop.index === 0) { } else if (drop.index === 0) {
legacyDropTarget = children[0]?._id legacyDropTarget = children[0]?._id
legacyDropMode = "above" legacyDropMode = DropPosition.ABOVE
} else { } else {
legacyDropTarget = children[drop.index - 1]?._id legacyDropTarget = children[drop.index - 1]?._id
legacyDropMode = "below" legacyDropMode = DropPosition.BELOW
} }
if (legacyDropTarget && legacyDropMode) { if (legacyDropTarget && legacyDropMode && source.id) {
dropping = true dropping = true
await builderStore.actions.moveComponent( await builderStore.actions.moveComponent(
source.id, source.id,

View File

@ -17,6 +17,7 @@
Devices, Devices,
GridDragMode, GridDragMode,
} from "@/utils/grid" } from "@/utils/grid"
import { DropPosition } from "@budibase/types"
type GridDragSide = type GridDragSide =
| "top" | "top"
@ -222,7 +223,7 @@
// If holding ctrl/cmd then leave behind a duplicate of this component // If holding ctrl/cmd then leave behind a duplicate of this component
if (mode === GridDragMode.Move && (e.ctrlKey || e.metaKey)) { if (mode === GridDragMode.Move && (e.ctrlKey || e.metaKey)) {
builderStore.actions.duplicateComponent(id, "above", false) builderStore.actions.duplicateComponent(id, DropPosition.ABOVE, false)
} }
// Find grid parent and read from DOM // Find grid parent and read from DOM

View File

@ -115,7 +115,7 @@ const createBuilderStore = () => {
component: string, component: string,
parent: string, parent: string,
index: number, index: number,
props: Record<string, any> props?: Record<string, any>
) => { ) => {
eventStore.actions.dispatchEvent("drop-new-component", { eventStore.actions.dispatchEvent("drop-new-component", {
component, component,

View File

@ -7,6 +7,7 @@
"target": "ESNext", "target": "ESNext",
"moduleResolution": "bundler", "moduleResolution": "bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"skipLibCheck": true,
"paths": { "paths": {
"@budibase/*": [ "@budibase/*": [
"../*/src/index.ts", "../*/src/index.ts",

View File

@ -16,8 +16,5 @@
"lodash": "4.17.21", "lodash": "4.17.21",
"shortid": "2.2.15", "shortid": "2.2.15",
"socket.io-client": "^4.7.5" "socket.io-client": "^4.7.5"
},
"devDependencies": {
"svelte-check": "^4.1.0"
} }
} }

View File

@ -8,7 +8,6 @@ import {
Row, Row,
SearchFilters, SearchFilters,
SortOrder, SortOrder,
SortType,
TableSchema, TableSchema,
} from "@budibase/types" } from "@budibase/types"
import { APIClient } from "../api/types" import { APIClient } from "../api/types"
@ -72,8 +71,6 @@ export default abstract class BaseDataFetch<
options: DataFetchOptions<TQuery> & { options: DataFetchOptions<TQuery> & {
datasource: TDatasource datasource: TDatasource
sortType: SortType | null
// Client side feature customisation // Client side feature customisation
clientSideSearching: boolean clientSideSearching: boolean
clientSideSorting: boolean clientSideSorting: boolean
@ -106,7 +103,6 @@ export default abstract class BaseDataFetch<
// Sorting config // Sorting config
sortColumn: null, sortColumn: null,
sortOrder: SortOrder.ASCENDING, sortOrder: SortOrder.ASCENDING,
sortType: null,
// Pagination config // Pagination config
paginate: true, paginate: true,
@ -227,31 +223,12 @@ export default abstract class BaseDataFetch<
this.options.sortColumn = this.getDefaultSortColumn(definition, schema) this.options.sortColumn = this.getDefaultSortColumn(definition, schema)
} }
// If we don't have a sort column specified then just ensure we don't set
// any sorting params
if (!this.options.sortColumn) {
this.options.sortOrder = SortOrder.ASCENDING
this.options.sortType = null
} else {
// Otherwise determine what sort type to use base on sort column
this.options.sortType = SortType.STRING
const fieldSchema = schema?.[this.options.sortColumn]
if (
fieldSchema?.type === FieldType.NUMBER ||
fieldSchema?.type === FieldType.BIGINT ||
("calculationType" in fieldSchema && fieldSchema?.calculationType)
) {
this.options.sortType = SortType.NUMBER
}
// If no sort order, default to ascending // If no sort order, default to ascending
if (!this.options.sortOrder) { if (!this.options.sortOrder) {
this.options.sortOrder = SortOrder.ASCENDING this.options.sortOrder = SortOrder.ASCENDING
} else { } else {
// Ensure sortOrder matches the enum // Ensure sortOrder matches the enum
this.options.sortOrder = this.options.sortOrder = this.options.sortOrder.toLowerCase() as SortOrder
this.options.sortOrder.toLowerCase() as SortOrder
}
} }
// Build the query // Build the query
@ -294,7 +271,6 @@ export default abstract class BaseDataFetch<
const { const {
sortColumn, sortColumn,
sortOrder, sortOrder,
sortType,
limit, limit,
clientSideSearching, clientSideSearching,
clientSideSorting, clientSideSorting,
@ -311,8 +287,8 @@ export default abstract class BaseDataFetch<
} }
// If we don't support sorting, do a client-side sort // If we don't support sorting, do a client-side sort
if (!this.features.supportsSort && clientSideSorting && sortType) { if (!this.features.supportsSort && clientSideSorting && sortColumn) {
rows = sort(rows, sortColumn as any, sortOrder, sortType) rows = sort(rows, sortColumn, sortOrder)
} }
// If we don't support pagination, do a client-side limit // If we don't support pagination, do a client-side limit

View File

@ -29,8 +29,7 @@ export default class TableFetch extends BaseDataFetch<TableDatasource, Table> {
} }
async getData() { async getData() {
const { datasource, limit, sortColumn, sortOrder, sortType, paginate } = const { datasource, limit, sortColumn, sortOrder, paginate } = this.options
this.options
const { tableId } = datasource const { tableId } = datasource
const { cursor, query } = get(this.store) const { cursor, query } = get(this.store)
@ -41,7 +40,6 @@ export default class TableFetch extends BaseDataFetch<TableDatasource, Table> {
limit, limit,
sort: sortColumn, sort: sortColumn,
sortOrder: sortOrder ?? SortOrder.ASCENDING, sortOrder: sortOrder ?? SortOrder.ASCENDING,
sortType,
paginate, paginate,
bookmark: cursor, bookmark: cursor,
}) })

View File

@ -1,4 +1,5 @@
import { import {
SearchViewRowRequest,
SortOrder, SortOrder,
ViewDatasource, ViewDatasource,
ViewV2Enriched, ViewV2Enriched,
@ -40,8 +41,7 @@ export default class ViewV2Fetch extends BaseDataFetch<
} }
async getData() { async getData() {
const { datasource, limit, sortColumn, sortOrder, sortType, paginate } = const { datasource, limit, sortColumn, sortOrder, paginate } = this.options
this.options
const { cursor, query, definition } = get(this.store) const { cursor, query, definition } = get(this.store)
// If this is a calculation view and we have no calculations, return nothing // If this is a calculation view and we have no calculations, return nothing
@ -68,14 +68,13 @@ export default class ViewV2Fetch extends BaseDataFetch<
} }
try { try {
const request = { const request: SearchViewRowRequest = {
query, query,
paginate, paginate,
limit, limit,
bookmark: cursor, bookmark: cursor,
sort: sortColumn, sort: sortColumn,
sortOrder: sortOrder, sortOrder: sortOrder,
sortType,
} }
if (paginate) { if (paginate) {
const res = await this.API.viewV2.fetch(datasource.id, { const res = await this.API.viewV2.fetch(datasource.id, {

View File

@ -1,6 +1,6 @@
export { createAPIClient } from "./api" export { createAPIClient } from "./api"
export type { APIClient } from "./api" export type { APIClient } from "./api"
export { fetchData, DataFetchMap } from "./fetch" export { fetchData, DataFetchMap, type DataFetchType } from "./fetch"
export * as Constants from "./constants" export * as Constants from "./constants"
export * from "./stores" export * from "./stores"
export * from "./utils" export * from "./utils"

View File

@ -0,0 +1,7 @@
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"
const config = {
preprocess: vitePreprocess(),
}
export default config

View File

@ -263,7 +263,6 @@ export async function search(ctx: Ctx<SearchRowRequest, SearchRowResponse>) {
limit: searchRequest.limit, limit: searchRequest.limit,
sort: searchRequest.sort ?? undefined, sort: searchRequest.sort ?? undefined,
sortOrder: searchRequest.sortOrder, sortOrder: searchRequest.sortOrder,
sortType: searchRequest.sortType ?? undefined,
countRows: searchRequest.countRows, countRows: searchRequest.countRows,
version: searchRequest.version, version: searchRequest.version,
disableEscaping: searchRequest.disableEscaping, disableEscaping: searchRequest.disableEscaping,

View File

@ -63,14 +63,12 @@ function getSortOptions(request: SearchViewRowRequest, view: ViewV2) {
return { return {
sort: request.sort, sort: request.sort,
sortOrder: request.sortOrder, sortOrder: request.sortOrder,
sortType: request.sortType ?? undefined,
} }
} }
if (view.sort) { if (view.sort) {
return { return {
sort: view.sort.field, sort: view.sort.field,
sortOrder: view.sort.order, sortOrder: view.sort.order,
sortType: view.sort.type,
} }
} }

View File

@ -38,7 +38,7 @@ import {
import _ from "lodash" import _ from "lodash"
import tk from "timekeeper" import tk from "timekeeper"
import { encodeJSBinding } from "@budibase/string-templates" import { encodeJSBinding } from "@budibase/string-templates"
import { dataFilters } from "@budibase/shared-core" import { dataFilters, InMemorySearchQuery } from "@budibase/shared-core"
import { Knex } from "knex" import { Knex } from "knex"
import { generator, structures, mocks } from "@budibase/backend-core/tests" import { generator, structures, mocks } from "@budibase/backend-core/tests"
import { DEFAULT_EMPLOYEE_TABLE_SCHEMA } from "../../../db/defaultData/datasource_bb_default" import { DEFAULT_EMPLOYEE_TABLE_SCHEMA } from "../../../db/defaultData/datasource_bb_default"
@ -200,31 +200,26 @@ if (descriptions.length) {
const isView = sourceType === "view" const isView = sourceType === "view"
class SearchAssertion { class SearchAssertion {
constructor(private readonly query: SearchRowRequest) {} constructor(
private readonly query: SearchRowRequest & {
sortType?: SortType
}
) {}
private async performSearch(): Promise<SearchResponse<Row>> { private async performSearch(): Promise<SearchResponse<Row>> {
if (isInMemory) { if (isInMemory) {
const inMemoryQuery: RequiredKeys< const inMemoryQuery: RequiredKeys<InMemorySearchQuery> = {
Omit<RowSearchParams, "tableId">
> = {
sort: this.query.sort ?? undefined, sort: this.query.sort ?? undefined,
query: { ...this.query.query }, query: { ...this.query.query },
paginate: this.query.paginate,
bookmark: this.query.bookmark ?? undefined,
limit: this.query.limit, limit: this.query.limit,
sortOrder: this.query.sortOrder, sortOrder: this.query.sortOrder,
sortType: this.query.sortType ?? undefined, sortType: this.query.sortType ?? undefined,
version: this.query.version,
disableEscaping: this.query.disableEscaping,
countRows: this.query.countRows, countRows: this.query.countRows,
viewId: undefined,
fields: undefined,
indexer: undefined,
rows: undefined,
} }
return dataFilters.search(_.cloneDeep(rows), inMemoryQuery) return dataFilters.search(_.cloneDeep(rows), inMemoryQuery)
} else { } else {
return config.api.row.search(tableOrViewId, this.query) const { sortType, ...query } = this.query
return config.api.row.search(tableOrViewId, query)
} }
} }
@ -400,7 +395,9 @@ if (descriptions.length) {
} }
} }
function expectSearch(query: SearchRowRequest) { function expectSearch(
query: SearchRowRequest & { sortType?: SortType }
) {
return new SearchAssertion(query) return new SearchAssertion(query)
} }
@ -1119,6 +1116,7 @@ if (descriptions.length) {
}).toMatchExactly([{ name: "foo" }, { name: "bar" }]) }).toMatchExactly([{ name: "foo" }, { name: "bar" }])
}) })
isInMemory &&
describe("sortType STRING", () => { describe("sortType STRING", () => {
it("sorts ascending", async () => { it("sorts ascending", async () => {
await expectSearch({ await expectSearch({
@ -1319,6 +1317,7 @@ if (descriptions.length) {
}) })
}) })
isInMemory &&
describe("sortType NUMBER", () => { describe("sortType NUMBER", () => {
it("sorts ascending", async () => { it("sorts ascending", async () => {
await expectSearch({ await expectSearch({
@ -1473,6 +1472,7 @@ if (descriptions.length) {
}).toMatchExactly([{ dob: JAN_10TH }, { dob: JAN_1ST }]) }).toMatchExactly([{ dob: JAN_10TH }, { dob: JAN_1ST }])
}) })
isInMemory &&
describe("sortType STRING", () => { describe("sortType STRING", () => {
it("sorts ascending", async () => { it("sorts ascending", async () => {
await expectSearch({ await expectSearch({
@ -1639,6 +1639,7 @@ if (descriptions.length) {
]) ])
}) })
isInMemory &&
describe("sortType STRING", () => { describe("sortType STRING", () => {
it("sorts ascending", async () => { it("sorts ascending", async () => {
await expectSearch({ await expectSearch({
@ -1675,6 +1676,7 @@ if (descriptions.length) {
}) })
}) })
!isInMemory &&
describe("datetime - date only", () => { describe("datetime - date only", () => {
describe.each([true, false])( describe.each([true, false])(
"saved with timestamp: %s", "saved with timestamp: %s",
@ -1847,6 +1849,7 @@ if (descriptions.length) {
]) ])
}) })
isInMemory &&
describe("sortType STRING", () => { describe("sortType STRING", () => {
it("sorts ascending", async () => { it("sorts ascending", async () => {
await expectSearch({ await expectSearch({

View File

@ -24,7 +24,6 @@ import {
SearchResponse, SearchResponse,
SearchViewRowRequest, SearchViewRowRequest,
SortOrder, SortOrder,
SortType,
StaticQuotaName, StaticQuotaName,
Table, Table,
TableSchema, TableSchema,
@ -154,7 +153,6 @@ if (descriptions.length) {
sort: { sort: {
field: "fieldToSort", field: "fieldToSort",
order: SortOrder.DESCENDING, order: SortOrder.DESCENDING,
type: SortType.STRING,
}, },
schema: { schema: {
id: { visible: true }, id: { visible: true },
@ -217,7 +215,6 @@ if (descriptions.length) {
sort: { sort: {
field: "fieldToSort", field: "fieldToSort",
order: SortOrder.DESCENDING, order: SortOrder.DESCENDING,
type: SortType.STRING,
}, },
schema: { schema: {
id: { visible: true }, id: { visible: true },
@ -1147,7 +1144,6 @@ if (descriptions.length) {
sort: { sort: {
field: generator.word(), field: generator.word(),
order: SortOrder.DESCENDING, order: SortOrder.DESCENDING,
type: SortType.STRING,
}, },
schema: { schema: {
id: { visible: true }, id: { visible: true },
@ -3153,7 +3149,6 @@ if (descriptions.length) {
{ {
field: string field: string
order?: SortOrder order?: SortOrder
type?: SortType
}, },
string[] string[]
][] = [ ][] = [
@ -3161,7 +3156,6 @@ if (descriptions.length) {
{ {
field: "name", field: "name",
order: SortOrder.ASCENDING, order: SortOrder.ASCENDING,
type: SortType.STRING,
}, },
["Alice", "Bob", "Charly", "Danny"], ["Alice", "Bob", "Charly", "Danny"],
], ],
@ -3178,22 +3172,6 @@ if (descriptions.length) {
}, },
["Danny", "Charly", "Bob", "Alice"], ["Danny", "Charly", "Bob", "Alice"],
], ],
[
{
field: "name",
order: SortOrder.DESCENDING,
type: SortType.STRING,
},
["Danny", "Charly", "Bob", "Alice"],
],
[
{
field: "age",
order: SortOrder.ASCENDING,
type: SortType.NUMBER,
},
["Danny", "Alice", "Charly", "Bob"],
],
[ [
{ {
field: "age", field: "age",
@ -3204,15 +3182,13 @@ if (descriptions.length) {
[ [
{ {
field: "age", field: "age",
order: SortOrder.DESCENDING,
}, },
["Bob", "Charly", "Alice", "Danny"], ["Danny", "Alice", "Charly", "Bob"],
], ],
[ [
{ {
field: "age", field: "age",
order: SortOrder.DESCENDING, order: SortOrder.DESCENDING,
type: SortType.NUMBER,
}, },
["Bob", "Charly", "Alice", "Danny"], ["Bob", "Charly", "Alice", "Danny"],
], ],
@ -3299,7 +3275,6 @@ if (descriptions.length) {
sort: { sort: {
field: "name", field: "name",
order: SortOrder.ASCENDING, order: SortOrder.ASCENDING,
type: SortType.STRING,
}, },
schema: viewSchema, schema: viewSchema,
}) })
@ -3307,7 +3282,6 @@ if (descriptions.length) {
const response = await config.api.viewV2.search(view.id, { const response = await config.api.viewV2.search(view.id, {
sort: sortParams.field, sort: sortParams.field,
sortOrder: sortParams.order, sortOrder: sortParams.order,
sortType: sortParams.type,
query: {}, query: {},
}) })

View File

@ -168,6 +168,7 @@ class S3Integration implements IntegrationBase {
secretAccessKey: config.secretAccessKey, secretAccessKey: config.secretAccessKey,
}, },
region: config.region, region: config.region,
endpoint: config.endpoint,
} }
if (config.endpoint) { if (config.endpoint) {
this.config.forcePathStyle = true this.config.forcePathStyle = true

View File

@ -46,7 +46,6 @@ export async function search(
query: options.query, query: options.query,
sort: options.sort, sort: options.sort,
sortOrder: options.sortOrder, sortOrder: options.sortOrder,
sortType: options.sortType,
limit: options.limit, limit: options.limit,
bookmark: options.bookmark, bookmark: options.bookmark,
paginate: options.paginate, paginate: options.paginate,

View File

@ -1,5 +1,6 @@
import { import {
Aggregation, Aggregation,
AutoFieldSubType,
CalculationType, CalculationType,
DocumentType, DocumentType,
EnrichedQueryJson, EnrichedQueryJson,
@ -420,7 +421,11 @@ export async function search(
} }
} else if (sortField) { } else if (sortField) {
const sortType = const sortType =
sortField.type === FieldType.NUMBER ? SortType.NUMBER : SortType.STRING sortField.type === FieldType.NUMBER ||
(sortField.type === FieldType.AUTO &&
sortField.subtype === AutoFieldSubType.AUTO_ID)
? SortType.NUMBER
: SortType.STRING
request.sort = { request.sort = {
[mapToUserColumn(sortField.name)]: { [mapToUserColumn(sortField.name)]: {
direction: params.sortOrder || SortOrder.ASCENDING, direction: params.sortOrder || SortOrder.ASCENDING,

View File

@ -11,7 +11,6 @@ import {
SortType, SortType,
FieldConstraints, FieldConstraints,
SortOrder, SortOrder,
RowSearchParams,
EmptyFilterOption, EmptyFilterOption,
SearchResponse, SearchResponse,
Table, Table,
@ -25,6 +24,8 @@ import {
isArraySearchOperator, isArraySearchOperator,
isRangeSearchOperator, isRangeSearchOperator,
SearchFilter, SearchFilter,
WithRequired,
SearchParams,
} from "@budibase/types" } from "@budibase/types"
import dayjs from "dayjs" import dayjs from "dayjs"
import { OperatorOptions, SqlNumberTypeRangeMap } from "./constants" import { OperatorOptions, SqlNumberTypeRangeMap } from "./constants"
@ -521,9 +522,19 @@ export function fixupFilterArrays(filters: SearchFilters) {
return filters return filters
} }
type SearchQuery = WithRequired<
Pick<
SearchParams,
"query" | "sort" | "sortOrder" | "sortType" | "limit" | "countRows"
>,
"query"
>
export type InMemorySearchQuery = SearchQuery
export function search<T extends Record<string, any>>( export function search<T extends Record<string, any>>(
docs: T[], docs: T[],
query: Omit<RowSearchParams, "tableId"> query: SearchQuery
): SearchResponse<T> { ): SearchResponse<T> {
let result = runQuery(docs, query.query) let result = runQuery(docs, query.query)
if (query.sort) { if (query.sort) {

View File

@ -1,5 +1,6 @@
export * from "./constants" export * from "./constants"
export * as dataFilters from "./filters" export * as dataFilters from "./filters"
export type * from "./filters"
export * as helpers from "./helpers" export * as helpers from "./helpers"
export * as utils from "./utils" export * as utils from "./utils"
export * as sdk from "./sdk" export * as sdk from "./sdk"

View File

@ -11,7 +11,7 @@ export interface SaveQueryRequest extends Query {}
export interface SaveQueryResponse extends Query {} export interface SaveQueryResponse extends Query {}
export interface ImportRestQueryRequest { export interface ImportRestQueryRequest {
datasourceId: string datasourceId?: string
data: string data: string
datasource: Datasource datasource: Datasource
} }

View File

@ -8,11 +8,7 @@ import {
SearchFilterKey, SearchFilterKey,
} from "../../../../sdk" } from "../../../../sdk"
import { Row } from "../../../../documents" import { Row } from "../../../../documents"
import { import { PaginationResponse, SortOrder } from "../../../../api/web/pagination"
PaginationResponse,
SortOrder,
SortType,
} from "../../../../api/web/pagination"
import { z } from "zod" import { z } from "zod"
const fieldKey = z const fieldKey = z
@ -70,7 +66,6 @@ const searchRowRequest = z.object({
limit: z.number().optional(), limit: z.number().optional(),
sort: z.string().nullish(), sort: z.string().nullish(),
sortOrder: z.nativeEnum(SortOrder).optional(), sortOrder: z.nativeEnum(SortOrder).optional(),
sortType: z.nativeEnum(SortType).nullish(),
version: z.string().optional(), version: z.string().optional(),
disableEscaping: z.boolean().optional(), disableEscaping: z.boolean().optional(),
countRows: z.boolean().optional(), countRows: z.boolean().optional(),
@ -83,7 +78,6 @@ export type SearchViewRowRequest = Pick<
SearchRowRequest, SearchRowRequest,
| "sort" | "sort"
| "sortOrder" | "sortOrder"
| "sortType"
| "limit" | "limit"
| "bookmark" | "bookmark"
| "paginate" | "paginate"

View File

@ -50,7 +50,7 @@ export interface SearchParams {
// when searching for rows we want a more extensive search type that requires certain properties // when searching for rows we want a more extensive search type that requires certain properties
export interface RowSearchParams export interface RowSearchParams
extends WithRequired<SearchParams, "tableId" | "query"> {} extends WithRequired<Omit<SearchParams, "sortType">, "tableId" | "query"> {}
export interface SearchResponse<T> { export interface SearchResponse<T> {
rows: T[] rows: T[]

View File

@ -20499,10 +20499,10 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
svelte-check@^4.1.0: svelte-check@^4.1.5:
version "4.1.0" version "4.1.5"
resolved "https://registry.yarnpkg.com/svelte-check/-/svelte-check-4.1.0.tgz#4389c1c88aa24f3d06fe0df94f9075a55017256d" resolved "https://registry.yarnpkg.com/svelte-check/-/svelte-check-4.1.5.tgz#afdb3f8050c123064124d5aa7821365c7befa7a4"
integrity sha512-AflEZYqI578KuDZcpcorPSf597LStxlkN7XqXi38u09zlHODVKd7c+7OuubGzbhgGRUqNTdQCZ+Ga96iRXEf2g== integrity sha512-Gb0T2IqBNe1tLB9EB1Qh+LOe+JB8wt2/rNBDGvkxQVvk8vNeAoG+vZgFB/3P5+zC7RWlyBlzm9dVjZFph/maIg==
dependencies: dependencies:
"@jridgewell/trace-mapping" "^0.3.25" "@jridgewell/trace-mapping" "^0.3.25"
chokidar "^4.0.1" chokidar "^4.0.1"