diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index dee0b86fb3..76a05b3bdb 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -35,7 +35,7 @@ export const getDataProviderComponents = (asset, componentId) => { // Filter by only data provider components return path.filter(component => { const def = store.actions.components.getDefinition(component._component) - return def?.dataProvider + return def?.dataContext != null }) } @@ -101,15 +101,16 @@ const getContextBindings = (asset, componentId) => { // Create bindings for each data provider dataProviders.forEach(component => { - const isForm = component._component.endsWith("/form") - const datasource = getDatasourceForProvider(component) - let tableName, schema + const def = store.actions.components.getDefinition(component._component) + const contextDefinition = def.dataContext + let schema // Forms are an edge case which do not need table schemas - if (isForm) { + if (contextDefinition.type === "form") { schema = buildFormSchema(component) tableName = "Fields" } else { + const datasource = getDatasourceForProvider(component) if (!datasource) { return } diff --git a/packages/builder/src/components/design/AppPreview/componentStructure.json b/packages/builder/src/components/design/AppPreview/componentStructure.json index 16109b5a96..0b53ea45fd 100644 --- a/packages/builder/src/components/design/AppPreview/componentStructure.json +++ b/packages/builder/src/components/design/AppPreview/componentStructure.json @@ -1,5 +1,6 @@ [ "container", + "dataprovider", "datagrid", "list", "button", diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataProviderSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataProviderSelect.svelte new file mode 100644 index 0000000000..d13b0c68eb --- /dev/null +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataProviderSelect.svelte @@ -0,0 +1,23 @@ + + + diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DatasourceSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte similarity index 100% rename from packages/builder/src/components/design/PropertiesPanel/PropertyControls/DatasourceSelect.svelte rename to packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/SchemaSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/SchemaSelect.svelte index 62584fbc93..b21f2c3bd5 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/SchemaSelect.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/SchemaSelect.svelte @@ -1,7 +1,7 @@ - + diff --git a/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte b/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte index 3cb2437e8e..5db5c77dd5 100644 --- a/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte @@ -13,7 +13,8 @@ import OptionSelect from "./PropertyControls/OptionSelect.svelte" import Checkbox from "./PropertyControls/Checkbox.svelte" import TableSelect from "./PropertyControls/TableSelect.svelte" - import DatasourceSelect from "./PropertyControls/DatasourceSelect.svelte" + import DataSourceSelect from "./PropertyControls/DataSourceSelect.svelte" + import DataProviderSelect from "./PropertyControls/DataProviderSelect.svelte" import FieldSelect from "./PropertyControls/FieldSelect.svelte" import MultiFieldSelect from "./PropertyControls/MultiFieldSelect.svelte" import SchemaSelect from "./PropertyControls/SchemaSelect.svelte" @@ -61,7 +62,8 @@ const controlMap = { text: Input, select: OptionSelect, - datasource: DatasourceSelect, + dataSource: DataSourceSelect, + dataProvider: DataProviderSelect, detailScreen: DetailScreenSelect, boolean: Checkbox, number: Input, diff --git a/packages/client/src/api/datasources.js b/packages/client/src/api/datasources.js index c2af90592e..508e1e8db0 100644 --- a/packages/client/src/api/datasources.js +++ b/packages/client/src/api/datasources.js @@ -7,31 +7,31 @@ import { executeQuery } from "./queries" /** * Fetches all rows for a particular Budibase data source. */ -export const fetchDatasource = async datasource => { - if (!datasource || !datasource.type) { +export const fetchDatasource = async dataSource => { + if (!dataSource || !dataSource.type) { return [] } // Fetch all rows in data source - const { type, tableId, fieldName } = datasource + const { type, tableId, fieldName } = dataSource let rows = [] if (type === "table") { rows = await fetchTableData(tableId) } else if (type === "view") { - rows = await fetchViewData(datasource) + rows = await fetchViewData(dataSource) } else if (type === "query") { // Set the default query params - let parameters = cloneDeep(datasource.queryParams || {}) - for (let param of datasource.parameters) { + let parameters = cloneDeep(dataSource.queryParams || {}) + for (let param of dataSource.parameters) { if (!parameters[param.name]) { parameters[param.name] = param.default } } - rows = await executeQuery({ queryId: datasource._id, parameters }) + rows = await executeQuery({ queryId: dataSource._id, parameters }) } else if (type === "link") { rows = await fetchRelationshipData({ - rowId: datasource.rowId, - tableId: datasource.rowTableId, + rowId: dataSource.rowId, + tableId: dataSource.rowTableId, fieldName, }) } diff --git a/packages/client/src/api/queries.js b/packages/client/src/api/queries.js index 9876c85a63..715841f0fa 100644 --- a/packages/client/src/api/queries.js +++ b/packages/client/src/api/queries.js @@ -1,4 +1,4 @@ -import { notificationStore, datasourceStore } from "../store" +import { notificationStore, dataSourceStore } from "../store" import API from "./api" /** @@ -20,7 +20,7 @@ export const executeQuery = async ({ queryId, parameters }) => { notificationStore.danger("An error has occurred") } else if (!query.readable) { notificationStore.success("Query executed successfully") - datasourceStore.actions.invalidateDatasource(query.datasourceId) + dataSourceStore.actions.invalidateDataSource(query.datasourceId) } return res } diff --git a/packages/client/src/api/rows.js b/packages/client/src/api/rows.js index 639516f0f6..87cc9a9d37 100644 --- a/packages/client/src/api/rows.js +++ b/packages/client/src/api/rows.js @@ -1,4 +1,4 @@ -import { notificationStore, datasourceStore } from "../store" +import { notificationStore, dataSourceStore } from "../store" import API from "./api" import { fetchTableDefinition } from "./tables" @@ -31,7 +31,7 @@ export const saveRow = async row => { : notificationStore.success("Row saved") // Refresh related datasources - datasourceStore.actions.invalidateDatasource(row.tableId) + dataSourceStore.actions.invalidateDataSource(row.tableId) return res } @@ -52,7 +52,7 @@ export const updateRow = async row => { : notificationStore.success("Row updated") // Refresh related datasources - datasourceStore.actions.invalidateDatasource(row.tableId) + dataSourceStore.actions.invalidateDataSource(row.tableId) return res } @@ -72,7 +72,7 @@ export const deleteRow = async ({ tableId, rowId, revId }) => { : notificationStore.success("Row deleted") // Refresh related datasources - datasourceStore.actions.invalidateDatasource(tableId) + dataSourceStore.actions.invalidateDataSource(tableId) return res } @@ -96,7 +96,7 @@ export const deleteRows = async ({ tableId, rows }) => { : notificationStore.success(`${rows.length} row(s) deleted`) // Refresh related datasources - datasourceStore.actions.invalidateDatasource(tableId) + dataSourceStore.actions.invalidateDataSource(tableId) return res } diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte index e8ab668ef2..8fb1b83d4c 100644 --- a/packages/client/src/components/ClientApp.svelte +++ b/packages/client/src/components/ClientApp.svelte @@ -33,7 +33,7 @@ { type: ActionTypes.RefreshDatasource, callback: () => authStore.actions.fetchUser(), - metadata: { datasource: { type: "table", tableId: TableNames.USERS } }, + metadata: { dataSource: { type: "table", tableId: TableNames.USERS } }, }, ] diff --git a/packages/client/src/components/Provider.svelte b/packages/client/src/components/Provider.svelte index 6f56e62abe..d2679957c0 100644 --- a/packages/client/src/components/Provider.svelte +++ b/packages/client/src/components/Provider.svelte @@ -1,6 +1,6 @@ diff --git a/packages/client/src/store/dataSource.js b/packages/client/src/store/dataSource.js new file mode 100644 index 0000000000..2645c949a2 --- /dev/null +++ b/packages/client/src/store/dataSource.js @@ -0,0 +1,84 @@ +import { writable, get } from "svelte/store" +import { notificationStore } from "./notification" + +export const createDataSourceStore = () => { + const store = writable([]) + + // Registers a new dataSource instance + const registerDataSource = (dataSource, instanceId, refresh) => { + if (!dataSource || !instanceId || !refresh) { + return + } + + // Create a list of all relevant dataSource IDs which would require that + // this dataSource is refreshed + let dataSourceIds = [] + + // Extract table ID + if (dataSource.type === "table" || dataSource.type === "view") { + if (dataSource.tableId) { + dataSourceIds.push(dataSource.tableId) + } + } + + // Extract both table IDs from both sides of the relationship + else if (dataSource.type === "link") { + if (dataSource.rowTableId) { + dataSourceIds.push(dataSource.rowTableId) + } + if (dataSource.tableId) { + dataSourceIds.push(dataSource.tableId) + } + } + + // Extract the dataSource ID (not the query ID) for queries + else if (dataSource.type === "query") { + if (dataSource.dataSourceId) { + dataSourceIds.push(dataSource.dataSourceId) + } + } + + // Store configs for each relevant dataSource ID + if (dataSourceIds.length) { + store.update(state => { + dataSourceIds.forEach(id => { + state.push({ + dataSourceId: id, + instanceId, + refresh, + }) + }) + return state + }) + } + } + + // Removes all registered dataSource instances belonging to a particular + // instance ID + const unregisterInstance = instanceId => { + store.update(state => { + return state.filter(instance => instance.instanceId !== instanceId) + }) + } + + // Invalidates a specific dataSource ID by refreshing all instances + // which depend on data from that dataSource + const invalidateDataSource = dataSourceId => { + const relatedInstances = get(store).filter(instance => { + return instance.dataSourceId === dataSourceId + }) + if (relatedInstances?.length) { + notificationStore.blockNotifications(1000) + } + relatedInstances?.forEach(instance => { + instance.refresh() + }) + } + + return { + subscribe: store.subscribe, + actions: { registerDataSource, unregisterInstance, invalidateDataSource }, + } +} + +export const dataSourceStore = createDataSourceStore() diff --git a/packages/client/src/store/datasource.js b/packages/client/src/store/datasource.js deleted file mode 100644 index d40fbbbfc1..0000000000 --- a/packages/client/src/store/datasource.js +++ /dev/null @@ -1,84 +0,0 @@ -import { writable, get } from "svelte/store" -import { notificationStore } from "./notification" - -export const createDatasourceStore = () => { - const store = writable([]) - - // Registers a new datasource instance - const registerDatasource = (datasource, instanceId, refresh) => { - if (!datasource || !instanceId || !refresh) { - return - } - - // Create a list of all relevant datasource IDs which would require that - // this datasource is refreshed - let datasourceIds = [] - - // Extract table ID - if (datasource.type === "table" || datasource.type === "view") { - if (datasource.tableId) { - datasourceIds.push(datasource.tableId) - } - } - - // Extract both table IDs from both sides of the relationship - else if (datasource.type === "link") { - if (datasource.rowTableId) { - datasourceIds.push(datasource.rowTableId) - } - if (datasource.tableId) { - datasourceIds.push(datasource.tableId) - } - } - - // Extract the datasource ID (not the query ID) for queries - else if (datasource.type === "query") { - if (datasource.datasourceId) { - datasourceIds.push(datasource.datasourceId) - } - } - - // Store configs for each relevant datasource ID - if (datasourceIds.length) { - store.update(state => { - datasourceIds.forEach(id => { - state.push({ - datasourceId: id, - instanceId, - refresh, - }) - }) - return state - }) - } - } - - // Removes all registered datasource instances belonging to a particular - // instance ID - const unregisterInstance = instanceId => { - store.update(state => { - return state.filter(instance => instance.instanceId !== instanceId) - }) - } - - // Invalidates a specific datasource ID by refreshing all instances - // which depend on data from that datasource - const invalidateDatasource = datasourceId => { - const relatedInstances = get(store).filter(instance => { - return instance.datasourceId === datasourceId - }) - if (relatedInstances?.length) { - notificationStore.blockNotifications(1000) - } - relatedInstances?.forEach(instance => { - instance.refresh() - }) - } - - return { - subscribe: store.subscribe, - actions: { registerDatasource, unregisterInstance, invalidateDatasource }, - } -} - -export const datasourceStore = createDatasourceStore() diff --git a/packages/client/src/store/index.js b/packages/client/src/store/index.js index 9d08423374..ebf89e14e3 100644 --- a/packages/client/src/store/index.js +++ b/packages/client/src/store/index.js @@ -3,7 +3,7 @@ export { notificationStore } from "./notification" export { routeStore } from "./routes" export { screenStore } from "./screens" export { builderStore } from "./builder" -export { datasourceStore } from "./datasource" +export { dataSourceStore } from "./dataSource" // Context stores are layered and duplicated, so it is not a singleton export { createContextStore } from "./context" diff --git a/packages/standard-components/manifest.json b/packages/standard-components/manifest.json index 5be1665b03..fdbba6e150 100644 --- a/packages/standard-components/manifest.json +++ b/packages/standard-components/manifest.json @@ -84,13 +84,11 @@ "icon": "ri-list-check-2", "styleable": true, "hasChildren": true, - "dataProvider": true, - "actions": ["RefreshDatasource"], "settings": [ { - "type": "datasource", + "type": "dataProvider", "label": "Data", - "key": "datasource" + "key": "dataProviderId" }, { "type": "text", @@ -103,7 +101,11 @@ "label": "Filtering", "key": "filter" } - ] + ], + "dataContext": { + "type": "schema", + "dataProviderSetting": "dataProviderId" + } }, "search": { "name": "Search", @@ -1472,5 +1474,44 @@ "defaultValue": false } ] + }, + "dataprovider": { + "name": "Data Provider", + "icon": "ri-database-2-line", + "styleable": false, + "hasChildren": true, + "settings": [ + { + "type": "dataSource", + "label": "Data", + "key": "dataSource" + }, + { + "type": "filter", + "label": "Filtering", + "key": "filter" + } + ], + "dataContext": { + "type": "static", + "values": [ + { + "label": "Rows", + "key": "rows" + }, + { + "label": "Rows Length", + "key": "rowsLength" + }, + { + "label": "Loading", + "key": "loading" + }, + { + "label": "Loaded", + "key": "loaded" + } + ] + } } } diff --git a/packages/standard-components/src/DataProvider.svelte b/packages/standard-components/src/DataProvider.svelte new file mode 100644 index 0000000000..a8f42c94d6 --- /dev/null +++ b/packages/standard-components/src/DataProvider.svelte @@ -0,0 +1,93 @@ + + + + + diff --git a/packages/standard-components/src/List.svelte b/packages/standard-components/src/List.svelte index ee7880fb24..e9c66c4a9f 100644 --- a/packages/standard-components/src/List.svelte +++ b/packages/standard-components/src/List.svelte @@ -1,68 +1,32 @@ - -
- {#if filteredRows.length > 0} - {#if $component.children === 0 && $builderStore.inBuilder} -

Add some components to display.

- {:else} - {#each filteredRows as row} - - - - {/each} - {/if} - {:else if loaded && noRowsMessage} -

{noRowsMessage}

+
+ {#if data.length > 0} + {#if $component.children === 0 && $builderStore.inBuilder} +

Add some components to display.

+ {:else} + {#each data as row} + + + + {/each} {/if} -
- + {:else if loaded && noRowsMessage} +

{noRowsMessage}

+ {/if} +