Add initial support for query datasources in grids
This commit is contained in:
parent
b9ae3d0e23
commit
d03fdb6df9
|
@ -5545,12 +5545,11 @@
|
||||||
"width": 600,
|
"width": 600,
|
||||||
"height": 400
|
"height": 400
|
||||||
},
|
},
|
||||||
"info": "Grid Blocks are only compatible with internal or SQL tables",
|
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "table",
|
"type": "dataSource",
|
||||||
"label": "Data",
|
"label": "Data",
|
||||||
"key": "table",
|
"key": "datasource",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { Grid } from "@budibase/frontend-core"
|
import { Grid } from "@budibase/frontend-core"
|
||||||
|
|
||||||
export let table
|
export let datasource
|
||||||
export let allowAddRows = true
|
export let allowAddRows = true
|
||||||
export let allowEditRows = true
|
export let allowEditRows = true
|
||||||
export let allowDeleteRows = true
|
export let allowDeleteRows = true
|
||||||
|
@ -15,6 +15,9 @@
|
||||||
export let fixedRowHeight = null
|
export let fixedRowHeight = null
|
||||||
export let columns = null
|
export let columns = null
|
||||||
|
|
||||||
|
// Legacy settings
|
||||||
|
export let table
|
||||||
|
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const { styleable, API, builderStore, notificationStore } = getContext("sdk")
|
const { styleable, API, builderStore, notificationStore } = getContext("sdk")
|
||||||
|
|
||||||
|
@ -38,7 +41,7 @@
|
||||||
class:in-builder={$builderStore.inBuilder}
|
class:in-builder={$builderStore.inBuilder}
|
||||||
>
|
>
|
||||||
<Grid
|
<Grid
|
||||||
datasource={table}
|
datasource={datasource || table}
|
||||||
{API}
|
{API}
|
||||||
{stripeRows}
|
{stripeRows}
|
||||||
{initialFilter}
|
{initialFilter}
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
column.schema.autocolumn ||
|
column.schema.autocolumn ||
|
||||||
column.schema.disabled ||
|
column.schema.disabled ||
|
||||||
column.schema.type === "formula" ||
|
column.schema.type === "formula" ||
|
||||||
(!$config.canEditRows && row._id)
|
(!$config.canEditRows && !row._isNewRow)
|
||||||
|
|
||||||
// Register this cell API if the row is focused
|
// Register this cell API if the row is focused
|
||||||
$: {
|
$: {
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
|
|
||||||
let visible = false
|
let visible = false
|
||||||
let isAdding = false
|
let isAdding = false
|
||||||
let newRow = {}
|
let newRow = { _isNewRow: true }
|
||||||
let offset = 0
|
let offset = 0
|
||||||
|
|
||||||
$: firstColumn = $stickyColumn || $renderedColumns[0]
|
$: firstColumn = $stickyColumn || $renderedColumns[0]
|
||||||
|
@ -58,7 +58,9 @@
|
||||||
|
|
||||||
// Create row
|
// Create row
|
||||||
const newRowIndex = offset ? undefined : 0
|
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) {
|
if (savedRow) {
|
||||||
// Reset state
|
// Reset state
|
||||||
clear()
|
clear()
|
||||||
|
|
|
@ -37,9 +37,10 @@ export const deriveStores = context => {
|
||||||
[props, hasNonAutoColumn],
|
[props, hasNonAutoColumn],
|
||||||
([$props, $hasNonAutoColumn]) => {
|
([$props, $hasNonAutoColumn]) => {
|
||||||
let config = { ...$props }
|
let config = { ...$props }
|
||||||
|
const type = $props.datasource?.type
|
||||||
|
|
||||||
// Disable some features if we're editing a view
|
// Disable some features if we're editing a view
|
||||||
if ($props.datasource?.type === "viewV2") {
|
if (type === "viewV2") {
|
||||||
config.canEditColumns = false
|
config.canEditColumns = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +49,14 @@ export const deriveStores = context => {
|
||||||
config.canAddRows = false
|
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
|
return config
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { derived, get, writable } from "svelte/store"
|
import { derived, get, writable } from "svelte/store"
|
||||||
|
import { getDatasourceDefinition } from "../../../fetch"
|
||||||
|
|
||||||
export const createStores = () => {
|
export const createStores = () => {
|
||||||
const definition = writable(null)
|
const definition = writable(null)
|
||||||
|
@ -19,6 +20,14 @@ export const deriveStores = context => {
|
||||||
}
|
}
|
||||||
let newSchema = { ...$definition?.schema }
|
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
|
// Apply schema overrides
|
||||||
Object.keys($schemaOverrides || {}).forEach(field => {
|
Object.keys($schemaOverrides || {}).forEach(field => {
|
||||||
if (newSchema[field]) {
|
if (newSchema[field]) {
|
||||||
|
@ -48,7 +57,16 @@ export const deriveStores = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createActions = 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
|
// Gets the appropriate API for the configured datasource type
|
||||||
const getAPI = () => {
|
const getAPI = () => {
|
||||||
|
@ -58,6 +76,8 @@ export const createActions = context => {
|
||||||
return table
|
return table
|
||||||
case "viewV2":
|
case "viewV2":
|
||||||
return viewV2
|
return viewV2
|
||||||
|
case "query":
|
||||||
|
return query
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -65,7 +85,8 @@ export const createActions = context => {
|
||||||
|
|
||||||
// Refreshes the datasource definition
|
// Refreshes the datasource definition
|
||||||
const refreshDefinition = async () => {
|
const refreshDefinition = async () => {
|
||||||
return await getAPI()?.actions.refreshDefinition()
|
const def = await getDatasourceDefinition({ API, datasource })
|
||||||
|
definition.set(def)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saves the datasource definition
|
// Saves the datasource definition
|
||||||
|
|
|
@ -17,6 +17,7 @@ import * as Filter from "./filter"
|
||||||
import * as Notifications from "./notifications"
|
import * as Notifications from "./notifications"
|
||||||
import * as Table from "./table"
|
import * as Table from "./table"
|
||||||
import * as ViewV2 from "./viewV2"
|
import * as ViewV2 from "./viewV2"
|
||||||
|
import * as Query from "./query"
|
||||||
import * as Datasource from "./datasource"
|
import * as Datasource from "./datasource"
|
||||||
|
|
||||||
const DependencyOrderedStores = [
|
const DependencyOrderedStores = [
|
||||||
|
@ -26,6 +27,7 @@ const DependencyOrderedStores = [
|
||||||
Scroll,
|
Scroll,
|
||||||
Table,
|
Table,
|
||||||
ViewV2,
|
ViewV2,
|
||||||
|
Query,
|
||||||
Datasource,
|
Datasource,
|
||||||
Columns,
|
Columns,
|
||||||
Rows,
|
Rows,
|
||||||
|
|
|
@ -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,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
import { writable, derived, get } from "svelte/store"
|
import { writable, derived, get } from "svelte/store"
|
||||||
import { fetchData } from "../../../fetch/fetchData"
|
import { fetchData } from "../../../fetch"
|
||||||
import { NewRowID, RowPageSize } from "../lib/constants"
|
import { NewRowID, RowPageSize } from "../lib/constants"
|
||||||
import { tick } from "svelte"
|
import { tick } from "svelte"
|
||||||
|
import { Helpers } from "@budibase/bbui"
|
||||||
|
|
||||||
export const createStores = () => {
|
export const createStores = () => {
|
||||||
const rows = writable([])
|
const rows = writable([])
|
||||||
|
@ -413,6 +414,13 @@ export const createActions = context => {
|
||||||
let newRow
|
let newRow
|
||||||
for (let i = 0; i < newRows.length; i++) {
|
for (let i = 0; i < newRows.length; i++) {
|
||||||
newRow = newRows[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]) {
|
if (!rowCacheMap[newRow._id]) {
|
||||||
rowCacheMap[newRow._id] = true
|
rowCacheMap[newRow._id] = true
|
||||||
rowsToAppend.push(newRow)
|
rowsToAppend.push(newRow)
|
||||||
|
|
|
@ -3,11 +3,7 @@ import { get } from "svelte/store"
|
||||||
const SuppressErrors = true
|
const SuppressErrors = true
|
||||||
|
|
||||||
export const createActions = context => {
|
export const createActions = context => {
|
||||||
const { definition, API, datasource, columns, stickyColumn } = context
|
const { API, datasource, columns, stickyColumn } = context
|
||||||
|
|
||||||
const refreshDefinition = async () => {
|
|
||||||
definition.set(await API.fetchTableDefinition(get(datasource).tableId))
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveDefinition = async newDefinition => {
|
const saveDefinition = async newDefinition => {
|
||||||
await API.saveTable(newDefinition)
|
await API.saveTable(newDefinition)
|
||||||
|
@ -52,7 +48,6 @@ export const createActions = context => {
|
||||||
return {
|
return {
|
||||||
table: {
|
table: {
|
||||||
actions: {
|
actions: {
|
||||||
refreshDefinition,
|
|
||||||
saveDefinition,
|
saveDefinition,
|
||||||
addRow: saveRow,
|
addRow: saveRow,
|
||||||
updateRow: saveRow,
|
updateRow: saveRow,
|
||||||
|
|
|
@ -3,20 +3,7 @@ import { get } from "svelte/store"
|
||||||
const SuppressErrors = true
|
const SuppressErrors = true
|
||||||
|
|
||||||
export const createActions = context => {
|
export const createActions = context => {
|
||||||
const { definition, API, datasource, columns, stickyColumn } = context
|
const { 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 saveDefinition = async newDefinition => {
|
const saveDefinition = async newDefinition => {
|
||||||
await API.viewV2.update(newDefinition)
|
await API.viewV2.update(newDefinition)
|
||||||
|
@ -61,7 +48,6 @@ export const createActions = context => {
|
||||||
return {
|
return {
|
||||||
viewV2: {
|
viewV2: {
|
||||||
actions: {
|
actions: {
|
||||||
refreshDefinition,
|
|
||||||
saveDefinition,
|
saveDefinition,
|
||||||
addRow: saveRow,
|
addRow: saveRow,
|
||||||
updateRow: saveRow,
|
updateRow: saveRow,
|
||||||
|
|
|
@ -24,7 +24,18 @@ const DataFetchMap = {
|
||||||
jsonarray: JSONArrayFetch,
|
jsonarray: JSONArrayFetch,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constructs a new fetch model for a certain datasource
|
||||||
export const fetchData = ({ API, datasource, options }) => {
|
export const fetchData = ({ API, datasource, options }) => {
|
||||||
const Fetch = DataFetchMap[datasource?.type] || TableFetch
|
const Fetch = DataFetchMap[datasource?.type] || TableFetch
|
||||||
return new Fetch({ API, datasource, ...options })
|
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)
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
export { createAPIClient } from "./api"
|
export { createAPIClient } from "./api"
|
||||||
export { fetchData } from "./fetch/fetchData"
|
export { fetchData } from "./fetch"
|
||||||
export { Utils } from "./utils"
|
export { Utils } from "./utils"
|
||||||
export * as Constants from "./constants"
|
export * as Constants from "./constants"
|
||||||
export * from "./stores"
|
export * from "./stores"
|
||||||
|
|
Loading…
Reference in New Issue