diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 4e56ca758d..b1391e36fd 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -5545,12 +5545,11 @@ "width": 600, "height": 400 }, - "info": "Grid Blocks are only compatible with internal or SQL tables", "settings": [ { - "type": "table", + "type": "dataSource", "label": "Data", - "key": "table", + "key": "datasource", "required": true }, { diff --git a/packages/client/src/components/app/GridBlock.svelte b/packages/client/src/components/app/GridBlock.svelte index 9bdea52124..1cdbcf00ff 100644 --- a/packages/client/src/components/app/GridBlock.svelte +++ b/packages/client/src/components/app/GridBlock.svelte @@ -4,7 +4,7 @@ import { getContext } from "svelte" import { Grid } from "@budibase/frontend-core" - export let table + export let datasource export let allowAddRows = true export let allowEditRows = true export let allowDeleteRows = true @@ -15,6 +15,9 @@ export let fixedRowHeight = null export let columns = null + // Legacy settings + export let table + const component = getContext("component") const { styleable, API, builderStore, notificationStore } = getContext("sdk") @@ -38,7 +41,7 @@ class:in-builder={$builderStore.inBuilder} > { [props, hasNonAutoColumn], ([$props, $hasNonAutoColumn]) => { let config = { ...$props } + const type = $props.datasource?.type // Disable some features if we're editing a view - if ($props.datasource?.type === "viewV2") { + if (type === "viewV2") { config.canEditColumns = false } @@ -48,6 +49,14 @@ export const deriveStores = context => { config.canAddRows = false } + // Disable features for non DS+ + if (!["table", "viewV2"].includes(type)) { + config.canAddRows = false + config.canEditRows = false + config.canDeleteRows = false + config.canExpandRows = false + } + return config } ) diff --git a/packages/frontend-core/src/components/grid/stores/datasource.js b/packages/frontend-core/src/components/grid/stores/datasource.js index 3f4347953e..e56b37a5f3 100644 --- a/packages/frontend-core/src/components/grid/stores/datasource.js +++ b/packages/frontend-core/src/components/grid/stores/datasource.js @@ -1,4 +1,5 @@ import { derived, get, writable } from "svelte/store" +import { getDatasourceDefinition } from "../../../fetch" export const createStores = () => { const definition = writable(null) @@ -19,6 +20,14 @@ export const deriveStores = context => { } let newSchema = { ...$definition?.schema } + // Ensure schema is configured as objects. + // Certain datasources like queries use primitives. + Object.keys(newSchema).forEach(key => { + if (typeof newSchema[key] !== "object") { + newSchema[key] = { type: newSchema[key] } + } + }) + // Apply schema overrides Object.keys($schemaOverrides || {}).forEach(field => { if (newSchema[field]) { @@ -48,7 +57,16 @@ export const deriveStores = context => { } export const createActions = context => { - const { datasource, definition, config, dispatch, table, viewV2 } = context + const { + API, + datasource, + definition, + config, + dispatch, + table, + viewV2, + query, + } = context // Gets the appropriate API for the configured datasource type const getAPI = () => { @@ -58,6 +76,8 @@ export const createActions = context => { return table case "viewV2": return viewV2 + case "query": + return query default: return null } @@ -65,7 +85,8 @@ export const createActions = context => { // Refreshes the datasource definition const refreshDefinition = async () => { - return await getAPI()?.actions.refreshDefinition() + const def = await getDatasourceDefinition({ API, datasource }) + definition.set(def) } // Saves the datasource definition diff --git a/packages/frontend-core/src/components/grid/stores/index.js b/packages/frontend-core/src/components/grid/stores/index.js index 7b73ea8be6..5a46227b49 100644 --- a/packages/frontend-core/src/components/grid/stores/index.js +++ b/packages/frontend-core/src/components/grid/stores/index.js @@ -17,6 +17,7 @@ import * as Filter from "./filter" import * as Notifications from "./notifications" import * as Table from "./table" import * as ViewV2 from "./viewV2" +import * as Query from "./query" import * as Datasource from "./datasource" const DependencyOrderedStores = [ @@ -26,6 +27,7 @@ const DependencyOrderedStores = [ Scroll, Table, ViewV2, + Query, Datasource, Columns, Rows, diff --git a/packages/frontend-core/src/components/grid/stores/query.js b/packages/frontend-core/src/components/grid/stores/query.js new file mode 100644 index 0000000000..6ac0547d27 --- /dev/null +++ b/packages/frontend-core/src/components/grid/stores/query.js @@ -0,0 +1,108 @@ +import { get } from "svelte/store" + +export const createActions = context => { + const { API, columns, stickyColumn } = context + + const saveDefinition = async newDefinition => { + await API.saveQuery(newDefinition) + } + + const saveRow = async () => { + throw "Rows cannot be updated through queries" + } + + const deleteRows = async () => { + throw "Rows cannot be deleted through queries" + } + + const getRow = () => { + throw "Queries don't support fetching individual rows" + } + + const isDatasourceValid = datasource => { + return datasource?.type === "query" && datasource?._id + } + + const canUseColumn = name => { + const $columns = get(columns) + const $sticky = get(stickyColumn) + return $columns.some(col => col.name === name) || $sticky?.name === name + } + + return { + query: { + actions: { + saveDefinition, + addRow: saveRow, + updateRow: saveRow, + deleteRows, + getRow, + isDatasourceValid, + canUseColumn, + }, + }, + } +} + +export const initialise = context => { + const { + datasource, + sort, + filter, + query, + initialFilter, + initialSortColumn, + initialSortOrder, + fetch, + } = context + + // Keep a list of subscriptions so that we can clear them when the datasource + // config changes + let unsubscribers = [] + + // Observe datasource changes and apply logic for view V2 datasources + datasource.subscribe($datasource => { + // Clear previous subscriptions + unsubscribers?.forEach(unsubscribe => unsubscribe()) + unsubscribers = [] + if (!query.actions.isDatasourceValid($datasource)) { + return + } + + // Wipe state + filter.set(get(initialFilter)) + sort.set({ + column: get(initialSortColumn), + order: get(initialSortOrder) || "ascending", + }) + + // Update fetch when filter changes + unsubscribers.push( + filter.subscribe($filter => { + // Ensure we're updating the correct fetch + const $fetch = get(fetch) + if ($fetch?.options?.datasource?._id !== $datasource._id) { + return + } + $fetch.update({ + filter: $filter, + }) + }) + ) + + // Update fetch when sorting changes + unsubscribers.push( + sort.subscribe($sort => { + // Ensure we're updating the correct fetch + const $fetch = get(fetch) + if ($fetch?.options?.datasource?._id !== $datasource._id) { + return + } + $fetch.update({ + sortOrder: $sort.order || "ascending", + sortColumn: $sort.column, + }) + }) + ) + }) +} diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index 392bf392e8..2e2832f209 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -1,7 +1,8 @@ import { writable, derived, get } from "svelte/store" -import { fetchData } from "../../../fetch/fetchData" +import { fetchData } from "../../../fetch" import { NewRowID, RowPageSize } from "../lib/constants" import { tick } from "svelte" +import { Helpers } from "@budibase/bbui" export const createStores = () => { const rows = writable([]) @@ -413,6 +414,13 @@ export const createActions = context => { let newRow for (let i = 0; i < newRows.length; i++) { newRow = newRows[i] + + // Ensure we have a unique _id. + // This means generating one for non DS+. + if (!newRow._id) { + newRow._id = Helpers.hashString(JSON.stringify(newRow)) + } + if (!rowCacheMap[newRow._id]) { rowCacheMap[newRow._id] = true rowsToAppend.push(newRow) diff --git a/packages/frontend-core/src/components/grid/stores/table.js b/packages/frontend-core/src/components/grid/stores/table.js index ed13609f45..9a4058ac8f 100644 --- a/packages/frontend-core/src/components/grid/stores/table.js +++ b/packages/frontend-core/src/components/grid/stores/table.js @@ -3,11 +3,7 @@ import { get } from "svelte/store" const SuppressErrors = true export const createActions = context => { - const { definition, API, datasource, columns, stickyColumn } = context - - const refreshDefinition = async () => { - definition.set(await API.fetchTableDefinition(get(datasource).tableId)) - } + const { API, datasource, columns, stickyColumn } = context const saveDefinition = async newDefinition => { await API.saveTable(newDefinition) @@ -52,7 +48,6 @@ export const createActions = context => { return { table: { actions: { - refreshDefinition, saveDefinition, addRow: saveRow, updateRow: saveRow, diff --git a/packages/frontend-core/src/components/grid/stores/viewV2.js b/packages/frontend-core/src/components/grid/stores/viewV2.js index b9a4bc099b..000727b262 100644 --- a/packages/frontend-core/src/components/grid/stores/viewV2.js +++ b/packages/frontend-core/src/components/grid/stores/viewV2.js @@ -3,20 +3,7 @@ import { get } from "svelte/store" const SuppressErrors = true export const createActions = context => { - const { definition, API, datasource, columns, stickyColumn } = context - - const refreshDefinition = async () => { - const $datasource = get(datasource) - if (!$datasource) { - definition.set(null) - return - } - const table = await API.fetchTableDefinition($datasource.tableId) - const view = Object.values(table?.views || {}).find( - view => view.id === $datasource.id - ) - definition.set(view) - } + const { API, datasource, columns, stickyColumn } = context const saveDefinition = async newDefinition => { await API.viewV2.update(newDefinition) @@ -61,7 +48,6 @@ export const createActions = context => { return { viewV2: { actions: { - refreshDefinition, saveDefinition, addRow: saveRow, updateRow: saveRow, diff --git a/packages/frontend-core/src/fetch/fetchData.js b/packages/frontend-core/src/fetch/index.js similarity index 72% rename from packages/frontend-core/src/fetch/fetchData.js rename to packages/frontend-core/src/fetch/index.js index 063dd02cbf..0edd07762b 100644 --- a/packages/frontend-core/src/fetch/fetchData.js +++ b/packages/frontend-core/src/fetch/index.js @@ -24,7 +24,18 @@ const DataFetchMap = { jsonarray: JSONArrayFetch, } +// Constructs a new fetch model for a certain datasource export const fetchData = ({ API, datasource, options }) => { const Fetch = DataFetchMap[datasource?.type] || TableFetch return new Fetch({ API, datasource, ...options }) } + +// Fetches the definition of any type of datasource +export const getDatasourceDefinition = async ({ API, datasource }) => { + const handler = DataFetchMap[datasource?.type] + if (!handler) { + return null + } + const instance = new handler({ API }) + return await instance.getDefinition(datasource) +} diff --git a/packages/frontend-core/src/index.js b/packages/frontend-core/src/index.js index b0afc0c25d..f51be616f8 100644 --- a/packages/frontend-core/src/index.js +++ b/packages/frontend-core/src/index.js @@ -1,5 +1,5 @@ export { createAPIClient } from "./api" -export { fetchData } from "./fetch/fetchData" +export { fetchData } from "./fetch" export { Utils } from "./utils" export * as Constants from "./constants" export * from "./stores"