1
0
Fork 0
mirror of synced 2024-10-03 10:36:59 +13:00

Add initial support for query datasources in grids

This commit is contained in:
Andrew Kingston 2023-10-03 17:35:00 +01:00
parent b9ae3d0e23
commit d03fdb6df9
13 changed files with 178 additions and 34 deletions

View file

@ -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
},
{

View file

@ -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}
>
<Grid
datasource={table}
datasource={datasource || table}
{API}
{stripeRows}
{initialFilter}

View file

@ -34,7 +34,7 @@
column.schema.autocolumn ||
column.schema.disabled ||
column.schema.type === "formula" ||
(!$config.canEditRows && row._id)
(!$config.canEditRows && !row._isNewRow)
// Register this cell API if the row is focused
$: {

View file

@ -33,7 +33,7 @@
let visible = false
let isAdding = false
let newRow = {}
let newRow = { _isNewRow: true }
let offset = 0
$: firstColumn = $stickyColumn || $renderedColumns[0]
@ -58,7 +58,9 @@
// Create row
const newRowIndex = offset ? undefined : 0
const savedRow = await rows.actions.addRow(newRow, newRowIndex)
let rowToCreate = { ...newRow }
delete rowToCreate._isNewRow
const savedRow = await rows.actions.addRow(rowToCreate, newRowIndex)
if (savedRow) {
// Reset state
clear()

View file

@ -37,9 +37,10 @@ export const deriveStores = context => {
[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
}
)

View file

@ -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

View file

@ -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,

View file

@ -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,
})
})
)
})
}

View file

@ -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)

View file

@ -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,

View file

@ -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,

View file

@ -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)
}

View file

@ -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"