diff --git a/packages/builder/src/components/common/NavItem.svelte b/packages/builder/src/components/common/NavItem.svelte index 2d7dc745d1..d2074b69b1 100644 --- a/packages/builder/src/components/common/NavItem.svelte +++ b/packages/builder/src/components/common/NavItem.svelte @@ -133,5 +133,6 @@ .iconText { margin-top: 1px; font-size: var(--spectrum-global-dimension-font-size-50); + flex: 0 0 34px; } diff --git a/packages/builder/src/components/integration/KeyValueBuilder.svelte b/packages/builder/src/components/integration/KeyValueBuilder.svelte index 94f3cb7f1f..39eca0955b 100644 --- a/packages/builder/src/components/integration/KeyValueBuilder.svelte +++ b/packages/builder/src/components/integration/KeyValueBuilder.svelte @@ -31,7 +31,10 @@ export let menuItems export let showMenu = false - let fields = Object.entries(object).map(([name, value]) => ({ name, value })) + let fields = Object.entries(object || {}).map(([name, value]) => ({ + name, + value, + })) let fieldActivity = buildFieldActivity(activity) $: object = fields.reduce( diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.js index a61f894300..c5fb294f80 100644 --- a/packages/builder/src/constants/backend/index.js +++ b/packages/builder/src/constants/backend/index.js @@ -219,3 +219,13 @@ export const RestBodyTypes = [ { name: "raw (XML)", value: "xml" }, { name: "raw (Text)", value: "text" }, ] + +export const PaginationTypes = [ + { label: "Page number based", value: "page" }, + { label: "Cursor based", value: "cursor" }, +] + +export const PaginationLocations = [ + { label: "Query parameters", value: "query" }, + { label: "Request body", value: "body" }, +] diff --git a/packages/builder/src/helpers/data/utils.js b/packages/builder/src/helpers/data/utils.js index b6478680ec..0e99109189 100644 --- a/packages/builder/src/helpers/data/utils.js +++ b/packages/builder/src/helpers/data/utils.js @@ -84,7 +84,7 @@ export function customQueryIconText(datasource, query) { case "read": return "GET" case "delete": - return "DELETE" + return "DEL" case "patch": return "PATCH" } diff --git a/packages/builder/src/helpers/fetchTableData.js b/packages/builder/src/helpers/fetchTableData.js index 8b413f3611..6d61ec813e 100644 --- a/packages/builder/src/helpers/fetchTableData.js +++ b/packages/builder/src/helpers/fetchTableData.js @@ -1,5 +1,7 @@ // Do not use any aliased imports in common files, as these will be bundled -// by multiple bundlers which may not be able to resolve them +// by multiple bundlers which may not be able to resolve them. +// This will eventually be replaced by the new client implementation when we +// add a core package. import { writable, derived, get } from "svelte/store" import * as API from "../builderStore/api" import { buildLuceneQuery } from "./lucene" diff --git a/packages/builder/src/helpers/lucene.js b/packages/builder/src/helpers/lucene.js index 2348bb1114..63fe542da2 100644 --- a/packages/builder/src/helpers/lucene.js +++ b/packages/builder/src/helpers/lucene.js @@ -122,12 +122,16 @@ export const luceneQuery = (docs, query) => { // Process a string match (fails if the value does not start with the string) const stringMatch = match("string", (docValue, testValue) => { - return !docValue || !docValue.startsWith(testValue) + return ( + !docValue || !docValue?.toLowerCase().startsWith(testValue?.toLowerCase()) + ) }) // Process a fuzzy match (treat the same as starts with when running locally) const fuzzyMatch = match("fuzzy", (docValue, testValue) => { - return !docValue || !docValue.startsWith(testValue) + return ( + !docValue || !docValue?.toLowerCase().startsWith(testValue?.toLowerCase()) + ) }) // Process a range match diff --git a/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte index c0e3bcea5e..808c3a49ec 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte @@ -30,6 +30,8 @@ import { RestBodyTypes as bodyTypes, SchemaTypeOptions, + PaginationLocations, + PaginationTypes, } from "constants/backend" import JSONPreview from "components/integration/JSONPreview.svelte" import AccessLevelSelect from "components/integration/AccessLevelSelect.svelte" @@ -269,6 +271,9 @@ query.fields.bodyType = RawRestBodyTypes.NONE } } + if (query && !query.fields.pagination) { + query.fields.pagination = {} + } dynamicVariables = getDynamicVariables(datasource, query._id) }) @@ -343,6 +348,42 @@ /> + + + {#if !$flags.queryTransformerBanner} @@ -564,4 +605,9 @@ .auth-select { width: 200px; } + .pagination { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--spacing-m); + } diff --git a/packages/client/src/api/datasources.js b/packages/client/src/api/datasources.js deleted file mode 100644 index 8622540149..0000000000 --- a/packages/client/src/api/datasources.js +++ /dev/null @@ -1,133 +0,0 @@ -import { cloneDeep } from "lodash/fp" -import { fetchTableData, fetchTableDefinition } from "./tables" -import { fetchViewData } from "./views" -import { fetchRelationshipData } from "./relationships" -import { FieldTypes } from "../constants" -import { executeQuery, fetchQueryDefinition } from "./queries" -import { - convertJSONSchemaToTableSchema, - getJSONArrayDatasourceSchema, -} from "builder/src/builderStore/jsonUtils" - -/** - * Fetches all rows for a particular Budibase data source. - */ -export const fetchDatasource = async dataSource => { - if (!dataSource || !dataSource.type) { - return [] - } - - // Fetch all rows in data source - const { type, tableId, fieldName } = dataSource - let rows = [], - info = {} - if (type === "table") { - rows = await fetchTableData(tableId) - } else if (type === "view") { - rows = await fetchViewData(dataSource) - } else if (type === "query") { - // Set the default query params - let parameters = cloneDeep(dataSource.queryParams || {}) - for (let param of dataSource.parameters) { - if (!parameters[param.name]) { - parameters[param.name] = param.default - } - } - const { data, ...rest } = await executeQuery({ - queryId: dataSource._id, - parameters, - }) - info = rest - rows = data - } else if (type === FieldTypes.LINK) { - rows = await fetchRelationshipData({ - rowId: dataSource.rowId, - tableId: dataSource.rowTableId, - fieldName, - }) - } - - // Enrich the result is always an array - return { rows: Array.isArray(rows) ? rows : [], info } -} - -/** - * Fetches the schema of any kind of datasource. - */ -export const fetchDatasourceSchema = async dataSource => { - if (!dataSource) { - return null - } - const { type } = dataSource - let schema - - // Nested providers should already have exposed their own schema - if (type === "provider") { - schema = dataSource.value?.schema - } - - // Field sources have their schema statically defined - if (type === "field") { - if (dataSource.fieldType === "attachment") { - schema = { - url: { - type: "string", - }, - name: { - type: "string", - }, - } - } else if (dataSource.fieldType === "array") { - schema = { - value: { - type: "string", - }, - } - } - } - - // JSON arrays need their table definitions fetched. - // We can then extract their schema as a subset of the table schema. - if (type === "jsonarray") { - const table = await fetchTableDefinition(dataSource.tableId) - schema = getJSONArrayDatasourceSchema(table?.schema, dataSource) - } - - // Tables, views and links can be fetched by table ID - if ( - (type === "table" || type === "view" || type === "link") && - dataSource.tableId - ) { - const table = await fetchTableDefinition(dataSource.tableId) - schema = table?.schema - } - - // Queries can be fetched by query ID - if (type === "query" && dataSource._id) { - const definition = await fetchQueryDefinition(dataSource._id) - schema = definition?.schema - } - - // Sanity check - if (!schema) { - return null - } - - // Check for any JSON fields so we can add any top level properties - let jsonAdditions = {} - Object.keys(schema).forEach(fieldKey => { - const fieldSchema = schema[fieldKey] - if (fieldSchema?.type === "json") { - const jsonSchema = convertJSONSchemaToTableSchema(fieldSchema, { - squashObjects: true, - }) - Object.keys(jsonSchema).forEach(jsonKey => { - jsonAdditions[`${fieldKey}.${jsonKey}`] = { - type: jsonSchema[jsonKey].type, - nestedJSON: true, - } - }) - } - }) - return { ...schema, ...jsonAdditions } -} diff --git a/packages/client/src/api/index.js b/packages/client/src/api/index.js index 1fd5b18139..d429eb437c 100644 --- a/packages/client/src/api/index.js +++ b/packages/client/src/api/index.js @@ -1,6 +1,5 @@ export * from "./rows" export * from "./auth" -export * from "./datasources" export * from "./tables" export * from "./attachments" export * from "./views" diff --git a/packages/client/src/api/queries.js b/packages/client/src/api/queries.js index 834282fe73..e8972f657e 100644 --- a/packages/client/src/api/queries.js +++ b/packages/client/src/api/queries.js @@ -4,7 +4,7 @@ import API from "./api" /** * Executes a query against an external data connector. */ -export const executeQuery = async ({ queryId, parameters }) => { +export const executeQuery = async ({ queryId, pagination, parameters }) => { const query = await fetchQueryDefinition(queryId) if (query?.datasourceId == null) { notificationStore.actions.error("That query couldn't be found") @@ -14,6 +14,7 @@ export const executeQuery = async ({ queryId, parameters }) => { url: `/api/v2/queries/${queryId}`, body: { parameters, + pagination, }, }) if (res.error) { diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index d8cdbd3f53..d05f01f58a 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -19,6 +19,16 @@ export let isScreen = false export let isBlock = false + // Ref to the svelte component + let ref + + // Initial settings are passed in on first render of the component. + // When the first instance of cachedSettings are set, this object is set to + // reference cachedSettings, so that mutations to cachedSettings also affect + // initialSettings, but it does not get caught by svelte invalidation - which + // would happen if we spread cachedSettings directly to the component. + let initialSettings + // Component settings are the un-enriched settings for this component that // need to be enriched at this level. // Nested settings are the un-enriched block settings that are to be passed on @@ -267,16 +277,26 @@ const cacheSettings = (enriched, nested, conditional) => { const allSettings = { ...enriched, ...nested, ...conditional } if (!cachedSettings) { - cachedSettings = allSettings + cachedSettings = { ...allSettings } + initialSettings = cachedSettings } else { Object.keys(allSettings).forEach(key => { - if (!propsAreSame(allSettings[key], cachedSettings[key])) { + const same = propsAreSame(allSettings[key], cachedSettings[key]) + if (!same) { cachedSettings[key] = allSettings[key] + assignSetting(key, allSettings[key]) } }) } } + // Assigns a certain setting to this component. + // We manually use the svelte $set function to avoid triggering additional + // reactive statements. + const assignSetting = (key, value) => { + ref?.$$set?.({ [key]: value }) + } + // Generates a key used to determine when components need to fully remount. // Currently only toggling editing requires remounting. const getRenderKey = (id, editing) => { @@ -299,7 +319,7 @@ data-id={id} data-name={name} > - + {#if children.length} {#each children as child (child._id)} diff --git a/packages/client/src/components/app/DataProvider.svelte b/packages/client/src/components/app/DataProvider.svelte index ec328c60c6..71c54db4da 100644 --- a/packages/client/src/components/app/DataProvider.svelte +++ b/packages/client/src/components/app/DataProvider.svelte @@ -1,13 +1,9 @@
- {#if !loaded} + {#if !$fetch.loaded}
@@ -328,14 +134,14 @@ {:else} {/if} - {#if paginate && internalTable} + {#if paginate && $fetch.supportsPagination} {/if} diff --git a/packages/client/src/components/app/blocks/CardsBlock.svelte b/packages/client/src/components/app/blocks/CardsBlock.svelte index 3a136b5b20..301b440ab3 100644 --- a/packages/client/src/components/app/blocks/CardsBlock.svelte +++ b/packages/client/src/components/app/blocks/CardsBlock.svelte @@ -30,7 +30,7 @@ export let cardButtonOnClick export let linkColumn - const { API, styleable } = getContext("sdk") + const { fetchDatasourceSchema, styleable } = getContext("sdk") const context = getContext("context") const component = getContext("component") const schemaComponentMap = { @@ -45,6 +45,7 @@ let dataProviderId let repeaterId let schema + let schemaLoaded = false $: fetchSchema(dataSource) $: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema) @@ -111,103 +112,106 @@ // Load the datasource schema so we can determine column types const fetchSchema = async dataSource => { if (dataSource) { - schema = await API.fetchDatasourceSchema(dataSource) + schema = await fetchDatasourceSchema(dataSource) } + schemaLoaded = true } - -
- - {#if title || enrichedSearchColumns?.length || showTitleButton} -
-
- {title || ""} +{#if schemaLoaded} + +
+ + {#if title || enrichedSearchColumns?.length || showTitleButton} +
+
+ {title || ""} +
+
+ {#if enrichedSearchColumns?.length} + + {/if} + {#if showTitleButton} + + {/if} +
-
- {#if enrichedSearchColumns?.length} - - {/if} - {#if showTitleButton} - - {/if} -
-
- {/if} - + {/if} + > + + - -
- +
+ +{/if}