From 95faeea28638f234342c42ea87c39512c8509bf5 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 18:32:31 +0200 Subject: [PATCH 001/316] datasourceType setup --- .../builder/src/components/backend/DataTable/DataTable.svelte | 1 + packages/client/src/components/app/GridBlock.svelte | 1 + packages/frontend-core/src/components/grid/layout/Grid.svelte | 2 ++ packages/frontend-core/src/components/grid/stores/rows.js | 3 ++- 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/components/backend/DataTable/DataTable.svelte b/packages/builder/src/components/backend/DataTable/DataTable.svelte index 33db9b60e3..3fcaefc5bd 100644 --- a/packages/builder/src/components/backend/DataTable/DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/DataTable.svelte @@ -44,6 +44,7 @@ { loading, sort, tableId, + datasourceType, API, scroll, validation, @@ -111,7 +112,7 @@ export const deriveStores = context => { const newFetch = fetchData({ API, datasource: { - type: "table", + type: datasourceType, tableId: $tableId, }, options: { From 9ccc54773dbdea33264677caf24d7a4f5c383715 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 18:32:52 +0200 Subject: [PATCH 002/316] ViewV2 page --- .../TableNavigator/TableNavigator.svelte | 10 +++++++-- .../data/view/v2/[viewName].svelte | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewName].svelte diff --git a/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte b/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte index d9def682dc..ef8872c1ca 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte @@ -37,13 +37,19 @@ {/if} - {#each [...Object.keys(table.views || {})].sort() as viewName, idx (idx)} + {#each [...Object.entries(table.views || {})].sort() as [viewName, view], idx (idx)} $goto(`./view/${encodeURIComponent(viewName)}`)} + on:click={() => { + if (view.version === 2) { + $goto(`./view/v2/${encodeURIComponent(viewName)}`) + } else { + $goto(`./view/${encodeURIComponent(viewName)}`) + } + }} selectedBy={$userSelectedResourceMap[viewName]} > + import { views } from "stores/backend" + import { Grid } from "@budibase/frontend-core" + import { API } from "api" + + $: tableId = $views.selected?.id + + +
+ +
+ + From 82ea9a7cc1c35ed1a4db616b3b5924c425b74eed Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 10:14:42 +0200 Subject: [PATCH 003/316] Setup datasource type --- .../data/view/v2/{[viewName].svelte => [id].svelte} | 10 ++++++++-- .../frontend-core/src/components/grid/stores/rows.js | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) rename packages/builder/src/pages/builder/app/[application]/data/view/v2/{[viewName].svelte => [id].svelte} (73%) diff --git a/packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewName].svelte b/packages/builder/src/pages/builder/app/[application]/data/view/v2/[id].svelte similarity index 73% rename from packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewName].svelte rename to packages/builder/src/pages/builder/app/[application]/data/view/v2/[id].svelte index 3df89bd777..266fd237f5 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewName].svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/view/v2/[id].svelte @@ -3,11 +3,17 @@ import { Grid } from "@budibase/frontend-core" import { API } from "api" - $: tableId = $views.selected?.id + export let id
- +
diff --git a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte index f7b6f61a10..8f5a35ea1f 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte @@ -1,7 +1,14 @@ diff --git a/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte b/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte index e641bd99a5..caa9de222a 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte @@ -1,5 +1,5 @@ -
- -
- - + diff --git a/packages/builder/src/stores/backend/index.js b/packages/builder/src/stores/backend/index.js index 6fbc9f82c7..278e43c1ed 100644 --- a/packages/builder/src/stores/backend/index.js +++ b/packages/builder/src/stores/backend/index.js @@ -1,6 +1,7 @@ export { database } from "./database" export { tables } from "./tables" export { views } from "./views" +export { viewsV2 } from "./viewsV2" export { permissions } from "./permissions" export { roles } from "./roles" export { datasources, ImportTableError } from "./datasources" diff --git a/packages/builder/src/stores/backend/views.js b/packages/builder/src/stores/backend/views.js index 8df337a299..603b0830fc 100644 --- a/packages/builder/src/stores/backend/views.js +++ b/packages/builder/src/stores/backend/views.js @@ -9,7 +9,10 @@ export function createViewsStore() { const derivedStore = derived([store, tables], ([$store, $tables]) => { let list = [] $tables.list?.forEach(table => { - list = list.concat(Object.values(table?.views || {})) + const views = Object.values(table?.views || {}).filter(view => { + return view.version !== 2 + }) + list = list.concat(views) }) return { ...$store, @@ -26,11 +29,7 @@ export function createViewsStore() { } const deleteView = async view => { - if (view.version === 2) { - await API.viewV2.delete(view.id) - } else { - await API.deleteView(view.name) - } + await API.deleteView(view.name) // Update tables tables.update(state => { @@ -40,20 +39,6 @@ export function createViewsStore() { }) } - const create = async view => { - const savedViewResponse = await API.viewV2.create(view) - const savedView = savedViewResponse.data - - // Update tables - tables.update(state => { - const table = state.list.find(table => table._id === view.tableId) - table.views[view.name] = savedView - return { ...state } - }) - - return savedView - } - const save = async view => { const savedView = await API.saveView(view) @@ -74,7 +59,6 @@ export function createViewsStore() { subscribe: derivedStore.subscribe, select, delete: deleteView, - create, save, } } diff --git a/packages/builder/src/stores/backend/viewsV2.js b/packages/builder/src/stores/backend/viewsV2.js new file mode 100644 index 0000000000..8b7b1d876c --- /dev/null +++ b/packages/builder/src/stores/backend/viewsV2.js @@ -0,0 +1,85 @@ +import { writable, derived } from "svelte/store" +import { tables } from "./" +import { API } from "api" + +export function createViewsV2Store() { + const store = writable({ + selectedViewId: null, + }) + const derivedStore = derived([store, tables], ([$store, $tables]) => { + let list = [] + $tables.list?.forEach(table => { + const views = Object.values(table?.views || {}).filter(view => { + return view.version === 2 + }) + list = list.concat(views) + }) + return { + ...$store, + list, + selected: list.find(view => view.id === $store.selectedViewId), + } + }) + + const select = id => { + store.update(state => ({ + ...state, + selectedViewId: id, + })) + } + + const deleteView = async view => { + await API.viewV2.delete(view.id) + + // Update tables + tables.update(state => { + const table = state.list.find(table => table._id === view.tableId) + delete table.views[view.name] + return { ...state } + }) + } + + const create = async view => { + const savedViewResponse = await API.viewV2.create(view) + const savedView = savedViewResponse.data + + // Update tables + tables.update(state => { + const table = state.list.find(table => table._id === view.tableId) + table.views[view.name] = savedView + return { ...state } + }) + + return savedView + } + + const save = async view => { + // No dedicated save endpoint at this time + // const savedView = await API.saveView(view) + // + // // Update tables + // tables.update(state => { + // const table = state.list.find(table => table._id === view.tableId) + // if (table) { + // if (view.originalName) { + // delete table.views[view.originalName] + // } + // table.views[view.name] = savedView + // } + // return { ...state } + // }) + } + + const replace = (id, view) => {} + + return { + subscribe: derivedStore.subscribe, + select, + delete: deleteView, + create, + save, + replace, + } +} + +export const viewsV2 = createViewsV2Store() diff --git a/packages/frontend-core/src/components/grid/layout/Grid.svelte b/packages/frontend-core/src/components/grid/layout/Grid.svelte index d92851719f..556e6ca16e 100644 --- a/packages/frontend-core/src/components/grid/layout/Grid.svelte +++ b/packages/frontend-core/src/components/grid/layout/Grid.svelte @@ -28,8 +28,7 @@ } from "../lib/constants" export let API = null - export let tableId = null - export let datasourceType = null + export let datasource = null export let schemaOverrides = null export let columnWhitelist = null export let allowAddRows = true @@ -77,8 +76,7 @@ // Keep config store up to date with props $: config.set({ - tableId, - datasourceType, + datasource, schemaOverrides, columnWhitelist, allowAddRows, diff --git a/packages/frontend-core/src/components/grid/layout/HeaderRow.svelte b/packages/frontend-core/src/components/grid/layout/HeaderRow.svelte index f39e679da4..d964931682 100644 --- a/packages/frontend-core/src/components/grid/layout/HeaderRow.svelte +++ b/packages/frontend-core/src/components/grid/layout/HeaderRow.svelte @@ -12,7 +12,7 @@ width, config, hasNonAutoColumn, - tableId, + datasource, loading, } = getContext("grid") @@ -33,7 +33,7 @@ {#if $config.allowSchemaChanges} - {#key $tableId} + {#key $datasource} { - const { rows, tableId, users, focusedCellId, table, API } = context + const { rows, datasource, users, focusedCellId, table, API } = context const socket = createWebsocket("/socket/grid") - const connectToTable = tableId => { + const connectToDatasource = datasource => { if (!socket.connected) { return } // Identify which table we are editing const appId = API.getAppID() socket.emit( - GridSocketEvent.SelectTable, - { tableId, appId }, + GridSocketEvent.SelectDatasource, + { datasource, appId }, ({ users: gridUsers }) => { users.set(gridUsers) } @@ -23,7 +23,7 @@ export const createGridWebsocket = context => { // Built-in events socket.on("connect", () => { - connectToTable(get(tableId)) + connectToDatasource(get(datasource)) }) socket.on("connect_error", err => { console.log("Failed to connect to grid websocket:", err.message) @@ -57,7 +57,7 @@ export const createGridWebsocket = context => { }) // Change websocket connection when table changes - tableId.subscribe(connectToTable) + datasource.subscribe(connectToDatasource) // Notify selected cell changes focusedCellId.subscribe($focusedCellId => { diff --git a/packages/frontend-core/src/components/grid/stores/config.js b/packages/frontend-core/src/components/grid/stores/config.js index fc9decc1ef..8aa6b818e2 100644 --- a/packages/frontend-core/src/components/grid/stores/config.js +++ b/packages/frontend-core/src/components/grid/stores/config.js @@ -6,7 +6,7 @@ export const createStores = context => { const getProp = prop => derivedMemo(config, $config => $config[prop]) // Derive and memoize some props so that we can react to them in isolation - const tableId = getProp("tableId") + const datasource = getProp("datasource") const initialSortColumn = getProp("initialSortColumn") const initialSortOrder = getProp("initialSortOrder") const initialFilter = getProp("initialFilter") @@ -18,7 +18,7 @@ export const createStores = context => { return { config, - tableId, + datasource, initialSortColumn, initialSortOrder, initialFilter, diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index dccbc774a4..1346d7c8b2 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -59,7 +59,7 @@ export const deriveStores = context => { filter, loading, sort, - tableId, + datasource, API, scroll, validation, @@ -71,7 +71,7 @@ export const deriveStores = context => { hasNextPage, error, notifications, - props, + config, } = context const instanceLoaded = writable(false) const fetch = writable(null) @@ -95,7 +95,7 @@ export const deriveStores = context => { // Reset everything when table ID changes let unsubscribe = null let lastResetKey = null - tableId.subscribe(async $tableId => { + datasource.subscribe(async $datasource => { // Unsub from previous fetch if one exists unsubscribe?.() fetch.set(null) @@ -108,21 +108,6 @@ export const deriveStores = context => { const $filter = get(filter) const $sort = get(sort) - let datasource - if (props.datasourceType === "viewV2") { - const tableId = $tableId - datasource = { - type: props.datasourceType, - id: $tableId, - tableId: tableId.split("_").slice(0, -1).join("_"), - } - } else { - datasource = { - type: props.datasourceType, - tableId: $tableId, - } - } - // Create new fetch model const newFetch = fetchData({ API, From 352b7ebe1ccb2ac84b95294309ed4ea28d8a9354 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 26 Jul 2023 14:07:07 +0100 Subject: [PATCH 022/316] Add dedicated route for routes v1, improve view creation modal, fix selection state --- .../DataTable/modals/CreateViewModal.svelte | 19 ++++++----- .../TableNavigator/TableNavigator.svelte | 32 ++++++++++--------- .../view/{ => v1}/[viewName]/_layout.svelte | 2 +- .../view/{ => v1}/[viewName]/index.svelte | 0 .../[application]/data/view/v1/index.svelte | 5 +++ .../view/v2/{[id] => [viewId]}/_layout.svelte | 2 +- .../view/v2/{[id] => [viewId]}/index.svelte | 0 .../[application]/data/view/v2/index.svelte | 5 +++ .../src/components/grid/stores/rows.js | 2 +- 9 files changed, 41 insertions(+), 26 deletions(-) rename packages/builder/src/pages/builder/app/[application]/data/view/{ => v1}/[viewName]/_layout.svelte (95%) rename packages/builder/src/pages/builder/app/[application]/data/view/{ => v1}/[viewName]/index.svelte (100%) create mode 100644 packages/builder/src/pages/builder/app/[application]/data/view/v1/index.svelte rename packages/builder/src/pages/builder/app/[application]/data/view/v2/{[id] => [viewId]}/_layout.svelte (96%) rename packages/builder/src/pages/builder/app/[application]/data/view/v2/{[id] => [viewId]}/index.svelte (100%) create mode 100644 packages/builder/src/pages/builder/app/[application]/data/view/v2/index.svelte diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte index 6a7f5b96a1..ac5e522923 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte @@ -1,22 +1,19 @@ {#if $database?._id} @@ -37,28 +45,22 @@ {/if}
- {#each [...Object.entries(table.views || {})].sort() as [viewName, view], idx (idx)} - {@const viewSelected = - $isActive("./view") && $views.selected?.name === viewName} - {@const viewV2Selected = - $isActive("./view/v2") && $viewsV2.selected?.name === viewName} + {#each [...Object.entries(table.views || {})].sort() as [name, view], idx (idx)} { if (view.version === 2) { $goto(`./view/v2/${view.id}`) } else { - $goto(`./view/${encodeURIComponent(viewName)}`) + $goto(`./view/v1/${encodeURIComponent(name)}`) } }} - selectedBy={$userSelectedResourceMap[viewName]} + selectedBy={$userSelectedResourceMap[name]} > - + {/each} {/each} diff --git a/packages/builder/src/pages/builder/app/[application]/data/view/[viewName]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/data/view/v1/[viewName]/_layout.svelte similarity index 95% rename from packages/builder/src/pages/builder/app/[application]/data/view/[viewName]/_layout.svelte rename to packages/builder/src/pages/builder/app/[application]/data/view/v1/[viewName]/_layout.svelte index f3793317e8..7f4fc9c597 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/view/[viewName]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/view/v1/[viewName]/_layout.svelte @@ -13,7 +13,7 @@ stateKey: "selectedViewName", validate: name => $views.list?.some(view => view.name === name), update: views.select, - fallbackUrl: "../", + fallbackUrl: "../../", store: views, routify, decode: decodeURIComponent, diff --git a/packages/builder/src/pages/builder/app/[application]/data/view/[viewName]/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/view/v1/[viewName]/index.svelte similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/data/view/[viewName]/index.svelte rename to packages/builder/src/pages/builder/app/[application]/data/view/v1/[viewName]/index.svelte diff --git a/packages/builder/src/pages/builder/app/[application]/data/view/v1/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/view/v1/index.svelte new file mode 100644 index 0000000000..c11ca87023 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/data/view/v1/index.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/builder/src/pages/builder/app/[application]/data/view/v2/[id]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewId]/_layout.svelte similarity index 96% rename from packages/builder/src/pages/builder/app/[application]/data/view/v2/[id]/_layout.svelte rename to packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewId]/_layout.svelte index 62844a54df..8ddd6adbd0 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/view/v2/[id]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewId]/_layout.svelte @@ -9,7 +9,7 @@ $: store.actions.websocket.selectResource(id) const stopSyncing = syncURLToState({ - urlParam: "id", + urlParam: "viewId", stateKey: "selectedViewId", validate: id => $viewsV2.list?.some(view => view.id === id), update: viewsV2.select, diff --git a/packages/builder/src/pages/builder/app/[application]/data/view/v2/[id]/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewId]/index.svelte similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/data/view/v2/[id]/index.svelte rename to packages/builder/src/pages/builder/app/[application]/data/view/v2/[viewId]/index.svelte diff --git a/packages/builder/src/pages/builder/app/[application]/data/view/v2/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/view/v2/index.svelte new file mode 100644 index 0000000000..c11ca87023 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/data/view/v2/index.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index 1346d7c8b2..5d3cd20109 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -111,7 +111,7 @@ export const deriveStores = context => { // Create new fetch model const newFetch = fetchData({ API, - datasource, + datasource: $datasource, options: { filter: $filter, sortColumn: $sort.column, From 2d3da0dfcf6213e4b2412c625074535d27ffc8c3 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 26 Jul 2023 14:26:34 +0100 Subject: [PATCH 023/316] Fix null issues in view fetch, fix edit view popover, improve handling of nullish view params --- .../backend/DataTable/ViewV2DataTable.svelte | 3 +- .../TableNavigator/TableNavigator.svelte | 2 +- .../popovers/EditViewPopover.svelte | 33 ++++++++++--------- packages/builder/src/helpers/urlStateSync.js | 2 +- .../app/[application]/data/view/index.svelte | 15 +++++---- .../src/components/grid/stores/rows.js | 5 +++ .../frontend-core/src/fetch/ViewV2Fetch.js | 6 ++-- 7 files changed, 39 insertions(+), 27 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte index 6d549147fb..028030bb9a 100644 --- a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte @@ -1,5 +1,5 @@ - + diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index 80e4e6039d..23eae75428 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -482,9 +482,14 @@ export const createActions = context => { } // Refreshes the schema of the data fetch subscription - const refreshTableDefinition = async () => { - const definition = await API.fetchTableDefinition(get(tableId)) - table.set(definition) + const refreshDatasourceDefinition = async () => { + const $datasource = get(datasource) + if ($datasource.type === "table") { + table.set(await API.fetchTableDefinition($datasource.tableId)) + } else if ($datasource.type === "viewV2") { + // const definition = await API.viewsV2.(get(tableId)) + // table.set(definition) + } } // Checks if we have a row with a certain ID @@ -520,7 +525,7 @@ export const createActions = context => { refreshRow, replaceRow, refreshData, - refreshTableDefinition, + refreshDatasourceDefinition, }, }, } From 9665ec34dd7de7dadc0cc49cf8fced0f88568950 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 27 Jul 2023 15:53:50 +0100 Subject: [PATCH 026/316] Adjust grid props and config --- .../src/components/grid/layout/Grid.svelte | 24 +++++++++---------- .../src/components/grid/stores/config.js | 16 +++---------- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/packages/frontend-core/src/components/grid/layout/Grid.svelte b/packages/frontend-core/src/components/grid/layout/Grid.svelte index ea7cd73a4b..343419af58 100644 --- a/packages/frontend-core/src/components/grid/layout/Grid.svelte +++ b/packages/frontend-core/src/components/grid/layout/Grid.svelte @@ -32,12 +32,12 @@ export let datasource = null export let schemaOverrides = null export let columnWhitelist = null - export let allowAddRows = true - export let allowExpandRows = true - export let allowEditRows = true - export let allowDeleteRows = true - export let allowEditColumns = true - export let allowSchemaChanges = true + export let canAddRows = true + export let canExpandRows = true + export let canEditRows = true + export let canDeleteRows = true + export let canEditColumns = true + export let canSaveSchema = true export let stripeRows = false export let collaboration = true export let showAvatars = true @@ -83,12 +83,12 @@ datasource, schemaOverrides, columnWhitelist, - allowAddRows, - allowExpandRows, - allowEditRows, - allowDeleteRows, - allowEditColumns, - allowSchemaChanges, + canAddRows, + canExpandRows, + canEditRows, + canDeleteRows, + canEditColumns, + canSaveSchema, stripeRows, collaboration, showAvatars, diff --git a/packages/frontend-core/src/components/grid/stores/config.js b/packages/frontend-core/src/components/grid/stores/config.js index 82ec8e29f3..0ed04bf741 100644 --- a/packages/frontend-core/src/components/grid/stores/config.js +++ b/packages/frontend-core/src/components/grid/stores/config.js @@ -21,16 +21,10 @@ export const deriveStores = context => { [props, hasNonAutoColumn], ([$props, $hasNonAutoColumn]) => { let config = { - // Row features - canAddRows: $props.allowAddRows, - canExpandRows: $props.allowExpandRows, - canEditRows: $props.allowEditRows, - canDeleteRows: $props.allowDeleteRows, + ...$props, - // Column features - canEditColumns: $props.allowEditColumns, - canEditPrimaryDisplay: $props.allowEditColumns, - canSaveSchema: $props.allowSchemaChanges, + // Additional granular features which we don't expose as props + canEditPrimaryDisplay: $props.canEditColumns, } // Disable some features if we're editing a view @@ -42,15 +36,11 @@ export const deriveStores = context => { config.canAddRows = false } - console.log($hasNonAutoColumn) - // Disable adding rows if we don't have any valid columns if (!$hasNonAutoColumn) { config.canAddRows = false } - console.log(config) - return config } ) From d83820b583bbaf0c4c661a00f9848513c4b94f3c Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 28 Jul 2023 15:57:35 +0100 Subject: [PATCH 027/316] Fix adding rows --- packages/frontend-core/src/api/rows.js | 2 +- .../src/components/grid/stores/index.js | 2 +- .../src/components/grid/stores/rows.js | 17 ++++++++++++----- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/frontend-core/src/api/rows.js b/packages/frontend-core/src/api/rows.js index 8e8570ea2a..3878b1f08b 100644 --- a/packages/frontend-core/src/api/rows.js +++ b/packages/frontend-core/src/api/rows.js @@ -23,7 +23,7 @@ export const buildRowEndpoints = API => ({ return } return await API.post({ - url: `/api/${row.tableId}/rows`, + url: `/api/${row.viewId || row.tableId}/rows`, body: row, suppressErrors, }) diff --git a/packages/frontend-core/src/components/grid/stores/index.js b/packages/frontend-core/src/components/grid/stores/index.js index 985fad8c4a..d287025342 100644 --- a/packages/frontend-core/src/components/grid/stores/index.js +++ b/packages/frontend-core/src/components/grid/stores/index.js @@ -22,8 +22,8 @@ const DependencyOrderedStores = [ Filter, Bounds, Scroll, - Rows, Columns, + Rows, UI, Validation, Resize, diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index 23eae75428..69dd7ae97a 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -90,7 +90,6 @@ export const createActions = context => { } = context const instanceLoaded = writable(false) const fetch = writable(null) - const tableId = writable(null) // Local cache of row IDs to speed up checking if a row exists let rowCacheMap = {} @@ -261,10 +260,18 @@ export const createActions = context => { const addRow = async (row, idx, bubble = false) => { try { // Create row - const newRow = await API.saveRow( - { ...row, tableId: get(tableId) }, - SuppressErrors - ) + const $datasource = get(datasource) + let newRow = { ...row } + if ($datasource.type === "table") { + newRow.tableId = $datasource.tableId + } else if ($datasource.type === "viewV2") { + newRow.tableId = $datasource.tableId + newRow._viewId = $datasource.id + } else { + return + } + console.log(newRow) + newRow = await API.saveRow(newRow, SuppressErrors) // Update state if (idx != null) { From c7c9bd656385d27a41127a52039f35d46638ddc9 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 28 Jul 2023 16:01:18 +0100 Subject: [PATCH 028/316] Fix row deletion and fetching for tables --- packages/frontend-core/src/components/grid/stores/rows.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index 69dd7ae97a..af7c698014 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -312,7 +312,7 @@ export const createActions = context => { // Fetches a row by ID using the search endpoint const fetchRow = async id => { const res = await API.searchTable({ - tableId: get(tableId), + tableId: get(datasource).tableId, limit: 1, query: { equal: { @@ -442,7 +442,7 @@ export const createActions = context => { delete row.__idx }) await API.deleteRows({ - tableId: get(tableId), + tableId: get(datasource).tableId, rows: rowsToDelete, }) From 1aea6fce095b1db31485c3f6569eb70a8c23113a Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 28 Jul 2023 16:02:31 +0100 Subject: [PATCH 029/316] Fix grid import and export for tables --- .../backend/DataTable/buttons/grid/GridExportButton.svelte | 4 ++-- .../backend/DataTable/buttons/grid/GridImportButton.svelte | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridExportButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridExportButton.svelte index b5fe202d11..f1bbc04328 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridExportButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridExportButton.svelte @@ -2,7 +2,7 @@ import ExportButton from "../ExportButton.svelte" import { getContext } from "svelte" - const { rows, columns, tableId, sort, selectedRows, filter } = + const { rows, columns, datasource, sort, selectedRows, filter } = getContext("grid") $: disabled = !$rows.length || !$columns.length @@ -12,7 +12,7 @@ From 8204935dfa9948c17580c25b11a63a71805eaafe Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Sun, 30 Jul 2023 12:49:07 +0100 Subject: [PATCH 030/316] Add ability to update views, create views with existing filters and sorting applied --- .../DataTable/modals/CreateViewModal.svelte | 15 +++++++++++++-- .../src/components/grid/lib/websocket.js | 5 ++++- .../src/components/grid/stores/columns.js | 14 +++++++++++--- packages/frontend-core/src/fetch/ViewV2Fetch.js | 4 +--- packages/server/src/sdk/app/views/index.ts | 1 + 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte index ac5e522923..b5c3fb6e31 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte @@ -1,11 +1,17 @@ @@ -35,7 +37,6 @@ align="right" offset={0} popoverTarget={document.getElementById(`add-column-button`)} - animate={false} customZindex={100} >
Date: Tue, 1 Aug 2023 10:54:01 +0100 Subject: [PATCH 033/316] Disable collab for views --- packages/frontend-core/src/components/grid/lib/websocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend-core/src/components/grid/lib/websocket.js b/packages/frontend-core/src/components/grid/lib/websocket.js index 1c490f74ca..2f9ebf59ad 100644 --- a/packages/frontend-core/src/components/grid/lib/websocket.js +++ b/packages/frontend-core/src/components/grid/lib/websocket.js @@ -7,7 +7,7 @@ export const createGridWebsocket = context => { const socket = createWebsocket("/socket/grid") const connectToDatasource = datasource => { - if (!socket.connected) { + if (!socket.connected || datasource?.type !== "table") { return } // Identify which table we are editing From 9d2b31af54232c03cca73c060c63642b53ba975d Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 1 Aug 2023 11:16:10 +0100 Subject: [PATCH 034/316] Enable creating and updating rows through views --- packages/frontend-core/src/api/viewsV2.js | 43 +++++++++++++++++++ .../grid/overlays/KeyboardManager.svelte | 6 +-- .../src/components/grid/stores/config.js | 2 - .../src/components/grid/stores/rows.js | 43 +++++++++++-------- 4 files changed, 71 insertions(+), 23 deletions(-) diff --git a/packages/frontend-core/src/api/viewsV2.js b/packages/frontend-core/src/api/viewsV2.js index 37bc78ae16..81fcfcf455 100644 --- a/packages/frontend-core/src/api/viewsV2.js +++ b/packages/frontend-core/src/api/viewsV2.js @@ -33,4 +33,47 @@ export const buildViewV2Endpoints = API => ({ delete: async viewId => { return await API.delete({ url: `/api/v2/views/${viewId}` }) }, + /** + * Creates a row from a view + * @param row the row to create + * @param suppressErrors whether or not to suppress error notifications + */ + createRow: async (row, suppressErrors = false) => { + if (!row?._viewId || !row?.tableId) { + return + } + return await API.post({ + url: `/api/v2/views/${row._viewId}/rows`, + body: row, + suppressErrors, + }) + }, + /** + * Updates an existing row through a view + * @param row the row to update + * @param suppressErrors whether or not to suppress error notifications + */ + updateRow: async (row, suppressErrors = false) => { + if (!row?._viewId || !row?.tableId || !row?._id) { + return + } + return await API.patch({ + url: `/api/v2/views/${row._viewId}/rows/${row._id}`, + body: row, + suppressErrors, + }) + }, + /** + * Deletes multiple rows from a table through a view + * @param viewId the table ID to delete the rows from + * @param rows the array of rows to delete + */ + deleteRows: async ({ viewId, rows }) => { + return await API.delete({ + url: `/api/v2/views/${viewId}/rows`, + body: { + rows, + }, + }) + }, }) diff --git a/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte b/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte index 721babd913..4f30c3c7db 100644 --- a/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte +++ b/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte @@ -4,7 +4,7 @@ import { NewRowID } from "../lib/constants" const { - enrichedRows, + rows, focusedCellId, visibleColumns, focusedRow, @@ -142,7 +142,7 @@ // Focuses the first cell in the grid const focusFirstCell = () => { - const firstRow = $enrichedRows[0] + const firstRow = $rows[0] if (!firstRow) { return } @@ -183,7 +183,7 @@ if (!$focusedRow) { return } - const newRow = $enrichedRows[$focusedRow.__idx + delta] + const newRow = $rows[$focusedRow.__idx + delta] if (newRow) { const split = $focusedCellId.split("-") $focusedCellId = `${newRow._id}-${split[1]}` diff --git a/packages/frontend-core/src/components/grid/stores/config.js b/packages/frontend-core/src/components/grid/stores/config.js index 0ed04bf741..9a72e781f7 100644 --- a/packages/frontend-core/src/components/grid/stores/config.js +++ b/packages/frontend-core/src/components/grid/stores/config.js @@ -31,9 +31,7 @@ export const deriveStores = context => { if ($props.datasource?.type === "viewV2") { config.canEditPrimaryDisplay = false config.canEditColumns = false - config.canEditRows = false config.canDeleteRows = false - config.canAddRows = false } // Disable adding rows if we don't have any valid columns diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index 35bacb786a..ac50672e07 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -16,17 +16,13 @@ export const createStores = () => { const error = writable(null) // Generate a lookup map to quick find a row by ID - const rowLookupMap = derived( - rows, - $rows => { - let map = {} - for (let i = 0; i < $rows.length; i++) { - map[$rows[i]._id] = i - } - return map - }, - {} - ) + const rowLookupMap = derived(rows, $rows => { + let map = {} + for (let i = 0; i < $rows.length; i++) { + map[$rows[i]._id] = i + } + return map + }) // Mark loaded as true if we've ever stopped loading let hasStartedLoading = false @@ -47,8 +43,7 @@ export const createStores = () => { ...$rowChangeCache[row._id], __idx: idx, })) - }, - [] + } ) return { @@ -262,13 +257,14 @@ export const createActions = context => { let newRow = { ...row } if ($datasource.type === "table") { newRow.tableId = $datasource.tableId + newRow = await API.saveRow(newRow, SuppressErrors) } else if ($datasource.type === "viewV2") { newRow.tableId = $datasource.tableId newRow._viewId = $datasource.id + newRow = await API.viewV2.createRow(newRow) } else { return } - newRow = await API.saveRow(newRow, SuppressErrors) // Update state if (idx != null) { @@ -362,6 +358,7 @@ export const createActions = context => { const updateRow = async (rowId, changes) => { const $rows = get(rows) const $rowLookupMap = get(rowLookupMap) + const $datasource = get(datasource) const index = $rowLookupMap[rowId] const row = $rows[index] if (index == null || !Object.keys(changes || {}).length) { @@ -395,10 +392,20 @@ export const createActions = context => { ...state, [rowId]: true, })) - const saved = await API.saveRow( - { ...row, ...get(rowChangeCache)[rowId] }, - SuppressErrors - ) + + let saved + if ($datasource.type === "table") { + saved = await API.saveRow( + { ...row, ...get(rowChangeCache)[rowId] }, + SuppressErrors + ) + } else if ($datasource.type === "viewV2") { + saved = await API.viewV2.updateRow( + { ...row, ...get(rowChangeCache)[rowId] }, + SuppressErrors + ) + saved._viewId = $datasource.id + } // Update state after a successful change if (saved?._id) { From 1d21b4260abd6ffe4ce1fc30bf5a3d1364b24f92 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 1 Aug 2023 11:21:22 +0100 Subject: [PATCH 035/316] Enable deleting rows through views --- packages/frontend-core/src/api/viewsV2.js | 4 ++++ .../src/components/grid/stores/config.js | 1 - .../src/components/grid/stores/rows.js | 16 ++++++++++++---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/frontend-core/src/api/viewsV2.js b/packages/frontend-core/src/api/viewsV2.js index 81fcfcf455..f2848b55f3 100644 --- a/packages/frontend-core/src/api/viewsV2.js +++ b/packages/frontend-core/src/api/viewsV2.js @@ -69,6 +69,10 @@ export const buildViewV2Endpoints = API => ({ * @param rows the array of rows to delete */ deleteRows: async ({ viewId, rows }) => { + // Ensure we delete _viewId from rows as otherwise this throws a 500 + rows?.forEach(row => { + delete row?._viewId + }) return await API.delete({ url: `/api/v2/views/${viewId}/rows`, body: { diff --git a/packages/frontend-core/src/components/grid/stores/config.js b/packages/frontend-core/src/components/grid/stores/config.js index 9a72e781f7..a0b1be19da 100644 --- a/packages/frontend-core/src/components/grid/stores/config.js +++ b/packages/frontend-core/src/components/grid/stores/config.js @@ -31,7 +31,6 @@ export const deriveStores = context => { if ($props.datasource?.type === "viewV2") { config.canEditPrimaryDisplay = false config.canEditColumns = false - config.canDeleteRows = false } // Disable adding rows if we don't have any valid columns diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index ac50672e07..ed9f99a3f6 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -440,15 +440,23 @@ export const createActions = context => { if (!rowsToDelete?.length) { return } + const $datasource = get(datasource) // Actually delete rows rowsToDelete.forEach(row => { delete row.__idx }) - await API.deleteRows({ - tableId: get(datasource).tableId, - rows: rowsToDelete, - }) + if ($datasource.type === "table") { + await API.deleteRows({ + tableId: $datasource.tableId, + rows: rowsToDelete, + }) + } else if ($datasource.type === "viewV2") { + await API.viewV2.deleteRows({ + viewId: $datasource.id, + rows: rowsToDelete, + }) + } // Update state handleRemoveRows(rowsToDelete) From cb01768e5a9b3154c0cea6f5f1aa4c3237098014 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 1 Aug 2023 14:18:12 +0100 Subject: [PATCH 036/316] Fix crash when schemaNonUI does not exist --- packages/server/src/api/controllers/view/viewsV2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/api/controllers/view/viewsV2.ts b/packages/server/src/api/controllers/view/viewsV2.ts index cd28a1b55f..e2ac4c83fa 100644 --- a/packages/server/src/api/controllers/view/viewsV2.ts +++ b/packages/server/src/api/controllers/view/viewsV2.ts @@ -18,7 +18,7 @@ async function parseSchemaUI(ctx: Ctx, view: CreateViewRequest) { newObj: Record, existingObj: Record ) { - const result = Object.entries(newObj).some(([key, value]) => { + const result = Object.entries(newObj || {}).some(([key, value]) => { const isObject = typeof value === "object" const existing = existingObj[key] if (isObject && hasOverrides(value, existing || {})) { From bb810e14d73b5a2c1357bf8948935af119070c7e Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 1 Aug 2023 14:24:06 +0100 Subject: [PATCH 037/316] Fix issue with order 0 being ignored --- packages/server/src/sdk/app/views/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index 7fa5165220..e9f678371c 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -77,13 +77,13 @@ export function enrichSchema(view: View | ViewV2, tableSchema: TableSchema) { let schema = { ...tableSchema } if (view.schemaUI) { const viewOverridesEntries = Object.entries(view.schemaUI) - const viewSetsOrder = viewOverridesEntries.some(([_, v]) => v.order) + const viewSetsOrder = viewOverridesEntries.some(([_, v]) => v.order != null) for (const [fieldName, schemaUI] of viewOverridesEntries) { schema[fieldName] = { ...schema[fieldName], ...schemaUI, order: viewSetsOrder - ? schemaUI.order || undefined + ? schemaUI.order ?? undefined : schema[fieldName].order, } } From d825c32fdfbe5933a71c412066a2596e6c92f409 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 1 Aug 2023 14:25:16 +0100 Subject: [PATCH 038/316] Hide access button for now --- .../src/components/backend/DataTable/ViewV2DataTable.svelte | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte index 028030bb9a..21b3e1c291 100644 --- a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte @@ -4,7 +4,6 @@ import { API } from "api" import GridCreateEditRowModal from "components/backend/DataTable/modals/grid/GridCreateEditRowModal.svelte" import GridFilterButton from "components/backend/DataTable/buttons/grid/GridFilterButton.svelte" - import GridManageAccessButton from "components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte" $: id = $viewsV2.selected?.id $: datasource = { @@ -31,7 +30,6 @@ - From 3482ec3d9e238f0bf13851f51dc7bda2b54ffbe9 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 1 Aug 2023 15:34:02 +0100 Subject: [PATCH 039/316] Add feature flags to view V2 fetch --- .../src/components/grid/stores/rows.js | 33 +++++++++++++++---- .../frontend-core/src/fetch/ViewV2Fetch.js | 8 +++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index ed9f99a3f6..3c33d92d25 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -169,6 +169,8 @@ export const createActions = context => { scroll.update(state => ({ ...state, top: 0 })) } + // For views we always update the filter to match the definition + // Process new rows handleNewRows($fetch.rows, resetRows) @@ -182,15 +184,19 @@ export const createActions = context => { // Update fetch when filter or sort config changes filter.subscribe($filter => { - get(fetch)?.update({ - filter: $filter, - }) + if (get(datasource)?.type === "table") { + get(fetch)?.update({ + filter: $filter, + }) + } }) sort.subscribe($sort => { - get(fetch)?.update({ - sortOrder: $sort.order, - sortColumn: $sort.column, - }) + if (get(datasource)?.type === "table") { + get(fetch)?.update({ + sortOrder: $sort.order, + sortColumn: $sort.column, + }) + } }) // Gets a row by ID @@ -549,3 +555,16 @@ export const createActions = context => { }, } } + +export const initialise = context => { + const { table, filter, datasource } = context + + // For views, always keep the UI for filter and sorting up to date with the + // latest view definition + table.subscribe($definition => { + if (!$definition || get(datasource)?.type !== "viewV2") { + return + } + filter.set($definition.query) + }) +} diff --git a/packages/frontend-core/src/fetch/ViewV2Fetch.js b/packages/frontend-core/src/fetch/ViewV2Fetch.js index 3713aab877..9bc61f0383 100644 --- a/packages/frontend-core/src/fetch/ViewV2Fetch.js +++ b/packages/frontend-core/src/fetch/ViewV2Fetch.js @@ -1,6 +1,14 @@ import DataFetch from "./DataFetch.js" export default class ViewV2Fetch extends DataFetch { + determineFeatureFlags() { + return { + supportsSearch: true, + supportsSort: true, + supportsPagination: true, + } + } + async getSchema(datasource, definition) { return definition?.schema } From ab47e49dd946c5e85ea4e7fc050ad8b6b40dffdf Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 2 Aug 2023 15:27:34 +0100 Subject: [PATCH 040/316] Update create view modal to only depend on grid context --- .../backend/DataTable/modals/CreateViewModal.svelte | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte index b5c3fb6e31..afb9efa53f 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte @@ -3,17 +3,16 @@ import { Input, notifications, ModalContent } from "@budibase/bbui" import { goto } from "@roxi/routify" import { viewsV2 } from "stores/backend" - import { tables } from "stores/backend" import { LuceneUtils } from "@budibase/frontend-core" const { filter, sort, table } = getContext("grid") $: query = LuceneUtils.buildLuceneQuery($filter) - $: console.log($table.schema) let name - $: views = Object.keys($tables.selected?.views || {}) + $: console.log($table) + $: views = Object.keys($table?.views || {}) $: nameExists = views.includes(name?.trim()) const saveView = async () => { @@ -21,17 +20,19 @@ try { const newView = await viewsV2.create({ name, - tableId: $tables.selected._id, + tableId: $table._id, query, sort: { field: $sort.column, order: $sort.order, }, - columns: $table.schema, + schema: $table.schema, + primaryDisplay: $table.primaryDisplay, }) notifications.success(`View ${name} created`) $goto(`../../view/v2/${newView.id}`) } catch (error) { + console.log(error) notifications.error("Error creating view") } } From cb2a19620bea0c00cc649506c390c59dac55abb6 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 3 Aug 2023 09:29:12 +0100 Subject: [PATCH 041/316] Initial commit --- .../src/DetailSummary/DetailSummary.svelte | 4 +- .../src/builderStore/store/frontend.js | 89 ++++++---- .../EditFieldPopover.svelte | 161 ++++++++++++++++++ .../FieldConfiguration.svelte | 155 +++++++++++++---- .../settings/controls/SettingsList.svelte | 133 +++++++++++++++ .../settings/ComponentSettingsSection.svelte | 36 ++-- packages/client/manifest.json | 110 ++++++------ .../app/blocks/form/FormBlock.svelte | 30 +++- .../app/blocks/form/InnerFormBlock.svelte | 34 ++-- 9 files changed, 591 insertions(+), 161 deletions(-) create mode 100644 packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte create mode 100644 packages/builder/src/components/design/settings/controls/SettingsList.svelte diff --git a/packages/bbui/src/DetailSummary/DetailSummary.svelte b/packages/bbui/src/DetailSummary/DetailSummary.svelte index f7e2611792..daa9f3f5ca 100644 --- a/packages/bbui/src/DetailSummary/DetailSummary.svelte +++ b/packages/bbui/src/DetailSummary/DetailSummary.svelte @@ -44,7 +44,9 @@ align-items: stretch; border-bottom: var(--border-light); } - + .property-group-container:last-child { + border-bottom: 0px; + } .property-group-name { cursor: pointer; display: flex; diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index f312a58e97..4df26182e6 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -93,6 +93,40 @@ const INITIAL_FRONTEND_STATE = { tourNodes: null, } +export const updateComponentSetting = (name, value) => { + return component => { + if (!name || !component) { + return false + } + // Skip update if the value is the same + if (component[name] === value) { + return false + } + + const settings = getComponentSettings(component._component) + const updatedSetting = settings.find(setting => setting.key === name) + + if ( + updatedSetting?.type === "dataSource" || + updatedSetting?.type === "table" + ) { + const { schema } = getSchemaForDatasource(null, value) + const columnNames = Object.keys(schema || {}) + const multifieldKeysToSelectAll = settings + .filter(setting => { + return setting.type === "multifield" && setting.selectAllFields + }) + .map(setting => setting.key) + + multifieldKeysToSelectAll.forEach(key => { + component[key] = columnNames + }) + } + + component[name] = value + } +} + export const getFrontendStore = () => { const store = writable({ ...INITIAL_FRONTEND_STATE }) let websocket @@ -111,13 +145,18 @@ export const getFrontendStore = () => { } let clone = cloneDeep(screen) const result = patchFn(clone) + console.log("sequentialScreenPatch ", result) if (result === false) { return } - return await store.actions.screens.save(clone) + return + //return await store.actions.screens.save(clone) }) store.actions = { + tester: (name, value) => { + return updateComponentSetting(name, value) + }, reset: () => { store.set({ ...INITIAL_FRONTEND_STATE }) websocket?.disconnect() @@ -825,6 +864,7 @@ export const getFrontendStore = () => { }, patch: async (patchFn, componentId, screenId) => { // Use selected component by default + console.log("front end patch") if (!componentId || !screenId) { const state = get(store) componentId = componentId || state.selectedComponentId @@ -834,6 +874,7 @@ export const getFrontendStore = () => { return } const patchScreen = screen => { + // findComponent looks in the tree not comp.settings[0] let component = findComponent(screen.props, componentId) if (!component) { return false @@ -842,6 +883,18 @@ export const getFrontendStore = () => { } await store.actions.screens.patch(patchScreen, screenId) }, + // Temporary + customPatch: async (patchFn, componentId, screenId) => { + console.log("patchUpdate :") + if (!componentId || !screenId) { + const state = get(store) + componentId = componentId || state.selectedComponentId + screenId = screenId || state.selectedScreenId + } + if (!componentId || !screenId || !patchFn) { + return + } + }, delete: async component => { if (!component) { return @@ -1207,37 +1260,9 @@ export const getFrontendStore = () => { }) }, updateSetting: async (name, value) => { - await store.actions.components.patch(component => { - if (!name || !component) { - return false - } - // Skip update if the value is the same - if (component[name] === value) { - return false - } - - const settings = getComponentSettings(component._component) - const updatedSetting = settings.find(setting => setting.key === name) - - if ( - updatedSetting?.type === "dataSource" || - updatedSetting?.type === "table" - ) { - const { schema } = getSchemaForDatasource(null, value) - const columnNames = Object.keys(schema || {}) - const multifieldKeysToSelectAll = settings - .filter(setting => { - return setting.type === "multifield" && setting.selectAllFields - }) - .map(setting => setting.key) - - multifieldKeysToSelectAll.forEach(key => { - component[key] = columnNames - }) - } - - component[name] = value - }) + await store.actions.components.patch( + updateComponentSetting(name, value) + ) }, requestEjectBlock: componentId => { store.actions.preview.sendEvent("eject-block", componentId) diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte new file mode 100644 index 0000000000..859f019a26 --- /dev/null +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte @@ -0,0 +1,161 @@ + + + + { + popover.show() + }} +/> + + + + + + + + diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte index f9dccf586c..922b994c5a 100644 --- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte @@ -1,45 +1,125 @@
- {text} +
- - - Configure the fields in your form. - - - - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsSection.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsSection.svelte index 78eecea76e..72383fcf3c 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsSection.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsSection.svelte @@ -13,11 +13,16 @@ export let bindings export let componentBindings export let isScreen = false + export let onUpdateSetting $: sections = getSections(componentInstance, componentDefinition, isScreen) const getSections = (instance, definition, isScreen) => { const settings = definition?.settings ?? [] + console.log( + "ComponentSettingsSection::definition?.settings", + definition?.settings + ) const generalSettings = settings.filter(setting => !setting.section) const customSections = settings.filter(setting => setting.section) let sections = [ @@ -46,19 +51,23 @@ } const updateSetting = async (setting, value) => { - try { - await store.actions.components.updateSetting(setting.key, value) + if (typeof onUpdateSetting === "function") { + onUpdateSetting(setting, value) + } else { + try { + await store.actions.components.updateSetting(setting.key, value) - // Send event if required - if (setting.sendEvents) { - analytics.captureEvent(Events.COMPONENT_UPDATED, { - name: componentInstance._component, - setting: setting.key, - value, - }) + // Send event if required + if (setting.sendEvents) { + analytics.captureEvent(Events.COMPONENT_UPDATED, { + name: componentInstance._component, + setting: setting.key, + value, + }) + } + } catch (error) { + notifications.error("Error updating component prop") } - } catch (error) { - notifications.error("Error updating component prop") } } @@ -129,10 +138,13 @@ {/if} {#each section.settings as setting (setting.key)} {#if setting.visible} + { - if (typeof fields?.[0] === "string") { - return fields.map(field => ({ name: field, displayName: field })) - } - - return fields + return typeof fields?.[0] === "string" + ? fields.map(field => ({ + name: field, + displayName: field, + active: true, + })) + : fields } + //All settings need to derive from the block config now + + // Parse the fields here too. Not present means false. const getDefaultFields = (fields, schema) => { + let formFields if (schema && (!fields || fields.length === 0)) { const defaultFields = [] @@ -41,13 +47,20 @@ defaultFields.push({ name: field.name, displayName: field.name, + active: true, }) }) - - return defaultFields + formFields = [...defaultFields] + } else { + formFields = (fields || []).map(field => { + return { + ...field, + active: typeof field?.active != "boolean" ? true : field?.active, + } + }) } - return fields + return formFields.filter(field => field.active) } let schema @@ -56,7 +69,6 @@ $: formattedFields = convertOldFieldFormat(fields) $: fieldsOrDefault = getDefaultFields(formattedFields, schema) - $: fetchSchema(dataSource) $: dataProvider = `{{ literal ${safe(providerId)} }}` $: filter = [ diff --git a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte index 5e4a156949..b2870b02bf 100644 --- a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte +++ b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte @@ -197,22 +197,24 @@ {/if} {/if} - - {#each fields as field, idx} - {#if getComponentForField(field.name)} - - {/if} - {/each} - + {#key fields} + + {#each fields as field, idx} + {#if getComponentForField(field.name)} + + {/if} + {/each} + + {/key} {:else} From e3cf0667be65faf53ba72bcce1b5f288d47b27a5 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 3 Aug 2023 11:18:19 +0100 Subject: [PATCH 042/316] Refactor grid to split up stores and provide better separation of datasource-specific logic --- .../backend/DataTable/TableDataTable.svelte | 2 +- .../buttons/grid/GridFilterButton.svelte | 10 +- .../buttons/grid/GridImportButton.svelte | 6 +- .../grid/GridManageAccessButton.svelte | 4 +- .../grid/GridRelationshipButton.svelte | 6 +- .../modals/grid/GridCreateColumnModal.svelte | 4 +- .../grid/controls/SizeButton.svelte | 14 ++- .../src/components/grid/index.js | 1 + .../src/components/grid/lib/constants.js | 4 + .../src/components/grid/stores/columns.js | 96 +++------------- .../src/components/grid/stores/config.js | 4 +- .../src/components/grid/stores/datsource.js | 107 ++++++++++++++++++ .../src/components/grid/stores/index.js | 9 ++ .../src/components/grid/stores/rows.js | 73 +++--------- .../src/components/grid/stores/sort.js | 3 +- .../src/components/grid/stores/table.js | 24 ++++ .../src/components/grid/stores/ui.js | 8 +- .../src/components/grid/stores/viewV2.js | 43 +++++++ packages/frontend-core/src/fetch/DataFetch.js | 10 +- 19 files changed, 262 insertions(+), 166 deletions(-) create mode 100644 packages/frontend-core/src/components/grid/stores/datsource.js create mode 100644 packages/frontend-core/src/components/grid/stores/table.js create mode 100644 packages/frontend-core/src/components/grid/stores/viewV2.js diff --git a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte index 33175b336d..109c965271 100644 --- a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte @@ -61,7 +61,7 @@ allowDeleteRows={!isUsersTable} schemaOverrides={isUsersTable ? userSchemaOverrides : null} showAvatars={false} - on:updatetable={handleGridTableUpdate} + on:updatedatasource={handleGridTableUpdate} > diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridFilterButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridFilterButton.svelte index 45e5d4e2b0..ae4d39483e 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridFilterButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridFilterButton.svelte @@ -2,22 +2,22 @@ import TableFilterButton from "../TableFilterButton.svelte" import { getContext } from "svelte" - const { columns, tableId, filter, table } = getContext("grid") + const { columns, datasource, filter, definition } = getContext("grid") // Wipe filter whenever table ID changes to avoid using stale filters - $: $tableId, filter.set([]) + $: $datasource, filter.set([]) const onFilter = e => { filter.set(e.detail || []) } -{#key $tableId} +{#key $datasource} {/key} diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridImportButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridImportButton.svelte index 667cf5e89a..71d971891c 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridImportButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridImportButton.svelte @@ -4,12 +4,12 @@ export let disabled = false - const { rows, datasource, table } = getContext("grid") + const { rows, datasource, definition } = getContext("grid") diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte index 154007950a..cc15ae564e 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte @@ -2,7 +2,7 @@ import ManageAccessButton from "../ManageAccessButton.svelte" import { getContext } from "svelte" - const { tableId } = getContext("grid") + const { datasource } = getContext("grid") - + diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridRelationshipButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridRelationshipButton.svelte index 460391366f..baa7dbed14 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridRelationshipButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridRelationshipButton.svelte @@ -2,12 +2,12 @@ import ExistingRelationshipButton from "../ExistingRelationshipButton.svelte" import { getContext } from "svelte" - const { table, rows } = getContext("grid") + const { definition, rows } = getContext("grid") -{#if $table} +{#if $definition} rows.actions.refreshData()} /> {/if} diff --git a/packages/builder/src/components/backend/DataTable/modals/grid/GridCreateColumnModal.svelte b/packages/builder/src/components/backend/DataTable/modals/grid/GridCreateColumnModal.svelte index 56ce7ebc2b..2040f66706 100644 --- a/packages/builder/src/components/backend/DataTable/modals/grid/GridCreateColumnModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/grid/GridCreateColumnModal.svelte @@ -2,7 +2,7 @@ import { getContext } from "svelte" import CreateEditColumn from "components/backend/DataTable/modals/CreateEditColumn.svelte" - const { rows } = getContext("grid") + const { datasource } = getContext("grid") - + diff --git a/packages/frontend-core/src/components/grid/controls/SizeButton.svelte b/packages/frontend-core/src/components/grid/controls/SizeButton.svelte index 22e0c6c2e9..c2797ce537 100644 --- a/packages/frontend-core/src/components/grid/controls/SizeButton.svelte +++ b/packages/frontend-core/src/components/grid/controls/SizeButton.svelte @@ -8,8 +8,14 @@ SmallRowHeight, } from "../lib/constants" - const { stickyColumn, columns, rowHeight, table, fixedRowHeight } = - getContext("grid") + const { + stickyColumn, + columns, + rowHeight, + definition, + fixedRowHeight, + datasource, + } = getContext("grid") // Some constants for column width options const smallColSize = 120 @@ -60,8 +66,8 @@ ] const changeRowHeight = height => { - columns.actions.saveTable({ - ...$table, + datasource.actions.saveDefinition({ + ...$definition, rowHeight: height, }) } diff --git a/packages/frontend-core/src/components/grid/index.js b/packages/frontend-core/src/components/grid/index.js index 25747ec142..453e12967f 100644 --- a/packages/frontend-core/src/components/grid/index.js +++ b/packages/frontend-core/src/components/grid/index.js @@ -1 +1,2 @@ export { default as Grid } from "./layout/Grid.svelte" +export { DatasourceType } from "./lib/constants" diff --git a/packages/frontend-core/src/components/grid/lib/constants.js b/packages/frontend-core/src/components/grid/lib/constants.js index a6e6723463..de9fa6591c 100644 --- a/packages/frontend-core/src/components/grid/lib/constants.js +++ b/packages/frontend-core/src/components/grid/lib/constants.js @@ -1,3 +1,7 @@ +export const DatasourceType = { + Table: "table", + ViewV2: "viewV2", +} export const Padding = 246 export const MaxCellRenderHeight = 222 export const ScrollBarSize = 8 diff --git a/packages/frontend-core/src/components/grid/stores/columns.js b/packages/frontend-core/src/components/grid/stores/columns.js index 6573bc3937..8059bd98d7 100644 --- a/packages/frontend-core/src/components/grid/stores/columns.js +++ b/packages/frontend-core/src/components/grid/stores/columns.js @@ -69,8 +69,7 @@ export const deriveStores = context => { } export const createActions = context => { - const { table, columns, stickyColumn, API, dispatch, config, datasource } = - context + const { columns, stickyColumn, config, datasource, definition } = context // Checks if we have a certain column by name const hasColumn = column => { @@ -79,13 +78,13 @@ export const createActions = context => { return $columns.some(col => col.name === column) || $sticky?.name === column } - // Updates the tables primary display column + // Updates the datasources primary display column const changePrimaryDisplay = async column => { if (!get(config).canEditPrimaryDisplay) { return } - return await saveTable({ - ...get(table), + return await datasource.actions.saveDefinition({ + ...get(definition), primaryDisplay: column, }) } @@ -107,14 +106,14 @@ export const createActions = context => { await saveChanges() } - // Persists column changes by saving metadata against table schema + // Persists column changes by saving metadata against datasource schema const saveChanges = async () => { const $columns = get(columns) - const $table = get(table) + const $definition = get(definition) const $stickyColumn = get(stickyColumn) - const newSchema = cloneDeep($table.schema) + const newSchema = cloneDeep($definition.schema) - // Build new updated table schema + // Build new updated datasource schema Object.keys(newSchema).forEach(column => { // Respect order specified by columns const index = $columns.findIndex(x => x.name === column) @@ -134,28 +133,10 @@ export const createActions = context => { } }) - await saveTable({ ...$table, schema: newSchema }) - } - - const saveTable = async newTable => { - const $config = get(config) - const $datasource = get(datasource) - - // Update local state - table.set(newTable) - - // Update server - if ($config.canSaveSchema) { - if ($datasource.type === "table") { - await API.saveTable(newTable) - } else if ($datasource.type === "viewV2") { - await API.viewV2.update({ ...newTable }) - } - } - - // Broadcast change to external state can be updated, as this change - // will not be received by the builder websocket because we caused it ourselves - dispatch("updatetable", newTable) + await datasource.actions.saveDefinition({ + ...$definition, + schema: newSchema, + }) } return { @@ -164,7 +145,6 @@ export const createActions = context => { actions: { hasColumn, saveChanges, - saveTable, changePrimaryDisplay, changeAllColumnWidths, }, @@ -173,51 +153,7 @@ export const createActions = context => { } export const initialise = context => { - const { table, columns, stickyColumn, schemaOverrides, columnWhitelist } = - context - - const schema = derived( - [table, schemaOverrides, columnWhitelist], - ([$table, $schemaOverrides, $columnWhitelist]) => { - if (!$table?.schema) { - return null - } - let newSchema = { ...$table?.schema } - - // Edge case to temporarily allow deletion of duplicated user - // fields that were saved with the "disabled" flag set. - // By overriding the saved schema we ensure only overrides can - // set the disabled flag. - // TODO: remove in future - Object.keys(newSchema).forEach(field => { - newSchema[field] = { - ...newSchema[field], - disabled: false, - } - }) - - // Apply schema overrides - Object.keys($schemaOverrides || {}).forEach(field => { - if (newSchema[field]) { - newSchema[field] = { - ...newSchema[field], - ...$schemaOverrides[field], - } - } - }) - - // Apply whitelist if specified - if ($columnWhitelist?.length) { - Object.keys(newSchema).forEach(key => { - if (!$columnWhitelist.includes(key)) { - delete newSchema[key] - } - }) - } - - return newSchema - } - ) + const { definition, columns, stickyColumn, schema } = context // Merge new schema fields with existing schema in order to preserve widths schema.subscribe($schema => { @@ -226,12 +162,12 @@ export const initialise = context => { stickyColumn.set(null) return } - const $table = get(table) + const $definition = get(definition) // Find primary display let primaryDisplay - if ($table.primaryDisplay && $schema[$table.primaryDisplay]) { - primaryDisplay = $table.primaryDisplay + if ($definition.primaryDisplay && $schema[$definition.primaryDisplay]) { + primaryDisplay = $definition.primaryDisplay } // Get field list diff --git a/packages/frontend-core/src/components/grid/stores/config.js b/packages/frontend-core/src/components/grid/stores/config.js index a0b1be19da..de81a32ff5 100644 --- a/packages/frontend-core/src/components/grid/stores/config.js +++ b/packages/frontend-core/src/components/grid/stores/config.js @@ -1,5 +1,6 @@ import { derivedMemo } from "../../../utils" import { derived } from "svelte/store" +import { DatasourceType } from "../lib/constants" export const deriveStores = context => { const { props, hasNonAutoColumn } = context @@ -28,8 +29,7 @@ export const deriveStores = context => { } // Disable some features if we're editing a view - if ($props.datasource?.type === "viewV2") { - config.canEditPrimaryDisplay = false + if ($props.datasource?.type === DatasourceType.ViewV2) { config.canEditColumns = false } diff --git a/packages/frontend-core/src/components/grid/stores/datsource.js b/packages/frontend-core/src/components/grid/stores/datsource.js new file mode 100644 index 0000000000..a0f6e36811 --- /dev/null +++ b/packages/frontend-core/src/components/grid/stores/datsource.js @@ -0,0 +1,107 @@ +import { derived, get, writable } from "svelte/store" + +export const createStores = () => { + const definition = writable(null) + + return { + definition, + } +} + +export const deriveStores = context => { + const { definition, schemaOverrides, columnWhitelist } = context + + const schema = derived( + [definition, schemaOverrides, columnWhitelist], + ([$definition, $schemaOverrides, $columnWhitelist]) => { + if (!$definition?.schema) { + return null + } + let newSchema = { ...$definition?.schema } + + // Edge case to temporarily allow deletion of duplicated user + // fields that were saved with the "disabled" flag set. + // By overriding the saved schema we ensure only overrides can + // set the disabled flag. + // TODO: remove in future + Object.keys(newSchema).forEach(field => { + newSchema[field] = { + ...newSchema[field], + disabled: false, + } + }) + + // Apply schema overrides + Object.keys($schemaOverrides || {}).forEach(field => { + if (newSchema[field]) { + newSchema[field] = { + ...newSchema[field], + ...$schemaOverrides[field], + } + } + }) + + // Apply whitelist if specified + if ($columnWhitelist?.length) { + Object.keys(newSchema).forEach(key => { + if (!$columnWhitelist.includes(key)) { + delete newSchema[key] + } + }) + } + + return newSchema + } + ) + + return { + schema, + } +} + +export const createActions = context => { + const { datasource, definition, API, config, dispatch } = context + + // Refreshes the datasource definition + const refreshDefinition = async () => { + const $datasource = get(datasource) + if ($datasource.type === "table") { + definition.set(await API.fetchTableDefinition($datasource.tableId)) + } else if ($datasource.type === "viewV2") { + // const definition = await API.viewsV2.(get(tableId)) + // table.set(definition) + } + } + + // Saves the datasource definition + const saveDefinition = async newDefinition => { + const $config = get(config) + const $datasource = get(datasource) + + // Update local state + definition.set(newDefinition) + + // Update server + if ($config.canSaveSchema) { + if ($datasource.type === "table") { + await API.saveTable(newDefinition) + } else if ($datasource.type === "viewV2") { + await API.viewV2.update({ ...newDefinition }) + } + } + + // Broadcast change to external state can be updated, as this change + // will not be received by the builder websocket because we caused it ourselves + dispatch("updatedefinition", newDefinition) + } + + return { + datasource: { + ...datasource, + actions: { + refreshDefinition, + saveDefinition, + }, + }, + } +} diff --git a/packages/frontend-core/src/components/grid/stores/index.js b/packages/frontend-core/src/components/grid/stores/index.js index d287025342..70a9471991 100644 --- a/packages/frontend-core/src/components/grid/stores/index.js +++ b/packages/frontend-core/src/components/grid/stores/index.js @@ -15,13 +15,18 @@ import * as Config from "./config" import * as Sort from "./sort" import * as Filter from "./filter" import * as Notifications from "./notifications" +import * as Table from "./table" +import * as ViewV2 from "./viewV2" +import * as Datasource from "./datsource" const DependencyOrderedStores = [ + // Common stores Notifications, Sort, Filter, Bounds, Scroll, + Datasource, Columns, Rows, UI, @@ -34,6 +39,10 @@ const DependencyOrderedStores = [ Pagination, Clipboard, Config, + + // Datasource specific stores + Table, + ViewV2, ] export const attachStores = context => { diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index 3c33d92d25..dce4fca64b 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -7,13 +7,13 @@ const SuppressErrors = true export const createStores = () => { const rows = writable([]) - const table = writable(null) const loading = writable(false) const loaded = writable(false) const rowChangeCache = writable({}) const inProgressChanges = writable({}) const hasNextPage = writable(false) const error = writable(null) + const fetch = writable(null) // Generate a lookup map to quick find a row by ID const rowLookupMap = derived(rows, $rows => { @@ -51,8 +51,8 @@ export const createStores = () => { ...rows, subscribe: enrichedRows.subscribe, }, + fetch, rowLookupMap, - table, loaded, loading, rowChangeCache, @@ -66,7 +66,7 @@ export const createActions = context => { const { rows, rowLookupMap, - table, + definition, filter, loading, sort, @@ -82,14 +82,14 @@ export const createActions = context => { hasNextPage, error, notifications, + fetch, } = context const instanceLoaded = writable(false) - const fetch = writable(null) // Local cache of row IDs to speed up checking if a row exists let rowCacheMap = {} - // Reset everything when table ID changes + // Reset everything when datasource changes let unsubscribe = null let lastResetKey = null datasource.subscribe(async $datasource => { @@ -100,11 +100,11 @@ export const createActions = context => { loading.set(true) // Abandon if we don't have a valid datasource - if (!$datasource?.tableId) { + if (!$datasource) { return } - // Tick to allow other reactive logic to update stores when table ID changes + // Tick to allow other reactive logic to update stores when datasource changes // before proceeding. This allows us to wipe filters etc if needed. await tick() const $filter = get(filter) @@ -142,7 +142,7 @@ export const createActions = context => { const previousResetKey = lastResetKey lastResetKey = $fetch.resetKey - // If resetting rows due to a table change, wipe data and wait for + // If resetting rows due to a datasource change, wipe data and wait for // derived stores to compute. This prevents stale data being passed // to cells when we save the new schema. if (!$instanceLoaded && previousResetKey) { @@ -152,16 +152,17 @@ export const createActions = context => { // Reset state properties when dataset changes if (!$instanceLoaded || resetRows) { - table.set($fetch.definition) - sort.set({ - column: $fetch.sortColumn, - order: $fetch.sortOrder, - }) + definition.set($fetch.definition) + + // sort.set({ + // column: $fetch.sortColumn, + // order: $fetch.sortOrder, + // }) } // Reset scroll state when data changes if (!$instanceLoaded) { - // Reset both top and left for a new table ID + // Reset both top and left for a new datasource ID instanceLoaded.set(true) scroll.set({ top: 0, left: 0 }) } else if (resetRows) { @@ -169,8 +170,6 @@ export const createActions = context => { scroll.update(state => ({ ...state, top: 0 })) } - // For views we always update the filter to match the definition - // Process new rows handleNewRows($fetch.rows, resetRows) @@ -182,23 +181,6 @@ export const createActions = context => { fetch.set(newFetch) }) - // Update fetch when filter or sort config changes - filter.subscribe($filter => { - if (get(datasource)?.type === "table") { - get(fetch)?.update({ - filter: $filter, - }) - } - }) - sort.subscribe($sort => { - if (get(datasource)?.type === "table") { - get(fetch)?.update({ - sortOrder: $sort.order, - sortColumn: $sort.column, - }) - } - }) - // Gets a row by ID const getRow = id => { const index = get(rowLookupMap)[id] @@ -506,17 +488,6 @@ export const createActions = context => { get(fetch)?.nextPage() } - // Refreshes the schema of the data fetch subscription - const refreshDatasourceDefinition = async () => { - const $datasource = get(datasource) - if ($datasource.type === "table") { - table.set(await API.fetchTableDefinition($datasource.tableId)) - } else if ($datasource.type === "viewV2") { - // const definition = await API.viewsV2.(get(tableId)) - // table.set(definition) - } - } - // Checks if we have a row with a certain ID const hasRow = id => { if (id === NewRowID) { @@ -550,21 +521,7 @@ export const createActions = context => { refreshRow, replaceRow, refreshData, - refreshDatasourceDefinition, }, }, } } - -export const initialise = context => { - const { table, filter, datasource } = context - - // For views, always keep the UI for filter and sorting up to date with the - // latest view definition - table.subscribe($definition => { - if (!$definition || get(datasource)?.type !== "viewV2") { - return - } - filter.set($definition.query) - }) -} diff --git a/packages/frontend-core/src/components/grid/stores/sort.js b/packages/frontend-core/src/components/grid/stores/sort.js index 3beecd2c2a..098e126930 100644 --- a/packages/frontend-core/src/components/grid/stores/sort.js +++ b/packages/frontend-core/src/components/grid/stores/sort.js @@ -16,7 +16,8 @@ export const createStores = context => { } export const initialise = context => { - const { sort, initialSortColumn, initialSortOrder } = context + const { sort, initialSortColumn, initialSortOrder, table, datasource } = + context // Reset sort when initial sort props change initialSortColumn.subscribe(newSortColumn => { diff --git a/packages/frontend-core/src/components/grid/stores/table.js b/packages/frontend-core/src/components/grid/stores/table.js new file mode 100644 index 0000000000..bcac6df48f --- /dev/null +++ b/packages/frontend-core/src/components/grid/stores/table.js @@ -0,0 +1,24 @@ +import { get } from "svelte/store" + +export const initialise = context => { + const { datasource, fetch, filter, sort } = context + + // Update fetch when filter changes + filter.subscribe($filter => { + if (get(datasource)?.type === "table") { + get(fetch)?.update({ + filter: $filter, + }) + } + }) + + // Update fetch when sorting changes + sort.subscribe($sort => { + if (get(datasource)?.type === "table") { + get(fetch)?.update({ + sortOrder: $sort.order, + sortColumn: $sort.column, + }) + } + }) +} diff --git a/packages/frontend-core/src/components/grid/stores/ui.js b/packages/frontend-core/src/components/grid/stores/ui.js index e0c6fd4c9c..e10e96a722 100644 --- a/packages/frontend-core/src/components/grid/stores/ui.js +++ b/packages/frontend-core/src/components/grid/stores/ui.js @@ -131,7 +131,7 @@ export const initialise = context => { focusedCellId, selectedRows, hoveredRowId, - table, + definition, rowHeight, fixedRowHeight, } = context @@ -187,9 +187,9 @@ export const initialise = context => { }) // Pull row height from table as long as we don't have a fixed height - table.subscribe($table => { + definition.subscribe($definition => { if (!get(fixedRowHeight)) { - rowHeight.set($table?.rowHeight || DefaultRowHeight) + rowHeight.set($definition?.rowHeight || DefaultRowHeight) } }) @@ -198,7 +198,7 @@ export const initialise = context => { if (height) { rowHeight.set(height) } else { - rowHeight.set(get(table)?.rowHeight || DefaultRowHeight) + rowHeight.set(get(definition)?.rowHeight || DefaultRowHeight) } }) } diff --git a/packages/frontend-core/src/components/grid/stores/viewV2.js b/packages/frontend-core/src/components/grid/stores/viewV2.js new file mode 100644 index 0000000000..bf47513908 --- /dev/null +++ b/packages/frontend-core/src/components/grid/stores/viewV2.js @@ -0,0 +1,43 @@ +import { get } from "svelte/store" + +export const initialise = context => { + const { definition, datasource, sort, rows } = context + + // For views, keep sort state in line with the view definition + definition.subscribe($definition => { + if (!$definition || get(datasource)?.type !== "viewV2") { + return + } + const $sort = get(sort) + if ( + $definition.sort?.field !== $sort?.column || + $definition.sort?.order !== $sort?.order + ) { + sort.set({ + column: $definition.sort?.field, + order: $definition.sort?.order, + }) + } + }) + + // When sorting changes, ensure view definition is kept up to date + sort.subscribe(async $sort => { + const $view = get(definition) + if (!$view || get(datasource)?.type !== "viewV2") { + return + } + if ( + $sort?.column !== $view.sort?.field || + $sort?.order !== $view.sort?.order + ) { + await datasource.actions.saveDefinition({ + ...$view, + sort: { + field: $sort.column, + order: $sort.order, + }, + }) + await rows.actions.refreshData() + } + }) +} diff --git a/packages/frontend-core/src/fetch/DataFetch.js b/packages/frontend-core/src/fetch/DataFetch.js index ea1cfdde77..a9803747ae 100644 --- a/packages/frontend-core/src/fetch/DataFetch.js +++ b/packages/frontend-core/src/fetch/DataFetch.js @@ -116,8 +116,16 @@ export default class DataFetch { async getInitialData() { const { datasource, filter, paginate } = this.options - // Fetch datasource definition and determine feature flags + // Fetch datasource definition and extract filter and sort if configured const definition = await this.getDefinition(datasource) + if (definition?.sort?.field) { + this.options.sortColumn = definition.sort.field + } + if (definition?.sort?.order) { + this.options.sortOrder = definition.sort.order + } + + // Determine feature flags const features = this.determineFeatureFlags(definition) this.features = { supportsSearch: !!features?.supportsSearch, From 3e97e299bf4702bcbd63918cdd13d28308582aaf Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 3 Aug 2023 11:27:33 +0100 Subject: [PATCH 043/316] Fix sorting for tables --- .../src/components/grid/stores/{datsource.js => datasource.js} | 2 +- packages/frontend-core/src/components/grid/stores/index.js | 2 +- packages/frontend-core/src/components/grid/stores/table.js | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) rename packages/frontend-core/src/components/grid/stores/{datsource.js => datasource.js} (98%) diff --git a/packages/frontend-core/src/components/grid/stores/datsource.js b/packages/frontend-core/src/components/grid/stores/datasource.js similarity index 98% rename from packages/frontend-core/src/components/grid/stores/datsource.js rename to packages/frontend-core/src/components/grid/stores/datasource.js index a0f6e36811..fb033cd1f1 100644 --- a/packages/frontend-core/src/components/grid/stores/datsource.js +++ b/packages/frontend-core/src/components/grid/stores/datasource.js @@ -86,7 +86,7 @@ export const createActions = context => { if ($datasource.type === "table") { await API.saveTable(newDefinition) } else if ($datasource.type === "viewV2") { - await API.viewV2.update({ ...newDefinition }) + await API.viewV2.update(newDefinition) } } diff --git a/packages/frontend-core/src/components/grid/stores/index.js b/packages/frontend-core/src/components/grid/stores/index.js index 70a9471991..8104319f38 100644 --- a/packages/frontend-core/src/components/grid/stores/index.js +++ b/packages/frontend-core/src/components/grid/stores/index.js @@ -17,7 +17,7 @@ import * as Filter from "./filter" import * as Notifications from "./notifications" import * as Table from "./table" import * as ViewV2 from "./viewV2" -import * as Datasource from "./datsource" +import * as Datasource from "./datasource" const DependencyOrderedStores = [ // Common stores diff --git a/packages/frontend-core/src/components/grid/stores/table.js b/packages/frontend-core/src/components/grid/stores/table.js index bcac6df48f..59d351950a 100644 --- a/packages/frontend-core/src/components/grid/stores/table.js +++ b/packages/frontend-core/src/components/grid/stores/table.js @@ -15,6 +15,8 @@ export const initialise = context => { // Update fetch when sorting changes sort.subscribe($sort => { if (get(datasource)?.type === "table") { + console.log("update", $sort) + console.log(get(fetch)) get(fetch)?.update({ sortOrder: $sort.order, sortColumn: $sort.column, From b00f3d24188b6798c18ec4340b98f25f41ef5064 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 3 Aug 2023 11:28:02 +0100 Subject: [PATCH 044/316] Remove log --- packages/frontend-core/src/components/grid/stores/table.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/frontend-core/src/components/grid/stores/table.js b/packages/frontend-core/src/components/grid/stores/table.js index 59d351950a..bcac6df48f 100644 --- a/packages/frontend-core/src/components/grid/stores/table.js +++ b/packages/frontend-core/src/components/grid/stores/table.js @@ -15,8 +15,6 @@ export const initialise = context => { // Update fetch when sorting changes sort.subscribe($sort => { if (get(datasource)?.type === "table") { - console.log("update", $sort) - console.log(get(fetch)) get(fetch)?.update({ sortOrder: $sort.order, sortColumn: $sort.column, From d443bf3616f97e0bc9f326ad40c74eeb31a24765 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 3 Aug 2023 13:18:58 +0100 Subject: [PATCH 045/316] Split out more datasource specific logic --- .../src/components/grid/index.js | 1 - .../src/components/grid/lib/constants.js | 4 -- .../src/components/grid/stores/config.js | 3 +- .../src/components/grid/stores/datasource.js | 64 ++++++++++--------- .../src/components/grid/stores/index.js | 7 +- .../src/components/grid/stores/rows.js | 45 +++---------- .../src/components/grid/stores/table.js | 38 +++++++++++ .../src/components/grid/stores/viewV2.js | 58 +++++++++++++++++ 8 files changed, 142 insertions(+), 78 deletions(-) diff --git a/packages/frontend-core/src/components/grid/index.js b/packages/frontend-core/src/components/grid/index.js index 453e12967f..25747ec142 100644 --- a/packages/frontend-core/src/components/grid/index.js +++ b/packages/frontend-core/src/components/grid/index.js @@ -1,2 +1 @@ export { default as Grid } from "./layout/Grid.svelte" -export { DatasourceType } from "./lib/constants" diff --git a/packages/frontend-core/src/components/grid/lib/constants.js b/packages/frontend-core/src/components/grid/lib/constants.js index de9fa6591c..a6e6723463 100644 --- a/packages/frontend-core/src/components/grid/lib/constants.js +++ b/packages/frontend-core/src/components/grid/lib/constants.js @@ -1,7 +1,3 @@ -export const DatasourceType = { - Table: "table", - ViewV2: "viewV2", -} export const Padding = 246 export const MaxCellRenderHeight = 222 export const ScrollBarSize = 8 diff --git a/packages/frontend-core/src/components/grid/stores/config.js b/packages/frontend-core/src/components/grid/stores/config.js index de81a32ff5..46bff299d4 100644 --- a/packages/frontend-core/src/components/grid/stores/config.js +++ b/packages/frontend-core/src/components/grid/stores/config.js @@ -1,6 +1,5 @@ import { derivedMemo } from "../../../utils" import { derived } from "svelte/store" -import { DatasourceType } from "../lib/constants" export const deriveStores = context => { const { props, hasNonAutoColumn } = context @@ -29,7 +28,7 @@ export const deriveStores = context => { } // Disable some features if we're editing a view - if ($props.datasource?.type === DatasourceType.ViewV2) { + if ($props.datasource?.type === "viewV2") { config.canEditColumns = false } diff --git a/packages/frontend-core/src/components/grid/stores/datasource.js b/packages/frontend-core/src/components/grid/stores/datasource.js index fb033cd1f1..4e6fbedfd6 100644 --- a/packages/frontend-core/src/components/grid/stores/datasource.js +++ b/packages/frontend-core/src/components/grid/stores/datasource.js @@ -19,18 +19,6 @@ export const deriveStores = context => { } let newSchema = { ...$definition?.schema } - // Edge case to temporarily allow deletion of duplicated user - // fields that were saved with the "disabled" flag set. - // By overriding the saved schema we ensure only overrides can - // set the disabled flag. - // TODO: remove in future - Object.keys(newSchema).forEach(field => { - newSchema[field] = { - ...newSchema[field], - disabled: false, - } - }) - // Apply schema overrides Object.keys($schemaOverrides || {}).forEach(field => { if (newSchema[field]) { @@ -60,34 +48,34 @@ export const deriveStores = context => { } export const createActions = context => { - const { datasource, definition, API, config, dispatch } = context + const { datasource, definition, config, dispatch, table, viewV2 } = context + + // Gets the appropriate API for the configured datasource type + const getAPI = () => { + const $datasource = get(datasource) + switch ($datasource?.type) { + case "table": + return table + case "viewV2": + return viewV2 + default: + return null + } + } // Refreshes the datasource definition const refreshDefinition = async () => { - const $datasource = get(datasource) - if ($datasource.type === "table") { - definition.set(await API.fetchTableDefinition($datasource.tableId)) - } else if ($datasource.type === "viewV2") { - // const definition = await API.viewsV2.(get(tableId)) - // table.set(definition) - } + return await getAPI()?.actions.refreshDefinition() } // Saves the datasource definition const saveDefinition = async newDefinition => { - const $config = get(config) - const $datasource = get(datasource) - // Update local state definition.set(newDefinition) // Update server - if ($config.canSaveSchema) { - if ($datasource.type === "table") { - await API.saveTable(newDefinition) - } else if ($datasource.type === "viewV2") { - await API.viewV2.update(newDefinition) - } + if (get(config).canSaveSchema) { + await getAPI()?.actions.saveDefinition(newDefinition) } // Broadcast change to external state can be updated, as this change @@ -95,12 +83,30 @@ export const createActions = context => { dispatch("updatedefinition", newDefinition) } + // Adds a row to the datasource + const addRow = async row => { + return await getAPI()?.actions.addRow(row) + } + + // Updates an existing row in the datasource + const updateRow = async row => { + return await getAPI()?.actions.updateRow(row) + } + + // Deletes rows from the datasource + const deleteRows = async rows => { + return await getAPI()?.actions.deleteRows(rows) + } + return { datasource: { ...datasource, actions: { refreshDefinition, saveDefinition, + addRow, + updateRow, + deleteRows, }, }, } diff --git a/packages/frontend-core/src/components/grid/stores/index.js b/packages/frontend-core/src/components/grid/stores/index.js index 8104319f38..cb858b7293 100644 --- a/packages/frontend-core/src/components/grid/stores/index.js +++ b/packages/frontend-core/src/components/grid/stores/index.js @@ -20,12 +20,13 @@ import * as ViewV2 from "./viewV2" import * as Datasource from "./datasource" const DependencyOrderedStores = [ - // Common stores Notifications, Sort, Filter, Bounds, Scroll, + Table, + ViewV2, Datasource, Columns, Rows, @@ -39,10 +40,6 @@ const DependencyOrderedStores = [ Pagination, Clipboard, Config, - - // Datasource specific stores - Table, - ViewV2, ] export const attachStores = context => { diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index dce4fca64b..d28707406b 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -240,19 +240,9 @@ export const createActions = context => { // Adds a new row const addRow = async (row, idx, bubble = false) => { try { - // Create row - const $datasource = get(datasource) + // Create row. Spread row so we can mutate and enrich safely. let newRow = { ...row } - if ($datasource.type === "table") { - newRow.tableId = $datasource.tableId - newRow = await API.saveRow(newRow, SuppressErrors) - } else if ($datasource.type === "viewV2") { - newRow.tableId = $datasource.tableId - newRow._viewId = $datasource.id - newRow = await API.viewV2.createRow(newRow) - } else { - return - } + newRow = await datasource.actions.addRow(newRow) // Update state if (idx != null) { @@ -381,19 +371,11 @@ export const createActions = context => { [rowId]: true, })) - let saved - if ($datasource.type === "table") { - saved = await API.saveRow( - { ...row, ...get(rowChangeCache)[rowId] }, - SuppressErrors - ) - } else if ($datasource.type === "viewV2") { - saved = await API.viewV2.updateRow( - { ...row, ...get(rowChangeCache)[rowId] }, - SuppressErrors - ) - saved._viewId = $datasource.id - } + // Update row + const saved = await datasource.actions.updateRow({ + ...row, + ...get(rowChangeCache)[rowId], + }) // Update state after a successful change if (saved?._id) { @@ -428,23 +410,12 @@ export const createActions = context => { if (!rowsToDelete?.length) { return } - const $datasource = get(datasource) // Actually delete rows rowsToDelete.forEach(row => { delete row.__idx }) - if ($datasource.type === "table") { - await API.deleteRows({ - tableId: $datasource.tableId, - rows: rowsToDelete, - }) - } else if ($datasource.type === "viewV2") { - await API.viewV2.deleteRows({ - viewId: $datasource.id, - rows: rowsToDelete, - }) - } + await datasource.actions.deleteRows(rowsToDelete) // Update state handleRemoveRows(rowsToDelete) diff --git a/packages/frontend-core/src/components/grid/stores/table.js b/packages/frontend-core/src/components/grid/stores/table.js index bcac6df48f..87a53343f4 100644 --- a/packages/frontend-core/src/components/grid/stores/table.js +++ b/packages/frontend-core/src/components/grid/stores/table.js @@ -1,5 +1,43 @@ import { get } from "svelte/store" +const SuppressErrors = true + +export const createActions = context => { + const { definition, API, datasource } = context + + const refreshDefinition = async () => { + definition.set(await API.fetchTableDefinition(get(datasource).tableId)) + } + + const saveDefinition = async newDefinition => { + await API.saveTable(newDefinition) + } + + const saveRow = async row => { + row.tableId = get(datasource)?.tableId + return await API.saveRow(row, SuppressErrors) + } + + const deleteRows = async rows => { + await API.deleteRows({ + tableId: get(datasource).tableId, + rows, + }) + } + + return { + table: { + actions: { + refreshDefinition, + saveDefinition, + addRow: saveRow, + updateRow: saveRow, + deleteRows, + }, + }, + } +} + export const initialise = context => { const { datasource, fetch, filter, sort } = context diff --git a/packages/frontend-core/src/components/grid/stores/viewV2.js b/packages/frontend-core/src/components/grid/stores/viewV2.js index bf47513908..44a73405a5 100644 --- a/packages/frontend-core/src/components/grid/stores/viewV2.js +++ b/packages/frontend-core/src/components/grid/stores/viewV2.js @@ -1,5 +1,63 @@ import { get } from "svelte/store" +const SuppressErrors = true + +export const createActions = context => { + const { definition, API, datasource } = 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 => { + await API.viewV2.update(newDefinition) + } + + const addRow = async row => { + const $datasource = get(datasource) + row.tableId = $datasource?.tableId + row._viewId = $datasource?.id + return await API.viewV2.createRow(row, SuppressErrors) + } + + const updateRow = async row => { + const $datasource = get(datasource) + const savedRow = await API.viewV2.updateRow(row, SuppressErrors) + return { + ...savedRow, + _viewId: $datasource.id, + } + } + + const deleteRows = async rows => { + await API.viewV2.deleteRows({ + viewId: get(datasource).id, + rows, + }) + } + + return { + viewV2: { + actions: { + refreshDefinition, + saveDefinition, + addRow, + updateRow, + deleteRows, + }, + }, + } +} + export const initialise = context => { const { definition, datasource, sort, rows } = context From 3eeb945934c58f5be69c5c11f6e1a13bfac72b30 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 3 Aug 2023 13:22:49 +0100 Subject: [PATCH 046/316] Split out logic for getting rows from datasources --- .../src/components/grid/stores/datasource.js | 5 +++++ .../src/components/grid/stores/rows.js | 20 +------------------ .../src/components/grid/stores/table.js | 15 ++++++++++++++ .../src/components/grid/stores/viewV2.js | 16 +++++++++++++++ 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/packages/frontend-core/src/components/grid/stores/datasource.js b/packages/frontend-core/src/components/grid/stores/datasource.js index 4e6fbedfd6..5ace5ceef8 100644 --- a/packages/frontend-core/src/components/grid/stores/datasource.js +++ b/packages/frontend-core/src/components/grid/stores/datasource.js @@ -98,6 +98,10 @@ export const createActions = context => { return await getAPI()?.actions.deleteRows(rows) } + const getRow = async id => { + return await getAPI()?.actions.getRow(id) + } + return { datasource: { ...datasource, @@ -107,6 +111,7 @@ export const createActions = context => { addRow, updateRow, deleteRows, + getRow, }, }, } diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index d28707406b..96194c6fc5 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -3,8 +3,6 @@ import { fetchData } from "../../../fetch/fetchData" import { NewRowID, RowPageSize } from "../lib/constants" import { tick } from "svelte" -const SuppressErrors = true - export const createStores = () => { const rows = writable([]) const loading = writable(false) @@ -280,21 +278,6 @@ export const createActions = context => { } } - // Fetches a row by ID using the search endpoint - const fetchRow = async id => { - const res = await API.searchTable({ - tableId: get(datasource).tableId, - limit: 1, - query: { - equal: { - _id: id, - }, - }, - paginate: false, - }) - return res?.rows?.[0] - } - // Replaces a row in state with the newly defined row, handling updates, // addition and deletion const replaceRow = (id, row) => { @@ -323,7 +306,7 @@ export const createActions = context => { // Refreshes a specific row const refreshRow = async id => { - const row = await fetchRow(id) + const row = await datasource.actions.getRow(id) replaceRow(id, row) } @@ -336,7 +319,6 @@ export const createActions = context => { const updateRow = async (rowId, changes) => { const $rows = get(rows) const $rowLookupMap = get(rowLookupMap) - const $datasource = get(datasource) const index = $rowLookupMap[rowId] const row = $rows[index] if (index == null || !Object.keys(changes || {}).length) { diff --git a/packages/frontend-core/src/components/grid/stores/table.js b/packages/frontend-core/src/components/grid/stores/table.js index 87a53343f4..d79696c263 100644 --- a/packages/frontend-core/src/components/grid/stores/table.js +++ b/packages/frontend-core/src/components/grid/stores/table.js @@ -25,6 +25,20 @@ export const createActions = context => { }) } + const getRow = async id => { + const res = await API.searchTable({ + tableId: get(datasource).tableId, + limit: 1, + query: { + equal: { + _id: id, + }, + }, + paginate: false, + }) + return res?.rows?.[0] + } + return { table: { actions: { @@ -33,6 +47,7 @@ export const createActions = context => { addRow: saveRow, updateRow: saveRow, deleteRows, + getRow, }, }, } diff --git a/packages/frontend-core/src/components/grid/stores/viewV2.js b/packages/frontend-core/src/components/grid/stores/viewV2.js index 44a73405a5..35e447d8fa 100644 --- a/packages/frontend-core/src/components/grid/stores/viewV2.js +++ b/packages/frontend-core/src/components/grid/stores/viewV2.js @@ -45,6 +45,21 @@ export const createActions = context => { }) } + // TODO: update in future. We can't depend on having table read access. + const getRow = async id => { + const res = await API.searchTable({ + tableId: get(datasource).tableId, + limit: 1, + query: { + equal: { + _id: id, + }, + }, + paginate: false, + }) + return res?.rows?.[0] + } + return { viewV2: { actions: { @@ -53,6 +68,7 @@ export const createActions = context => { addRow, updateRow, deleteRows, + getRow, }, }, } From f5e5a883cf82b9f386167027e5d551477ebb8bb8 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 3 Aug 2023 13:31:44 +0100 Subject: [PATCH 047/316] Enable renaming views and lint --- .../builder/src/stores/backend/viewsV2.js | 30 ++++++++----------- .../src/components/grid/stores/sort.js | 3 +- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/builder/src/stores/backend/viewsV2.js b/packages/builder/src/stores/backend/viewsV2.js index 8b7b1d876c..511545adcb 100644 --- a/packages/builder/src/stores/backend/viewsV2.js +++ b/packages/builder/src/stores/backend/viewsV2.js @@ -54,23 +54,20 @@ export function createViewsV2Store() { } const save = async view => { - // No dedicated save endpoint at this time - // const savedView = await API.saveView(view) - // - // // Update tables - // tables.update(state => { - // const table = state.list.find(table => table._id === view.tableId) - // if (table) { - // if (view.originalName) { - // delete table.views[view.originalName] - // } - // table.views[view.name] = savedView - // } - // return { ...state } - // }) - } + const savedView = await API.viewV2.update(view) - const replace = (id, view) => {} + // Update tables + tables.update(state => { + const table = state.list.find(table => table._id === view.tableId) + if (table) { + if (view.originalName) { + delete table.views[view.originalName] + } + table.views[view.name] = savedView + } + return { ...state } + }) + } return { subscribe: derivedStore.subscribe, @@ -78,7 +75,6 @@ export function createViewsV2Store() { delete: deleteView, create, save, - replace, } } diff --git a/packages/frontend-core/src/components/grid/stores/sort.js b/packages/frontend-core/src/components/grid/stores/sort.js index 098e126930..3beecd2c2a 100644 --- a/packages/frontend-core/src/components/grid/stores/sort.js +++ b/packages/frontend-core/src/components/grid/stores/sort.js @@ -16,8 +16,7 @@ export const createStores = context => { } export const initialise = context => { - const { sort, initialSortColumn, initialSortOrder, table, datasource } = - context + const { sort, initialSortColumn, initialSortOrder } = context // Reset sort when initial sort props change initialSortColumn.subscribe(newSortColumn => { From 19ca7e4a0a70241a70966de49171ae3e066c27e1 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 3 Aug 2023 13:40:36 +0100 Subject: [PATCH 048/316] Fix issue with viewV2 renaming --- packages/builder/src/stores/backend/viewsV2.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/stores/backend/viewsV2.js b/packages/builder/src/stores/backend/viewsV2.js index 511545adcb..c8f38ff5e5 100644 --- a/packages/builder/src/stores/backend/viewsV2.js +++ b/packages/builder/src/stores/backend/viewsV2.js @@ -54,7 +54,8 @@ export function createViewsV2Store() { } const save = async view => { - const savedView = await API.viewV2.update(view) + const res = await API.viewV2.update(view) + const savedView = res?.data // Update tables tables.update(state => { From 46f16764dbcbb578a77cddcf9ad8a186351a831e Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 4 Aug 2023 11:47:06 +0100 Subject: [PATCH 049/316] Fix initial sorting state for tables and memoize sorting store to avoid loops --- .../src/components/grid/stores/rows.js | 5 ----- .../src/components/grid/stores/sort.js | 5 +++-- .../src/components/grid/stores/table.js | 18 +++++++++++++++++- .../src/components/grid/stores/viewV2.js | 14 ++++---------- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index 96194c6fc5..d876b5f8df 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -151,11 +151,6 @@ export const createActions = context => { // Reset state properties when dataset changes if (!$instanceLoaded || resetRows) { definition.set($fetch.definition) - - // sort.set({ - // column: $fetch.sortColumn, - // order: $fetch.sortOrder, - // }) } // Reset scroll state when data changes diff --git a/packages/frontend-core/src/components/grid/stores/sort.js b/packages/frontend-core/src/components/grid/stores/sort.js index 3beecd2c2a..689b278874 100644 --- a/packages/frontend-core/src/components/grid/stores/sort.js +++ b/packages/frontend-core/src/components/grid/stores/sort.js @@ -1,11 +1,12 @@ -import { writable, get } from "svelte/store" +import { get } from "svelte/store" +import { memo } from "../../../utils" export const createStores = context => { const { props } = context const $props = get(props) // Initialise to default props - const sort = writable({ + const sort = memo({ column: $props.initialSortColumn, order: $props.initialSortOrder || "ascending", }) diff --git a/packages/frontend-core/src/components/grid/stores/table.js b/packages/frontend-core/src/components/grid/stores/table.js index d79696c263..5c3d2c9ef2 100644 --- a/packages/frontend-core/src/components/grid/stores/table.js +++ b/packages/frontend-core/src/components/grid/stores/table.js @@ -54,7 +54,7 @@ export const createActions = context => { } export const initialise = context => { - const { datasource, fetch, filter, sort } = context + const { datasource, fetch, filter, sort, definition } = context // Update fetch when filter changes filter.subscribe($filter => { @@ -74,4 +74,20 @@ export const initialise = context => { }) } }) + + // Ensure sorting UI reflects the fetch state whenever we reset the fetch, + // which triggers a new definition + definition.subscribe(() => { + if (get(datasource)?.type === "table") { + const $fetch = get(fetch) + if (!$fetch) { + return + } + const { sortColumn, sortOrder } = get($fetch) + sort.set({ + column: sortColumn, + order: sortOrder, + }) + } + }) } diff --git a/packages/frontend-core/src/components/grid/stores/viewV2.js b/packages/frontend-core/src/components/grid/stores/viewV2.js index 35e447d8fa..b2d80acd1a 100644 --- a/packages/frontend-core/src/components/grid/stores/viewV2.js +++ b/packages/frontend-core/src/components/grid/stores/viewV2.js @@ -82,16 +82,10 @@ export const initialise = context => { if (!$definition || get(datasource)?.type !== "viewV2") { return } - const $sort = get(sort) - if ( - $definition.sort?.field !== $sort?.column || - $definition.sort?.order !== $sort?.order - ) { - sort.set({ - column: $definition.sort?.field, - order: $definition.sort?.order, - }) - } + sort.set({ + column: $definition.sort?.field, + order: $definition.sort?.order, + }) }) // When sorting changes, ensure view definition is kept up to date From cd2231630f7d248dd249d99a8797989b76423266 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 4 Aug 2023 14:54:45 +0100 Subject: [PATCH 050/316] Improve updating of viewV2 state and restore grid<>builder sync for datasource definitions --- .../backend/DataTable/ViewV2DataTable.svelte | 3 +- .../builder/src/stores/backend/viewsV2.js | 73 ++++++++++++------- .../src/components/grid/stores/datasource.js | 2 +- 3 files changed, 50 insertions(+), 28 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte index 21b3e1c291..859ce11280 100644 --- a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte @@ -13,7 +13,8 @@ } const handleGridViewUpdate = async e => { - viewsV2.replace(id, e.detail) + console.log("update") + viewsV2.replaceView(id, e.detail) } diff --git a/packages/builder/src/stores/backend/viewsV2.js b/packages/builder/src/stores/backend/viewsV2.js index c8f38ff5e5..6fa3b52bb9 100644 --- a/packages/builder/src/stores/backend/viewsV2.js +++ b/packages/builder/src/stores/backend/viewsV2.js @@ -1,4 +1,4 @@ -import { writable, derived } from "svelte/store" +import { writable, derived, get } from "svelte/store" import { tables } from "./" import { API } from "api" @@ -30,44 +30,64 @@ export function createViewsV2Store() { const deleteView = async view => { await API.viewV2.delete(view.id) - - // Update tables - tables.update(state => { - const table = state.list.find(table => table._id === view.tableId) - delete table.views[view.name] - return { ...state } - }) + replaceView(view.id, null) } const create = async view => { const savedViewResponse = await API.viewV2.create(view) const savedView = savedViewResponse.data - - // Update tables - tables.update(state => { - const table = state.list.find(table => table._id === view.tableId) - table.views[view.name] = savedView - return { ...state } - }) - + replaceView(savedView.id, savedView) return savedView } const save = async view => { const res = await API.viewV2.update(view) const savedView = res?.data + replaceView(view.id, savedView) + } - // Update tables - tables.update(state => { - const table = state.list.find(table => table._id === view.tableId) - if (table) { - if (view.originalName) { - delete table.views[view.originalName] - } - table.views[view.name] = savedView - } - return { ...state } + // Handles external updates of tables + const replaceView = (viewId, view) => { + console.log("replace", viewId, view) + if (!viewId) { + return + } + const existingView = get(derivedStore).list.find(view => view.id === viewId) + const tableIndex = get(tables).list.findIndex(table => { + return table._id === view?.tableId || table._id === existingView?.tableId }) + if (tableIndex === -1) { + return + } + + // Handle deletion + if (!view) { + tables.update(state => { + delete state.list[tableIndex].views[existingView.name] + return state + }) + return + } + + // Add new view + if (!existingView) { + tables.update(state => { + state.list[tableIndex].views[view.name] = view + return state + }) + } + + // Update existing view + else { + tables.update(state => { + // Remove old view + delete state.list[tableIndex].views[existingView.name] + + // Add new view + state.list[tableIndex].views[view.name] = view + return state + }) + } } return { @@ -76,6 +96,7 @@ export function createViewsV2Store() { delete: deleteView, create, save, + replaceView, } } diff --git a/packages/frontend-core/src/components/grid/stores/datasource.js b/packages/frontend-core/src/components/grid/stores/datasource.js index 5ace5ceef8..d5017aa8d1 100644 --- a/packages/frontend-core/src/components/grid/stores/datasource.js +++ b/packages/frontend-core/src/components/grid/stores/datasource.js @@ -80,7 +80,7 @@ export const createActions = context => { // Broadcast change to external state can be updated, as this change // will not be received by the builder websocket because we caused it ourselves - dispatch("updatedefinition", newDefinition) + dispatch("updatedatasource", newDefinition) } // Adds a row to the datasource From 0a87e3502e2ad720d49facf102f04f062cd1d74d Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 4 Aug 2023 14:58:12 +0100 Subject: [PATCH 051/316] Fix other user selection state for viewV2s --- .../components/backend/TableNavigator/TableNavigator.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte b/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte index f1965117ae..056a36c4a7 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte @@ -58,7 +58,8 @@ $goto(`./view/v1/${encodeURIComponent(name)}`) } }} - selectedBy={$userSelectedResourceMap[name]} + selectedBy={$userSelectedResourceMap[name] || + $userSelectedResourceMap[view.id]} > From 938a5a445f87b32cc0672ba3feab58c3eb917eba Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 4 Aug 2023 15:04:58 +0100 Subject: [PATCH 052/316] Add multi dev collab for viewV2 definitions --- packages/server/src/api/controllers/view/viewsV2.ts | 12 +++++++++++- packages/server/src/sdk/app/views/index.ts | 3 ++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/server/src/api/controllers/view/viewsV2.ts b/packages/server/src/api/controllers/view/viewsV2.ts index 226b7201b8..ba97fff8b6 100644 --- a/packages/server/src/api/controllers/view/viewsV2.ts +++ b/packages/server/src/api/controllers/view/viewsV2.ts @@ -8,6 +8,7 @@ import { ViewV2, RequiredKeys, } from "@budibase/types" +import { builderSocket } from "../../../websockets" async function parseSchemaUI(ctx: Ctx, view: CreateViewRequest) { if (!view.schema) { @@ -86,6 +87,9 @@ export async function create(ctx: Ctx) { ctx.body = { data: result, } + + const table = await sdk.tables.getTable(tableId) + builderSocket?.emitTableUpdate(ctx, table) } export async function update(ctx: Ctx) { @@ -118,11 +122,17 @@ export async function update(ctx: Ctx) { ctx.body = { data: result, } + + const table = await sdk.tables.getTable(tableId) + builderSocket?.emitTableUpdate(ctx, table) } export async function remove(ctx: Ctx) { const { viewId } = ctx.params - await sdk.views.remove(viewId) + const view = await sdk.views.remove(viewId) ctx.status = 204 + + const table = await sdk.tables.getTable(view.tableId) + builderSocket?.emitTableUpdate(ctx, table) } diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index d87715f49b..aafaab3a36 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -56,7 +56,7 @@ export function isV2(view: View | ViewV2): view is ViewV2 { return (view as ViewV2).version === 2 } -export async function remove(viewId: string): Promise { +export async function remove(viewId: string): Promise { const db = context.getAppDB() const view = await get(viewId) @@ -67,6 +67,7 @@ export async function remove(viewId: string): Promise { delete table.views![view?.name] await db.put(table) + return view } export function enrichSchema(view: View | ViewV2, tableSchema: TableSchema) { From ffe82e18e993b7a402cfccdd6e9b319898bced77 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 4 Aug 2023 15:07:44 +0100 Subject: [PATCH 053/316] Remove log --- packages/builder/src/stores/backend/viewsV2.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/builder/src/stores/backend/viewsV2.js b/packages/builder/src/stores/backend/viewsV2.js index 6fa3b52bb9..dcc2c27152 100644 --- a/packages/builder/src/stores/backend/viewsV2.js +++ b/packages/builder/src/stores/backend/viewsV2.js @@ -48,7 +48,6 @@ export function createViewsV2Store() { // Handles external updates of tables const replaceView = (viewId, view) => { - console.log("replace", viewId, view) if (!viewId) { return } From 8a9db6d8deef8d27ec8909e3ef038e781a809951 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 4 Aug 2023 15:15:43 +0100 Subject: [PATCH 054/316] Remove log --- .../src/components/backend/DataTable/ViewV2DataTable.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte index 859ce11280..e4e1adda36 100644 --- a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte @@ -13,7 +13,6 @@ } const handleGridViewUpdate = async e => { - console.log("update") viewsV2.replaceView(id, e.detail) } From ef4ff87d8a3aacd73d23ef61f43b5a175c47a2b7 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 4 Aug 2023 15:17:30 +0100 Subject: [PATCH 055/316] Remove filter button on views for now --- .../src/components/backend/DataTable/ViewV2DataTable.svelte | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte index e4e1adda36..789f7d730a 100644 --- a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte @@ -26,9 +26,7 @@ showAvatars={false} on:updatedatasource={handleGridViewUpdate} > - - - + From dd4b3047369d7b0d8fdaf5d0ab03eaa1303ac854 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 4 Aug 2023 15:20:30 +0100 Subject: [PATCH 056/316] Fix creating views --- .../buttons/grid/GridCreateViewButton.svelte | 4 ++-- .../GridCreateViewModal.svelte} | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) rename packages/builder/src/components/backend/DataTable/modals/{CreateViewModal.svelte => grid/GridCreateViewModal.svelte} (82%) diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridCreateViewButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridCreateViewButton.svelte index 33c416d7ef..3244ce3277 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridCreateViewButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridCreateViewButton.svelte @@ -1,7 +1,7 @@ - + {/if}
diff --git a/packages/frontend-core/src/components/grid/stores/pagination.js b/packages/frontend-core/src/components/grid/stores/pagination.js index c6a856e229..1dbea6e0d2 100644 --- a/packages/frontend-core/src/components/grid/stores/pagination.js +++ b/packages/frontend-core/src/components/grid/stores/pagination.js @@ -1,4 +1,4 @@ -import { derived } from "svelte/store" +import { derived, get } from "svelte/store" export const initialise = context => { const { scrolledRowCount, rows, visualRowCapacity } = context @@ -11,13 +11,12 @@ export const initialise = context => { [scrolledRowCount, rowCount, visualRowCapacity], ([$scrolledRowCount, $rowCount, $visualRowCapacity]) => { return Math.max(0, $rowCount - $scrolledRowCount - $visualRowCapacity) - }, - 100 + } ) // Fetch next page when fewer than 25 remaining rows to scroll remainingRows.subscribe(remaining => { - if (remaining < 25) { + if (remaining < 25 && get(rowCount)) { rows.actions.loadNextPage() } }) diff --git a/packages/frontend-core/src/components/grid/stores/sort.js b/packages/frontend-core/src/components/grid/stores/sort.js index 689b278874..9b2dbfb8df 100644 --- a/packages/frontend-core/src/components/grid/stores/sort.js +++ b/packages/frontend-core/src/components/grid/stores/sort.js @@ -1,4 +1,4 @@ -import { get } from "svelte/store" +import { derived, get } from "svelte/store" import { memo } from "../../../utils" export const createStores = context => { @@ -17,13 +17,34 @@ export const createStores = context => { } export const initialise = context => { - const { sort, initialSortColumn, initialSortOrder } = context + const { sort, initialSortColumn, initialSortOrder, definition } = context // Reset sort when initial sort props change initialSortColumn.subscribe(newSortColumn => { sort.update(state => ({ ...state, column: newSortColumn })) }) initialSortOrder.subscribe(newSortOrder => { - sort.update(state => ({ ...state, order: newSortOrder })) + sort.update(state => ({ ...state, order: newSortOrder || "ascending" })) + }) + + // Derive if the current sort column exists in the schema + const sortColumnExists = derived( + [sort, definition], + ([$sort, $definition]) => { + if (!$sort?.column) { + return true + } + return $definition?.schema?.[$sort.column] != null + } + ) + + // Clear sort state if our sort column does not exist + sortColumnExists.subscribe(exists => { + if (!exists) { + sort.set({ + column: null, + order: "ascending", + }) + } }) } diff --git a/packages/frontend-core/src/components/grid/stores/table.js b/packages/frontend-core/src/components/grid/stores/table.js index 5c33e8171d..6c877e353a 100644 --- a/packages/frontend-core/src/components/grid/stores/table.js +++ b/packages/frontend-core/src/components/grid/stores/table.js @@ -59,51 +59,55 @@ export const createActions = context => { } export const initialise = context => { - const { datasource, fetch, filter, sort, definition } = context + const { datasource, fetch, filter, sort, table } = context - // Wipe filter whenever table ID changes to avoid using stale filters + // 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 table datasources datasource.subscribe($datasource => { - if ($datasource?.type !== "table") { + // Clear previous subscriptions + unsubscribers?.forEach(unsubscribe => unsubscribe()) + unsubscribers = [] + if (!table.actions.isDatasourceValid($datasource)) { return } + + // Wipe state filter.set([]) - }) - - // Update fetch when filter changes - filter.subscribe($filter => { - if (get(datasource)?.type !== "table") { - return - } - get(fetch)?.update({ - filter: $filter, - }) - }) - - // Update fetch when sorting changes - sort.subscribe($sort => { - if (get(datasource)?.type !== "table") { - return - } - get(fetch)?.update({ - sortOrder: $sort.order, - sortColumn: $sort.column, - }) - }) - - // Ensure sorting UI reflects the fetch state whenever we reset the fetch, - // which triggers a new definition - definition.subscribe(() => { - if (get(datasource)?.type !== "table") { - return - } - const $fetch = get(fetch) - if (!$fetch) { - return - } - const { sortColumn, sortOrder } = get($fetch) sort.set({ - column: sortColumn, - order: sortOrder, + column: null, + order: "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?.tableId !== $datasource.tableId) { + 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?.tableId !== $datasource.tableId) { + return + } + $fetch.update({ + sortOrder: $sort.order || "ascending", + sortColumn: $sort.column, + }) + }) + ) }) } diff --git a/packages/frontend-core/src/components/grid/stores/viewV2.js b/packages/frontend-core/src/components/grid/stores/viewV2.js index b24bf56c98..f93dc57b69 100644 --- a/packages/frontend-core/src/components/grid/stores/viewV2.js +++ b/packages/frontend-core/src/components/grid/stores/viewV2.js @@ -69,62 +69,91 @@ export const createActions = context => { } export const initialise = context => { - const { definition, datasource, sort, rows, filter, subscribe } = context + const { definition, datasource, sort, rows, filter, subscribe, viewV2 } = + context - // Keep sort and filter state in line with the view definition - definition.subscribe($definition => { - if (!$definition || get(datasource)?.type !== "viewV2") { + // 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 (!viewV2.actions.isDatasourceValid($datasource)) { return } + + // Reset state for new view + filter.set([]) sort.set({ - column: $definition.sort?.field, - order: $definition.sort?.order, + column: null, + order: "ascending", }) - filter.set($definition.query || []) - }) - // When sorting changes, ensure view definition is kept up to date - sort.subscribe(async $sort => { - const $view = get(definition) - if (!$view || get(datasource)?.type !== "viewV2") { - return - } - if ( - $sort?.column !== $view.sort?.field || - $sort?.order !== $view.sort?.order - ) { - await datasource.actions.saveDefinition({ - ...$view, - sort: { - field: $sort.column, - order: $sort.order, - }, + // Keep sort and filter state in line with the view definition + unsubscribers.push( + definition.subscribe($definition => { + if ($definition?.id !== $datasource.id) { + return + } + sort.set({ + column: $definition.sort?.field, + order: $definition.sort?.order || "ascending", + }) + filter.set($definition.query || []) }) - await rows.actions.refreshData() - } - }) + ) - // When filters change, ensure view definition is kept up to date - filter.subscribe(async $filter => { - const $view = get(definition) - if (!$view || get(datasource)?.type !== "viewV2") { - return - } - if (JSON.stringify($filter) !== JSON.stringify($view.query)) { - await datasource.actions.saveDefinition({ - ...$view, - query: $filter, + // When sorting changes, ensure view definition is kept up to date + unsubscribers.push( + sort.subscribe(async $sort => { + // Ensure we're updating the correct view + const $view = get(definition) + if ($view?.id !== $datasource.id) { + return + } + if ( + $sort?.column !== $view.sort?.field || + $sort?.order !== $view.sort?.order + ) { + await datasource.actions.saveDefinition({ + ...$view, + sort: { + field: $sort.column, + order: $sort.order || "ascending", + }, + }) + await rows.actions.refreshData() + } }) - await rows.actions.refreshData() - } - }) + ) - // When hidden we show columns, we need to refresh data in order to fetch - // values for those columns - subscribe("show-column", async () => { - if (get(datasource)?.type !== "viewV2") { - return - } - await rows.actions.refreshData() + // When filters change, ensure view definition is kept up to date + unsubscribers?.push( + filter.subscribe(async $filter => { + // Ensure we're updating the correct view + const $view = get(definition) + if ($view?.id !== $datasource.id) { + return + } + if (JSON.stringify($filter) !== JSON.stringify($view.query)) { + await datasource.actions.saveDefinition({ + ...$view, + query: $filter, + }) + await rows.actions.refreshData() + } + }) + ) + + // When hidden we show columns, we need to refresh data in order to fetch + // values for those columns + unsubscribers.push( + subscribe("show-column", async () => { + await rows.actions.refreshData() + }) + ) }) } diff --git a/packages/frontend-core/src/fetch/DataFetch.js b/packages/frontend-core/src/fetch/DataFetch.js index 28fa68afe9..cd12535ddc 100644 --- a/packages/frontend-core/src/fetch/DataFetch.js +++ b/packages/frontend-core/src/fetch/DataFetch.js @@ -110,6 +110,17 @@ export default class DataFetch { return this.derivedStore.subscribe } + /** + * Gets the default sort column for this datasource + */ + getDefaultSortColumn(definition, schema) { + if (definition?.primaryDisplay && schema[definition.primaryDisplay]) { + return definition.primaryDisplay + } else { + return Object.keys(schema)[0] + } + } + /** * Fetches a fresh set of data from the server, resetting pagination */ @@ -118,12 +129,6 @@ export default class DataFetch { // Fetch datasource definition and extract sort properties if configured const definition = await this.getDefinition(datasource) - if (definition?.sort?.field) { - this.options.sortColumn = definition.sort.field - } - if (definition?.sort?.order) { - this.options.sortOrder = definition.sort.order - } // Determine feature flags const features = this.determineFeatureFlags(definition) @@ -140,32 +145,32 @@ export default class DataFetch { return } - // If no sort order, default to descending - if (!this.options.sortOrder) { + // If an invalid sort column is specified, delete it + if (this.options.sortColumn && !schema[this.options.sortColumn]) { + this.options.sortColumn = null + } + + // If no sort column, get the default column for this datasource + if (!this.options.sortColumn) { + this.options.sortColumn = this.getDefaultSortColumn(definition, schema) + } + + // If we don't have a sort column specified then just ensure we don't set + // any sorting params + if (!this.options.sortColumn) { this.options.sortOrder = "ascending" - } + this.options.sortType = null + } else { + // Otherwise determine what sort type to use base on sort column + const type = schema?.[this.options.sortColumn]?.type + this.options.sortType = + type === "number" || type === "bigint" ? "number" : "string" - // If no sort column, or an invalid sort column is provided, use the primary - // display and fallback to first column - const sortValid = this.options.sortColumn && schema[this.options.sortColumn] - if (!sortValid) { - let newSortColumn - if (definition?.primaryDisplay && schema[definition.primaryDisplay]) { - newSortColumn = definition.primaryDisplay - } else { - newSortColumn = Object.keys(schema)[0] + // If no sort order, default to ascending + if (!this.options.sortOrder) { + this.options.sortOrder = "ascending" } - this.options.sortColumn = newSortColumn } - const { sortOrder, sortColumn } = this.options - - // Determine what sort type to use - let sortType = "string" - if (sortColumn) { - const type = schema?.[sortColumn]?.type - sortType = type === "number" || type === "bigint" ? "number" : "string" - } - this.options.sortType = sortType // Build the lucene query let query = this.options.query @@ -182,8 +187,6 @@ export default class DataFetch { loading: true, cursors: [], cursor: null, - sortOrder, - sortColumn, })) // Actually fetch data @@ -351,6 +354,14 @@ export default class DataFetch { const entries = Object.entries(newOptions || {}) for (let [key, value] of entries) { if (JSON.stringify(value) !== JSON.stringify(this.options[key])) { + console.log( + key, + "is different", + "new", + value, + "vs old", + this.options[key] + ) refresh = true break } diff --git a/packages/frontend-core/src/fetch/QueryFetch.js b/packages/frontend-core/src/fetch/QueryFetch.js index 456abaec79..6420893515 100644 --- a/packages/frontend-core/src/fetch/QueryFetch.js +++ b/packages/frontend-core/src/fetch/QueryFetch.js @@ -29,6 +29,10 @@ export default class QueryFetch extends DataFetch { } } + getDefaultSortColumn() { + return null + } + async getData() { const { datasource, limit, paginate } = this.options const { supportsPagination } = this.features diff --git a/packages/frontend-core/src/fetch/ViewV2Fetch.js b/packages/frontend-core/src/fetch/ViewV2Fetch.js index d8b96df7ae..b9eaf4bdf7 100644 --- a/packages/frontend-core/src/fetch/ViewV2Fetch.js +++ b/packages/frontend-core/src/fetch/ViewV2Fetch.js @@ -4,9 +4,6 @@ import { get } from "svelte/store" export default class ViewV2Fetch extends DataFetch { determineFeatureFlags() { return { - // The API does not actually support dynamic filtering, but since views - // have filters built in we don't want to perform client side filtering - // which would happen if we marked this as false supportsSearch: true, supportsSort: true, supportsPagination: true, @@ -33,18 +30,23 @@ export default class ViewV2Fetch extends DataFetch { } } + getDefaultSortColumn() { + return null + } + async getData() { const { datasource, limit, sortColumn, sortOrder, sortType, paginate } = this.options - const { cursor } = get(this.store) + const { cursor, query } = get(this.store) try { const res = await this.API.viewV2.fetch({ viewId: datasource.id, + query, paginate, limit, bookmark: cursor, sort: sortColumn, - sortOrder, + sortOrder: sortOrder?.toLowerCase(), sortType, }) return { diff --git a/packages/server/src/api/controllers/row/views.ts b/packages/server/src/api/controllers/row/views.ts index 455a4c0aa2..578c775a55 100644 --- a/packages/server/src/api/controllers/row/views.ts +++ b/packages/server/src/api/controllers/row/views.ts @@ -6,9 +6,11 @@ import { SearchViewRowRequest, RequiredKeys, SearchParams, + SearchFilters, } from "@budibase/types" import { dataFilters } from "@budibase/shared-core" import sdk from "../../../sdk" +import { db } from "@budibase/backend-core" export async function searchView( ctx: UserCtx @@ -19,15 +21,37 @@ export async function searchView( if (!view) { ctx.throw(404, `View ${viewId} not found`) } - if (view.version !== 2) { ctx.throw(400, `This method only supports viewsV2`) } const viewFields = Object.keys(view.schema || {}) - const { body } = ctx.request - const query = dataFilters.buildLuceneQuery(view.query || []) + + // Enrich saved query with ephemeral query params. + // We prevent searching on any fields that are saved as part of the query, as + // that could let users find rows they should not be allowed to access. + let query = dataFilters.buildLuceneQuery(view.query || []) + if (body.query) { + // Extract existing fields + const existingFields = + view.query + ?.filter(filter => filter.field) + .map(filter => db.removeKeyNumbering(filter.field)) || [] + + // Prevent using an "OR" search + delete body.query.allOr + + // Carry over filters for unused fields + Object.keys(body.query).forEach(key => { + const operator = key as keyof Omit + Object.keys(body.query[operator] || {}).forEach(field => { + if (!existingFields.includes(db.removeKeyNumbering(field))) { + query[operator]![field] = body.query[operator]![field] + } + }) + }) + } const searchOptions: RequiredKeys & RequiredKeys> = { diff --git a/packages/types/src/api/web/app/rows.ts b/packages/types/src/api/web/app/rows.ts index 2b51c7b203..a99ef0e837 100644 --- a/packages/types/src/api/web/app/rows.ts +++ b/packages/types/src/api/web/app/rows.ts @@ -16,7 +16,13 @@ export interface SearchRowRequest extends Omit {} export interface SearchViewRowRequest extends Pick< SearchRowRequest, - "sort" | "sortOrder" | "sortType" | "limit" | "bookmark" | "paginate" + | "sort" + | "sortOrder" + | "sortType" + | "limit" + | "bookmark" + | "paginate" + | "query" > {} export interface SearchRowResponse { From 729b93532b0fd9a65af497a914e350788f743dac Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 21 Aug 2023 12:01:17 +0100 Subject: [PATCH 099/316] Prevent overriding onEmptyFilter behaviour for views --- packages/server/src/api/controllers/row/views.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/server/src/api/controllers/row/views.ts b/packages/server/src/api/controllers/row/views.ts index 578c775a55..36a0b588b6 100644 --- a/packages/server/src/api/controllers/row/views.ts +++ b/packages/server/src/api/controllers/row/views.ts @@ -39,12 +39,16 @@ export async function searchView( ?.filter(filter => filter.field) .map(filter => db.removeKeyNumbering(filter.field)) || [] - // Prevent using an "OR" search + // Delete extraneous search params that cannot be overridden delete body.query.allOr + delete body.query.onEmptyFilter // Carry over filters for unused fields Object.keys(body.query).forEach(key => { - const operator = key as keyof Omit + const operator = key as keyof Omit< + SearchFilters, + "allOr" | "onEmptyFilter" + > Object.keys(body.query[operator] || {}).forEach(field => { if (!existingFields.includes(db.removeKeyNumbering(field))) { query[operator]![field] = body.query[operator]![field] From 9c5ff69213beae406f58bbd2f0c7f18558593f2d Mon Sep 17 00:00:00 2001 From: melohagan <101575380+melohagan@users.noreply.github.com> Date: Mon, 21 Aug 2023 12:45:26 +0100 Subject: [PATCH 100/316] Return the actual objects (#11570) --- packages/bbui/src/Table/Table.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bbui/src/Table/Table.svelte b/packages/bbui/src/Table/Table.svelte index 3a86295178..529d1144ee 100644 --- a/packages/bbui/src/Table/Table.svelte +++ b/packages/bbui/src/Table/Table.svelte @@ -215,7 +215,7 @@ const nameA = getDisplayName(a) const nameB = getDisplayName(b) if (orderA !== orderB) { - return orderA < orderB ? orderA : orderB + return orderA < orderB ? a : b } return nameA < nameB ? a : b }) From 27bd653b02753900c58bf59cb387078f9c9fde50 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Mon, 21 Aug 2023 11:45:46 +0000 Subject: [PATCH 101/316] Bump version to 2.9.30 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index c3c9741bd0..1af0927e16 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.9.29", + "version": "2.9.30", "npmClient": "yarn", "packages": [ "packages/*" From cad624a9a41f7b2b96821bf70aa21755bbd0bded Mon Sep 17 00:00:00 2001 From: Dean Date: Mon, 21 Aug 2023 12:50:16 +0100 Subject: [PATCH 102/316] Merge Commit --- packages/bbui/src/Drawer/Drawer.svelte | 7 +++++++ packages/bbui/src/Form/Core/Combobox.svelte | 15 ++++----------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/bbui/src/Drawer/Drawer.svelte b/packages/bbui/src/Drawer/Drawer.svelte index 4ff4df854b..421d12615f 100644 --- a/packages/bbui/src/Drawer/Drawer.svelte +++ b/packages/bbui/src/Drawer/Drawer.svelte @@ -4,6 +4,8 @@ import Body from "../Typography/Body.svelte" import Heading from "../Typography/Heading.svelte" import { setContext } from "svelte" + import { createEventDispatcher } from "svelte" + import { generate } from "shortid" export let title export let fillWidth @@ -11,13 +13,17 @@ export let width = "calc(100% - 626px)" export let headless = false + const dispatch = createEventDispatcher() + let visible = false + let drawerId = generate() export function show() { if (visible) { return } visible = true + dispatch("drawerShow", drawerId) } export function hide() { @@ -25,6 +31,7 @@ return } visible = false + dispatch("drawerHide", drawerId) } setContext("drawer-actions", { diff --git a/packages/bbui/src/Form/Core/Combobox.svelte b/packages/bbui/src/Form/Core/Combobox.svelte index b68a24d8db..b1b264a9b7 100644 --- a/packages/bbui/src/Form/Core/Combobox.svelte +++ b/packages/bbui/src/Form/Core/Combobox.svelte @@ -2,8 +2,8 @@ import "@spectrum-css/inputgroup/dist/index-vars.css" import "@spectrum-css/popover/dist/index-vars.css" import "@spectrum-css/menu/dist/index-vars.css" - import { fly } from "svelte/transition" import { createEventDispatcher } from "svelte" + import clickOutside from "../../Actions/click_outside" export let value = null export let id = null @@ -80,10 +80,11 @@ {#if open} -
(open = false)} />
{ + open = false + }} >
    {#if options && Array.isArray(options)} @@ -125,14 +126,6 @@ .spectrum-Textfield-input { width: 0; } - .overlay { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - z-index: 999; - } .spectrum-Popover { max-height: 240px; width: 100%; From 5abab4cb625a11b7b04fd00997e1dc4ed1ccf1cb Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 21 Aug 2023 16:11:37 +0100 Subject: [PATCH 103/316] Update grids to allow filtering and sorting in client lib with V2 views --- .../src/components/grid/stores/table.js | 17 ++- .../src/components/grid/stores/viewV2.js | 115 ++++++++++++------ 2 files changed, 94 insertions(+), 38 deletions(-) diff --git a/packages/frontend-core/src/components/grid/stores/table.js b/packages/frontend-core/src/components/grid/stores/table.js index 6c877e353a..a881f45b5f 100644 --- a/packages/frontend-core/src/components/grid/stores/table.js +++ b/packages/frontend-core/src/components/grid/stores/table.js @@ -59,7 +59,16 @@ export const createActions = context => { } export const initialise = context => { - const { datasource, fetch, filter, sort, table } = context + const { + datasource, + fetch, + filter, + sort, + table, + initialFilter, + initialSortColumn, + initialSortOrder, + } = context // Keep a list of subscriptions so that we can clear them when the datasource // config changes @@ -75,10 +84,10 @@ export const initialise = context => { } // Wipe state - filter.set([]) + filter.set(get(initialFilter)) sort.set({ - column: null, - order: "ascending", + column: get(initialSortColumn), + order: get(initialSortOrder) || "ascending", }) // Update fetch when filter changes diff --git a/packages/frontend-core/src/components/grid/stores/viewV2.js b/packages/frontend-core/src/components/grid/stores/viewV2.js index f93dc57b69..59bf747330 100644 --- a/packages/frontend-core/src/components/grid/stores/viewV2.js +++ b/packages/frontend-core/src/components/grid/stores/viewV2.js @@ -69,8 +69,20 @@ export const createActions = context => { } export const initialise = context => { - const { definition, datasource, sort, rows, filter, subscribe, viewV2 } = - context + const { + definition, + datasource, + sort, + rows, + filter, + subscribe, + viewV2, + initialFilter, + initialSortColumn, + initialSortOrder, + config, + fetch, + } = context // Keep a list of subscriptions so that we can clear them when the datasource // config changes @@ -86,10 +98,10 @@ export const initialise = context => { } // Reset state for new view - filter.set([]) + filter.set(get(initialFilter)) sort.set({ - column: null, - order: "ascending", + column: get(initialSortColumn), + order: get(initialSortOrder) || "ascending", }) // Keep sort and filter state in line with the view definition @@ -98,34 +110,55 @@ export const initialise = context => { if ($definition?.id !== $datasource.id) { return } - sort.set({ - column: $definition.sort?.field, - order: $definition.sort?.order || "ascending", - }) - filter.set($definition.query || []) + // Only override sorting if we don't have an initial sort column + if (!get(initialSortColumn)) { + sort.set({ + column: $definition.sort?.field, + order: $definition.sort?.order || "ascending", + }) + } + // Only override filter state if we don't have an initial filter + if (!get(initialFilter)) { + filter.set($definition.query) + } }) ) // When sorting changes, ensure view definition is kept up to date unsubscribers.push( sort.subscribe(async $sort => { - // Ensure we're updating the correct view - const $view = get(definition) - if ($view?.id !== $datasource.id) { - return + // If we can mutate schema then update the view definition + if (get(config).canSaveSchema) { + // Ensure we're updating the correct view + const $view = get(definition) + if ($view?.id !== $datasource.id) { + return + } + if ( + $sort?.column !== $view.sort?.field || + $sort?.order !== $view.sort?.order + ) { + await datasource.actions.saveDefinition({ + ...$view, + sort: { + field: $sort.column, + order: $sort.order || "ascending", + }, + }) + await rows.actions.refreshData() + } } - if ( - $sort?.column !== $view.sort?.field || - $sort?.order !== $view.sort?.order - ) { - await datasource.actions.saveDefinition({ - ...$view, - sort: { - field: $sort.column, - order: $sort.order || "ascending", - }, + // Otherwise just update the fetch + else { + // Ensure we're updating the correct fetch + const $fetch = get(fetch) + if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) { + return + } + $fetch.update({ + sortOrder: $sort.order || "ascending", + sortColumn: $sort.column, }) - await rows.actions.refreshData() } }) ) @@ -133,17 +166,31 @@ export const initialise = context => { // When filters change, ensure view definition is kept up to date unsubscribers?.push( filter.subscribe(async $filter => { - // Ensure we're updating the correct view - const $view = get(definition) - if ($view?.id !== $datasource.id) { - return + // If we can mutate schema then update the view definition + if (get(config).canSaveSchema) { + // Ensure we're updating the correct view + const $view = get(definition) + if ($view?.id !== $datasource.id) { + return + } + if (JSON.stringify($filter) !== JSON.stringify($view.query)) { + await datasource.actions.saveDefinition({ + ...$view, + query: $filter, + }) + await rows.actions.refreshData() + } } - if (JSON.stringify($filter) !== JSON.stringify($view.query)) { - await datasource.actions.saveDefinition({ - ...$view, - query: $filter, + // Otherwise just update the fetch + else { + // Ensure we're updating the correct fetch + const $fetch = get(fetch) + if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) { + return + } + $fetch.update({ + filter: $filter, }) - await rows.actions.refreshData() } }) ) From 59559ee93c4e2b98cd18f1db891f785af9e43bfe Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 21 Aug 2023 16:53:09 +0100 Subject: [PATCH 104/316] Remove log and account for onEmptyFilter in grid button --- .../backend/DataTable/buttons/TableFilterButton.svelte | 9 ++------- packages/frontend-core/src/fetch/DataFetch.js | 8 -------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte index b96738ab1a..23f6d1dea1 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte @@ -15,6 +15,7 @@ $: tempValue = filters || [] $: schemaFields = Object.values(schema || {}) $: text = getText(filters) + $: selected = tempValue.filter(x => !x.onEmptyFilter)?.length > 0 const getText = filters => { const count = filters?.filter(filter => filter.field)?.length @@ -22,13 +23,7 @@ } - 0} -> + {text} diff --git a/packages/frontend-core/src/fetch/DataFetch.js b/packages/frontend-core/src/fetch/DataFetch.js index cd12535ddc..857072601e 100644 --- a/packages/frontend-core/src/fetch/DataFetch.js +++ b/packages/frontend-core/src/fetch/DataFetch.js @@ -354,14 +354,6 @@ export default class DataFetch { const entries = Object.entries(newOptions || {}) for (let [key, value] of entries) { if (JSON.stringify(value) !== JSON.stringify(this.options[key])) { - console.log( - key, - "is different", - "new", - value, - "vs old", - this.options[key] - ) refresh = true break } From 10cbf4f08a887d17a570715673b09a9c456fb100 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 21 Aug 2023 17:49:21 +0100 Subject: [PATCH 105/316] Update grid sorting settings to make order conditional on column --- packages/client/manifest.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 7be23cda77..cc3100acdf 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -5583,7 +5583,8 @@ "label": "Sort order", "key": "initialSortOrder", "options": ["Ascending", "Descending"], - "defaultValue": "Ascending" + "defaultValue": "Ascending", + "dependsOn": "initialSortColumn" }, { "type": "select", From c7d1010ce316695de8a37d0aaaf92641fe2d8dad Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 21 Aug 2023 17:49:54 +0100 Subject: [PATCH 106/316] Fix resetting sort column when sort column doesn't exist in schema whenever schema is yet to be loaded --- packages/frontend-core/src/components/grid/stores/sort.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend-core/src/components/grid/stores/sort.js b/packages/frontend-core/src/components/grid/stores/sort.js index 9b2dbfb8df..734a876eed 100644 --- a/packages/frontend-core/src/components/grid/stores/sort.js +++ b/packages/frontend-core/src/components/grid/stores/sort.js @@ -31,10 +31,10 @@ export const initialise = context => { const sortColumnExists = derived( [sort, definition], ([$sort, $definition]) => { - if (!$sort?.column) { + if (!$sort?.column || !$definition) { return true } - return $definition?.schema?.[$sort.column] != null + return $definition.schema?.[$sort.column] != null } ) From c936304410e44adad38091379a63a62b7540f4cb Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 22 Aug 2023 11:31:25 +0100 Subject: [PATCH 107/316] Fix grid config store so that schema overrides work --- .../backend/DataTable/TableDataTable.svelte | 4 +-- .../components/grid/cells/HeaderCell.svelte | 1 - .../src/components/grid/stores/columns.js | 3 -- .../src/components/grid/stores/config.js | 36 ++++++++++--------- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte index 109c965271..58e0a0e691 100644 --- a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte @@ -57,8 +57,8 @@ Use as display column diff --git a/packages/frontend-core/src/components/grid/stores/columns.js b/packages/frontend-core/src/components/grid/stores/columns.js index 8059bd98d7..09e25586fd 100644 --- a/packages/frontend-core/src/components/grid/stores/columns.js +++ b/packages/frontend-core/src/components/grid/stores/columns.js @@ -80,9 +80,6 @@ export const createActions = context => { // Updates the datasources primary display column const changePrimaryDisplay = async column => { - if (!get(config).canEditPrimaryDisplay) { - return - } return await datasource.actions.saveDefinition({ ...get(definition), primaryDisplay: column, diff --git a/packages/frontend-core/src/components/grid/stores/config.js b/packages/frontend-core/src/components/grid/stores/config.js index 46bff299d4..ae995b4ac7 100644 --- a/packages/frontend-core/src/components/grid/stores/config.js +++ b/packages/frontend-core/src/components/grid/stores/config.js @@ -1,8 +1,8 @@ import { derivedMemo } from "../../../utils" import { derived } from "svelte/store" -export const deriveStores = context => { - const { props, hasNonAutoColumn } = context +export const createStores = context => { + const { props } = context const getProp = prop => derivedMemo(props, $props => $props[prop]) // Derive and memoize some props so that we can react to them in isolation @@ -16,16 +16,27 @@ export const deriveStores = context => { const notifySuccess = getProp("notifySuccess") const notifyError = getProp("notifyError") + return { + datasource, + initialSortColumn, + initialSortOrder, + initialFilter, + fixedRowHeight, + schemaOverrides, + columnWhitelist, + notifySuccess, + notifyError, + } +} + +export const deriveStores = context => { + const { props, hasNonAutoColumn } = context + // Derive features const config = derived( [props, hasNonAutoColumn], ([$props, $hasNonAutoColumn]) => { - let config = { - ...$props, - - // Additional granular features which we don't expose as props - canEditPrimaryDisplay: $props.canEditColumns, - } + let config = { ...$props } // Disable some features if we're editing a view if ($props.datasource?.type === "viewV2") { @@ -43,14 +54,5 @@ export const deriveStores = context => { return { config, - datasource, - initialSortColumn, - initialSortOrder, - initialFilter, - fixedRowHeight, - schemaOverrides, - columnWhitelist, - notifySuccess, - notifyError, } } From eb7c12ba09b3817ca3c5fd63220d1533cb4ae701 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 22 Aug 2023 11:31:38 +0100 Subject: [PATCH 108/316] Prevent editing columns in grids in client apps --- packages/client/src/components/app/GridBlock.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/src/components/app/GridBlock.svelte b/packages/client/src/components/app/GridBlock.svelte index a7c3b8eef2..9bdea52124 100644 --- a/packages/client/src/components/app/GridBlock.svelte +++ b/packages/client/src/components/app/GridBlock.svelte @@ -50,6 +50,7 @@ canAddRows={allowAddRows} canEditRows={allowEditRows} canDeleteRows={allowDeleteRows} + canEditColumns={false} canExpandRows={false} canSaveSchema={false} showControls={false} From 51f6574942a8bc20dc50d4c7807fd3f8ef910344 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 22 Aug 2023 11:32:57 +0100 Subject: [PATCH 109/316] Fix issue with tab styles --- packages/bbui/src/Tabs/Tabs.svelte | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/bbui/src/Tabs/Tabs.svelte b/packages/bbui/src/Tabs/Tabs.svelte index 9c3d25a807..c94b396398 100644 --- a/packages/bbui/src/Tabs/Tabs.svelte +++ b/packages/bbui/src/Tabs/Tabs.svelte @@ -57,10 +57,8 @@ function calculateIndicatorLength() { if (!vertical) { width = $tab.info?.width + "px" - height = $tab.info?.height } else { height = $tab.info?.height + 4 + "px" - width = $tab.info?.width } } From e77644ce11ba0a4a9f8193b0989f4e651820b05f Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 22 Aug 2023 11:48:06 +0100 Subject: [PATCH 110/316] Lint --- .../components/backend/DataTable/ViewV2DataTable.svelte | 2 +- .../DataTable/buttons/grid/GridManageAccessButton.svelte | 8 ++++---- .../frontend-core/src/components/grid/stores/columns.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte index 5cb65e340f..0c6a0cca9a 100644 --- a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte @@ -4,7 +4,7 @@ import { API } from "api" import GridCreateEditRowModal from "components/backend/DataTable/modals/grid/GridCreateEditRowModal.svelte" import GridFilterButton from "components/backend/DataTable/buttons/grid/GridFilterButton.svelte" - import GridManageAccessButton from "components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte"; + import GridManageAccessButton from "components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte" $: id = $viewsV2.selected?.id $: datasource = { diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte index 8c7eb49923..0cd008bab1 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte @@ -7,10 +7,10 @@ $: resourceId = getResourceID($datasource) const getResourceID = datasource => { - if (!datasource) { - return null - } - return datasource.type === "table" ? datasource.tableId : datasource.id + if (!datasource) { + return null + } + return datasource.type === "table" ? datasource.tableId : datasource.id } diff --git a/packages/frontend-core/src/components/grid/stores/columns.js b/packages/frontend-core/src/components/grid/stores/columns.js index 09e25586fd..a5d90adcfe 100644 --- a/packages/frontend-core/src/components/grid/stores/columns.js +++ b/packages/frontend-core/src/components/grid/stores/columns.js @@ -69,7 +69,7 @@ export const deriveStores = context => { } export const createActions = context => { - const { columns, stickyColumn, config, datasource, definition } = context + const { columns, stickyColumn, datasource, definition } = context // Checks if we have a certain column by name const hasColumn = column => { From df676bbe9e55c8f1a42d61878f13fbe55c605308 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 22 Aug 2023 13:39:58 +0100 Subject: [PATCH 111/316] Update table block to work with views --- packages/client/src/components/app/blocks/TableBlock.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/app/blocks/TableBlock.svelte b/packages/client/src/components/app/blocks/TableBlock.svelte index efc83fd5ac..c2a13ddef2 100644 --- a/packages/client/src/components/app/blocks/TableBlock.svelte +++ b/packages/client/src/components/app/blocks/TableBlock.svelte @@ -53,7 +53,8 @@ $: editTitle = getEditTitle(detailsFormBlockId, primaryDisplay) $: normalFields = getNormalFields(schema) $: rowClickActions = - clickBehaviour === "actions" || dataSource?.type !== "table" + clickBehaviour === "actions" || + (dataSource?.type !== "table" && dataSource?.type !== "viewV2") ? onClick : [ { From 4192618bdf1df82da3239d86377d2b34343c12b5 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 23 Aug 2023 18:56:04 +0100 Subject: [PATCH 112/316] Handle missing required columns in views by showing errors --- .../src/components/grid/stores/columns.js | 8 -------- .../src/components/grid/stores/datasource.js | 6 ++++++ .../frontend-core/src/components/grid/stores/rows.js | 2 +- .../src/components/grid/stores/table.js | 9 ++++++++- .../src/components/grid/stores/viewV2.js | 12 +++++++++++- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/packages/frontend-core/src/components/grid/stores/columns.js b/packages/frontend-core/src/components/grid/stores/columns.js index a5d90adcfe..629d5dd893 100644 --- a/packages/frontend-core/src/components/grid/stores/columns.js +++ b/packages/frontend-core/src/components/grid/stores/columns.js @@ -71,13 +71,6 @@ export const deriveStores = context => { export const createActions = context => { const { columns, stickyColumn, datasource, definition } = context - // Checks if we have a certain column by name - const hasColumn = column => { - const $columns = get(columns) - const $sticky = get(stickyColumn) - return $columns.some(col => col.name === column) || $sticky?.name === column - } - // Updates the datasources primary display column const changePrimaryDisplay = async column => { return await datasource.actions.saveDefinition({ @@ -140,7 +133,6 @@ export const createActions = context => { columns: { ...columns, actions: { - hasColumn, saveChanges, changePrimaryDisplay, changeAllColumnWidths, diff --git a/packages/frontend-core/src/components/grid/stores/datasource.js b/packages/frontend-core/src/components/grid/stores/datasource.js index 61caa79734..3f4347953e 100644 --- a/packages/frontend-core/src/components/grid/stores/datasource.js +++ b/packages/frontend-core/src/components/grid/stores/datasource.js @@ -108,6 +108,11 @@ export const createActions = context => { return getAPI()?.actions.isDatasourceValid(datasource) } + // Checks if this datasource can use a specific column by name + const canUseColumn = name => { + return getAPI()?.actions.canUseColumn(name) + } + return { datasource: { ...datasource, @@ -119,6 +124,7 @@ export const createActions = context => { deleteRows, getRow, isDatasourceValid, + canUseColumn, }, }, } diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index f595276e73..392bf392e8 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -192,7 +192,7 @@ export const createActions = context => { let erroredColumns = [] let missingColumns = [] for (let column of keys) { - if (columns.actions.hasColumn(column)) { + if (datasource.actions.canUseColumn(column)) { erroredColumns.push(column) } else { missingColumns.push(column) diff --git a/packages/frontend-core/src/components/grid/stores/table.js b/packages/frontend-core/src/components/grid/stores/table.js index a881f45b5f..ed13609f45 100644 --- a/packages/frontend-core/src/components/grid/stores/table.js +++ b/packages/frontend-core/src/components/grid/stores/table.js @@ -3,7 +3,7 @@ import { get } from "svelte/store" const SuppressErrors = true export const createActions = context => { - const { definition, API, datasource } = context + const { definition, API, datasource, columns, stickyColumn } = context const refreshDefinition = async () => { definition.set(await API.fetchTableDefinition(get(datasource).tableId)) @@ -43,6 +43,12 @@ export const createActions = context => { return res?.rows?.[0] } + const canUseColumn = name => { + const $columns = get(columns) + const $sticky = get(stickyColumn) + return $columns.some(col => col.name === name) || $sticky?.name === name + } + return { table: { actions: { @@ -53,6 +59,7 @@ export const createActions = context => { deleteRows, getRow, isDatasourceValid, + canUseColumn, }, }, } diff --git a/packages/frontend-core/src/components/grid/stores/viewV2.js b/packages/frontend-core/src/components/grid/stores/viewV2.js index 59bf747330..8f0e07c0de 100644 --- a/packages/frontend-core/src/components/grid/stores/viewV2.js +++ b/packages/frontend-core/src/components/grid/stores/viewV2.js @@ -3,7 +3,7 @@ import { get } from "svelte/store" const SuppressErrors = true export const createActions = context => { - const { definition, API, datasource } = context + const { definition, API, datasource, columns, stickyColumn } = context const refreshDefinition = async () => { const $datasource = get(datasource) @@ -53,6 +53,15 @@ export const createActions = context => { ) } + const canUseColumn = name => { + const $columns = get(columns) + const $sticky = get(stickyColumn) + return ( + $columns.some(col => col.name === name && col.visible) || + $sticky?.name === name + ) + } + return { viewV2: { actions: { @@ -63,6 +72,7 @@ export const createActions = context => { deleteRows, getRow, isDatasourceValid, + canUseColumn, }, }, } From 9a15277fa1474dde4f435f189acc992000e3643c Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 24 Aug 2023 09:11:54 +0200 Subject: [PATCH 113/316] Split authorized middleware to handle resource id fetch --- packages/server/src/api/routes/row.ts | 6 ++++-- packages/server/src/middleware/authorized.ts | 14 +++++++++++++- packages/server/src/middleware/resourceId.ts | 1 + 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/server/src/api/routes/row.ts b/packages/server/src/api/routes/row.ts index a4ac8aa3ee..60bc4e0735 100644 --- a/packages/server/src/api/routes/row.ts +++ b/packages/server/src/api/routes/row.ts @@ -1,10 +1,11 @@ import Router from "@koa/router" import * as rowController from "../controllers/row" -import authorized from "../../middleware/authorized" +import authorized, { authorizedResource } from "../../middleware/authorized" import { paramResource, paramSubResource } from "../../middleware/resourceId" import { permissions } from "@budibase/backend-core" import { internalSearchValidator } from "./utils/validators" import trimViewRowInfo from "../../middleware/trimViewRowInfo" +import { extractViewInfoFromID } from "../../db/utils" const { PermissionType, PermissionLevel } = permissions const router: Router = new Router() @@ -269,7 +270,8 @@ router router.post( "/api/v2/views/:viewId/search", - authorized(PermissionType.TABLE, PermissionLevel.READ), + paramResource("viewId", val => extractViewInfoFromID(val).tableId), + authorizedResource(PermissionType.TABLE, PermissionLevel.READ), rowController.views.searchView ) diff --git a/packages/server/src/middleware/authorized.ts b/packages/server/src/middleware/authorized.ts index 915344f747..930fb0f0ea 100644 --- a/packages/server/src/middleware/authorized.ts +++ b/packages/server/src/middleware/authorized.ts @@ -74,7 +74,8 @@ const checkAuthorizedResource = async ( } } -export default ( +const authorized = + ( permType: PermissionType, permLevel?: PermissionLevel, opts = { schema: false } @@ -143,3 +144,14 @@ export default ( // csrf protection return csrf(ctx, next) } + +export default ( + permType: PermissionType, + permLevel?: PermissionLevel, + opts = { schema: false } +) => authorized(permType, permLevel, opts) + +export const authorizedResource = ( + permType: PermissionType, + permLevel?: PermissionLevel +) => authorized(permType, permLevel) diff --git a/packages/server/src/middleware/resourceId.ts b/packages/server/src/middleware/resourceId.ts index 0917941061..1ad0b2a0c1 100644 --- a/packages/server/src/middleware/resourceId.ts +++ b/packages/server/src/middleware/resourceId.ts @@ -43,6 +43,7 @@ export class ResourceIdGetter { } } +/** @deprecated we should use the authorizedResource middleware instead */ export function paramResource(main: string) { return new ResourceIdGetter("params").mainResource(main).build() } From b3802070645278b13a37793e53cc027d4ed801c1 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 24 Aug 2023 09:36:44 +0200 Subject: [PATCH 114/316] Merge resource and authorized, allowing transformers --- packages/server/src/api/routes/row.ts | 8 ++++++-- packages/server/src/middleware/authorized.ts | 21 +++++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/server/src/api/routes/row.ts b/packages/server/src/api/routes/row.ts index 60bc4e0735..9f4beb8173 100644 --- a/packages/server/src/api/routes/row.ts +++ b/packages/server/src/api/routes/row.ts @@ -270,8 +270,12 @@ router router.post( "/api/v2/views/:viewId/search", - paramResource("viewId", val => extractViewInfoFromID(val).tableId), - authorizedResource(PermissionType.TABLE, PermissionLevel.READ), + authorizedResource( + PermissionType.TABLE, + PermissionLevel.READ, + "viewId", + val => extractViewInfoFromID(val).tableId + ), rowController.views.searchView ) diff --git a/packages/server/src/middleware/authorized.ts b/packages/server/src/middleware/authorized.ts index 930fb0f0ea..a754bcc1f0 100644 --- a/packages/server/src/middleware/authorized.ts +++ b/packages/server/src/middleware/authorized.ts @@ -8,6 +8,7 @@ import { import { PermissionLevel, PermissionType, Role, UserCtx } from "@budibase/types" import builderMiddleware from "./builder" import { isWebhookEndpoint } from "./utils" +import { paramResource } from "./resourceId" function hasResource(ctx: any) { return ctx.resourceId != null @@ -78,7 +79,8 @@ const authorized = ( permType: PermissionType, permLevel?: PermissionLevel, - opts = { schema: false } + opts = { schema: false }, + resourceId?: { path: string; transformer?: (val: string) => string } ) => async (ctx: any, next: any) => { // webhooks don't need authentication, each webhook unique @@ -99,6 +101,15 @@ const authorized = ? PermissionLevel.WRITE : PermissionLevel.READ const appId = context.getAppId() + + if (resourceId?.path) { + // Reusing the existing middleware to extract the value + paramResource(resourceId.path)(ctx, () => {}) + if (resourceId.transformer) { + ctx.resourceId = resourceId.transformer(ctx.resourceId) + } + } + if (appId && hasResource(ctx)) { resourceRoles = await roles.getRequiredResourceRole(permLevel!, ctx) if (opts && opts.schema) { @@ -153,5 +164,9 @@ export default ( export const authorizedResource = ( permType: PermissionType, - permLevel?: PermissionLevel -) => authorized(permType, permLevel) + permLevel: PermissionLevel, + path: string, + transformer?: (val: string) => string +) => { + return authorized(permType, permLevel, undefined, { path, transformer }) +} From 972cc9916babfc6427624a0e042142f6985aecf6 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 24 Aug 2023 09:39:38 +0200 Subject: [PATCH 115/316] Add inheritance tests --- .../src/api/routes/tests/permissions.spec.ts | 26 +++++++++++++++++++ .../server/src/tests/utilities/api/viewV2.ts | 8 ++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/packages/server/src/api/routes/tests/permissions.spec.ts b/packages/server/src/api/routes/tests/permissions.spec.ts index 118d35f8fd..3437f65a46 100644 --- a/packages/server/src/api/routes/tests/permissions.spec.ts +++ b/packages/server/src/api/routes/tests/permissions.spec.ts @@ -12,6 +12,7 @@ import { PermissionLevel, Row, Table, + ViewV2, } from "@budibase/types" import * as setup from "./utilities" @@ -27,6 +28,7 @@ describe("/permission", () => { let table: Table & { _id: string } let perms: Document[] let row: Row + let view: ViewV2 afterAll(setup.afterAll) @@ -39,6 +41,7 @@ describe("/permission", () => { table = (await config.createTable()) as typeof table row = await config.createRow() + view = await config.api.viewV2.create({ tableId: table._id }) perms = await config.api.permission.set({ roleId: STD_ROLE_ID, resourceId: table._id, @@ -162,6 +165,29 @@ describe("/permission", () => { expect(res.body[0]._id).toEqual(row._id) }) + it("should be able to access the view data when the table is set to public and with no view permissions overrides", async () => { + // replicate changes before checking permissions + await config.publish() + + const res = await config.api.viewV2.search(view.id, undefined, { + usePublicUser: true, + }) + expect(res.body.rows[0]._id).toEqual(row._id) + }) + + it("should be able to access the view data when the table is set to public and with no view permissions overrides", async () => { + await config.api.permission.revoke({ + roleId: STD_ROLE_ID, + resourceId: table._id, + level: PermissionLevel.READ, + }) + + await config.api.viewV2.search(view.id, undefined, { + expectStatus: 403, + usePublicUser: true, + }) + }) + it("shouldn't allow writing from a public user", async () => { const res = await request .post(`/api/${table._id}/rows`) diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index 1520154641..bba65e187f 100644 --- a/packages/server/src/tests/utilities/api/viewV2.ts +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -77,12 +77,16 @@ export class ViewV2API extends TestAPI { search = async ( viewId: string, params?: SearchViewRowRequest, - { expectStatus } = { expectStatus: 200 } + { expectStatus = 200, usePublicUser = false } = {} ) => { return this.request .post(`/api/v2/views/${viewId}/search`) .send(params) - .set(this.config.defaultHeaders()) + .set( + usePublicUser + ? this.config.publicHeaders() + : this.config.defaultHeaders() + ) .expect("Content-Type", /json/) .expect(expectStatus) } From bfa2b491f349030c42c436fcb55a99c23fffa7ef Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 24 Aug 2023 10:22:08 +0200 Subject: [PATCH 116/316] Allow view permission type --- packages/server/src/api/routes/row.ts | 9 ++--- packages/server/src/middleware/authorized.ts | 38 +++++++++++++++----- packages/types/src/sdk/permissions.ts | 1 + 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/packages/server/src/api/routes/row.ts b/packages/server/src/api/routes/row.ts index 9f4beb8173..c29cb65eac 100644 --- a/packages/server/src/api/routes/row.ts +++ b/packages/server/src/api/routes/row.ts @@ -5,7 +5,7 @@ import { paramResource, paramSubResource } from "../../middleware/resourceId" import { permissions } from "@budibase/backend-core" import { internalSearchValidator } from "./utils/validators" import trimViewRowInfo from "../../middleware/trimViewRowInfo" -import { extractViewInfoFromID } from "../../db/utils" + const { PermissionType, PermissionLevel } = permissions const router: Router = new Router() @@ -270,12 +270,7 @@ router router.post( "/api/v2/views/:viewId/search", - authorizedResource( - PermissionType.TABLE, - PermissionLevel.READ, - "viewId", - val => extractViewInfoFromID(val).tableId - ), + authorizedResource(PermissionType.VIEW, PermissionLevel.READ, "viewId"), rowController.views.searchView ) diff --git a/packages/server/src/middleware/authorized.ts b/packages/server/src/middleware/authorized.ts index a754bcc1f0..3d4c44a108 100644 --- a/packages/server/src/middleware/authorized.ts +++ b/packages/server/src/middleware/authorized.ts @@ -6,9 +6,11 @@ import { users, } from "@budibase/backend-core" import { PermissionLevel, PermissionType, Role, UserCtx } from "@budibase/types" +import { features } from "@budibase/pro" import builderMiddleware from "./builder" import { isWebhookEndpoint } from "./utils" import { paramResource } from "./resourceId" +import { extractViewInfoFromID, isViewID } from "../db/utils" function hasResource(ctx: any) { return ctx.resourceId != null @@ -75,12 +77,31 @@ const checkAuthorizedResource = async ( } } +const resourceIdTranformers: Partial< + Record Promise> +> = { + [PermissionType.VIEW]: async ctx => { + const { resourceId } = ctx + if (!isViewID(resourceId)) { + ctx.throw(400, `"${resourceId}" is not a valid viewId`) + } + + if (await features.isViewPermissionEnabled()) { + ctx.subResourceId = ctx.resourceId + ctx.resourceId = extractViewInfoFromID(resourceId).tableId + } else { + ctx.resourceId = extractViewInfoFromID(resourceId).tableId + delete ctx.subResourceId + } + }, +} + const authorized = ( permType: PermissionType, permLevel?: PermissionLevel, opts = { schema: false }, - resourceId?: { path: string; transformer?: (val: string) => string } + resourcePath?: string ) => async (ctx: any, next: any) => { // webhooks don't need authentication, each webhook unique @@ -102,15 +123,15 @@ const authorized = : PermissionLevel.READ const appId = context.getAppId() - if (resourceId?.path) { + if (resourcePath) { // Reusing the existing middleware to extract the value - paramResource(resourceId.path)(ctx, () => {}) - if (resourceId.transformer) { - ctx.resourceId = resourceId.transformer(ctx.resourceId) - } + paramResource(resourcePath)(ctx, () => {}) } if (appId && hasResource(ctx)) { + if (resourceIdTranformers[permType]) { + await resourceIdTranformers[permType]!(ctx) + } resourceRoles = await roles.getRequiredResourceRole(permLevel!, ctx) if (opts && opts.schema) { otherLevelRoles = await roles.getRequiredResourceRole(otherLevel, ctx) @@ -165,8 +186,7 @@ export default ( export const authorizedResource = ( permType: PermissionType, permLevel: PermissionLevel, - path: string, - transformer?: (val: string) => string + resourcePath: string ) => { - return authorized(permType, permLevel, undefined, { path, transformer }) + return authorized(permType, permLevel, undefined, resourcePath) } diff --git a/packages/types/src/sdk/permissions.ts b/packages/types/src/sdk/permissions.ts index 9fe1970e44..a33d4985ee 100644 --- a/packages/types/src/sdk/permissions.ts +++ b/packages/types/src/sdk/permissions.ts @@ -15,4 +15,5 @@ export enum PermissionType { BUILDER = "builder", GLOBAL_BUILDER = "globalBuilder", QUERY = "query", + VIEW = "view", } From 9a7a3b9c7209878ebdce0e4a3b374f0f10aa7d20 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 24 Aug 2023 10:23:21 +0200 Subject: [PATCH 117/316] Rename test --- packages/server/src/api/routes/tests/permissions.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/api/routes/tests/permissions.spec.ts b/packages/server/src/api/routes/tests/permissions.spec.ts index 3437f65a46..5261259b77 100644 --- a/packages/server/src/api/routes/tests/permissions.spec.ts +++ b/packages/server/src/api/routes/tests/permissions.spec.ts @@ -175,7 +175,7 @@ describe("/permission", () => { expect(res.body.rows[0]._id).toEqual(row._id) }) - it("should be able to access the view data when the table is set to public and with no view permissions overrides", async () => { + it("should not be able to access the view data when the table is not public and there are no view permissions overrides", async () => { await config.api.permission.revoke({ roleId: STD_ROLE_ID, resourceId: table._id, From 84a6f239a93c6b96b5a1b409a7ff603077575be9 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 24 Aug 2023 11:05:31 +0200 Subject: [PATCH 118/316] Migrate tests to ts --- ...{authorized.spec.js => authorized.spec.ts} | 91 +++++++++++-------- 1 file changed, 52 insertions(+), 39 deletions(-) rename packages/server/src/middleware/tests/{authorized.spec.js => authorized.spec.ts} (65%) diff --git a/packages/server/src/middleware/tests/authorized.spec.js b/packages/server/src/middleware/tests/authorized.spec.ts similarity index 65% rename from packages/server/src/middleware/tests/authorized.spec.js rename to packages/server/src/middleware/tests/authorized.spec.ts index 3adc4d99a1..900928f0bc 100644 --- a/packages/server/src/middleware/tests/authorized.spec.js +++ b/packages/server/src/middleware/tests/authorized.spec.ts @@ -1,34 +1,40 @@ jest.mock("../../environment", () => ({ - prod: false, - isTest: () => true, - isProd: () => this.prod, - _set: function(key, value) { - this.prod = value === "production" - } - }) -) -const authorizedMiddleware = require("../authorized").default -const env = require("../../environment") -const { PermissionType, PermissionLevel } = require("@budibase/types") + prod: false, + isTest: () => true, + // @ts-ignore + isProd: () => this.prod, + _set: function (key: string, value: string) { + this.prod = value === "production" + }, +})) +import authorizedMiddleware from "../authorized" +import env from "../../environment" +import { PermissionType, PermissionLevel } from "@budibase/types" const APP_ID = "" class TestConfiguration { - constructor(role) { - this.middleware = authorizedMiddleware(role) + middleware: (ctx: any, next: any) => Promise + next: () => void + throw: () => void + headers: Record + ctx: any + + constructor() { + this.middleware = authorizedMiddleware(PermissionType.APP) this.next = jest.fn() this.throw = jest.fn() this.headers = {} this.ctx = { headers: {}, request: { - url: "" + url: "", }, appId: APP_ID, auth: {}, next: this.next, throw: this.throw, - get: (name) => this.headers[name], + get: (name: string) => this.headers[name], } } @@ -36,32 +42,33 @@ class TestConfiguration { return this.middleware(this.ctx, this.next) } - setUser(user) { + setUser(user: any) { this.ctx.user = user } - setMiddlewareRequiredPermission(...perms) { + setMiddlewareRequiredPermission(...perms: any[]) { + // @ts-ignore this.middleware = authorizedMiddleware(...perms) } - setResourceId(id) { + setResourceId(id: string) { this.ctx.resourceId = id } - setAuthenticated(isAuthed) { + setAuthenticated(isAuthed: boolean) { this.ctx.isAuthenticated = isAuthed } - setRequestUrl(url) { + setRequestUrl(url: string) { this.ctx.request.url = url } - setEnvironment(isProd) { + setEnvironment(isProd: boolean) { env._set("NODE_ENV", isProd ? "production" : "jest") } - setRequestHeaders(headers) { - this.ctx.headers = headers + setRequestHeaders(headers: Record) { + this.ctx.headers = headers } afterEach() { @@ -69,10 +76,9 @@ class TestConfiguration { } } - describe("Authorization middleware", () => { const next = jest.fn() - let config + let config: TestConfiguration afterEach(() => { config.afterEach() @@ -83,8 +89,6 @@ describe("Authorization middleware", () => { }) describe("non-webhook call", () => { - let config - beforeEach(() => { config = new TestConfiguration() config.setEnvironment(true) @@ -102,21 +106,21 @@ describe("Authorization middleware", () => { _id: "user", role: { _id: "ADMIN", - } + }, }) await config.executeMiddleware() expect(config.next).toHaveBeenCalled() }) - + it("throws if the user does not have builder permissions", async () => { config.setEnvironment(false) config.setMiddlewareRequiredPermission(PermissionType.BUILDER) config.setUser({ role: { - _id: "" - } + _id: "", + }, }) await config.executeMiddleware() @@ -127,8 +131,8 @@ describe("Authorization middleware", () => { config.setResourceId(PermissionType.QUERY) config.setUser({ role: { - _id: "" - } + _id: "", + }, }) config.setMiddlewareRequiredPermission(PermissionType.QUERY) @@ -139,25 +143,34 @@ describe("Authorization middleware", () => { it("throws if the user session is not authenticated", async () => { config.setUser({ role: { - _id: "" + _id: "", }, }) config.setAuthenticated(false) await config.executeMiddleware() - expect(config.throw).toHaveBeenCalledWith(403, "Session not authenticated") + expect(config.throw).toHaveBeenCalledWith( + 403, + "Session not authenticated" + ) }) it("throws if the user does not have base permissions to perform the operation", async () => { config.setUser({ role: { - _id: "" + _id: "", }, }) - config.setMiddlewareRequiredPermission(PermissionType.ADMIN, PermissionLevel.BASIC) - + config.setMiddlewareRequiredPermission( + PermissionType.APP, + PermissionLevel.READ + ) + await config.executeMiddleware() - expect(config.throw).toHaveBeenCalledWith(403, "User does not have permission") + expect(config.throw).toHaveBeenCalledWith( + 403, + "User does not have permission" + ) }) }) }) From 8359185a22e704146eb7a48fad182241045adfa2 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 24 Aug 2023 11:23:30 +0200 Subject: [PATCH 119/316] Add unhappy paths tests --- packages/server/src/middleware/authorized.ts | 18 ++++--- .../src/middleware/tests/authorized.spec.ts | 49 +++++++++++++++++-- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/packages/server/src/middleware/authorized.ts b/packages/server/src/middleware/authorized.ts index 3d4c44a108..52ced1b9cc 100644 --- a/packages/server/src/middleware/authorized.ts +++ b/packages/server/src/middleware/authorized.ts @@ -82,8 +82,14 @@ const resourceIdTranformers: Partial< > = { [PermissionType.VIEW]: async ctx => { const { resourceId } = ctx + if (!resourceId) { + ctx.throw(400, `Cannot obtain the view id`) + return + } + if (!isViewID(resourceId)) { - ctx.throw(400, `"${resourceId}" is not a valid viewId`) + ctx.throw(400, `"${resourceId}" is not a valid view id`) + return } if (await features.isViewPermissionEnabled()) { @@ -121,17 +127,17 @@ const authorized = permLevel === PermissionLevel.READ ? PermissionLevel.WRITE : PermissionLevel.READ - const appId = context.getAppId() if (resourcePath) { // Reusing the existing middleware to extract the value paramResource(resourcePath)(ctx, () => {}) } - if (appId && hasResource(ctx)) { - if (resourceIdTranformers[permType]) { - await resourceIdTranformers[permType]!(ctx) - } + if (resourceIdTranformers[permType]) { + await resourceIdTranformers[permType]!(ctx) + } + + if (hasResource(ctx)) { resourceRoles = await roles.getRequiredResourceRole(permLevel!, ctx) if (opts && opts.schema) { otherLevelRoles = await roles.getRequiredResourceRole(otherLevel, ctx) diff --git a/packages/server/src/middleware/tests/authorized.spec.ts b/packages/server/src/middleware/tests/authorized.spec.ts index 900928f0bc..4d6f281294 100644 --- a/packages/server/src/middleware/tests/authorized.spec.ts +++ b/packages/server/src/middleware/tests/authorized.spec.ts @@ -3,13 +3,16 @@ jest.mock("../../environment", () => ({ isTest: () => true, // @ts-ignore isProd: () => this.prod, - _set: function (key: string, value: string) { + _set: function (_key: string, value: string) { this.prod = value === "production" }, })) + +import { PermissionType, PermissionLevel } from "@budibase/types" + import authorizedMiddleware from "../authorized" import env from "../../environment" -import { PermissionType, PermissionLevel } from "@budibase/types" +import { generateTableID, generateViewID } from "../../db/utils" const APP_ID = "" @@ -51,7 +54,7 @@ class TestConfiguration { this.middleware = authorizedMiddleware(...perms) } - setResourceId(id: string) { + setResourceId(id?: string) { this.ctx.resourceId = id } @@ -85,6 +88,7 @@ describe("Authorization middleware", () => { }) beforeEach(() => { + jest.clearAllMocks() config = new TestConfiguration() }) @@ -172,5 +176,44 @@ describe("Authorization middleware", () => { "User does not have permission" ) }) + + describe("view type", () => { + const tableId = generateTableID() + const viewId = generateViewID(tableId) + + beforeEach(() => { + config.setMiddlewareRequiredPermission( + PermissionType.VIEW, + PermissionLevel.READ + ) + config.setResourceId(viewId) + + config.setUser({ + role: { + _id: "", + }, + }) + }) + + it("throw an exception if the resource id is not provided", async () => { + config.setResourceId(undefined) + await config.executeMiddleware() + expect(config.throw).toHaveBeenNthCalledWith( + 1, + 400, + "Cannot obtain the view id" + ) + }) + + it("throw an exception if the resource id is not a valid view id", async () => { + config.setResourceId(tableId) + await config.executeMiddleware() + expect(config.throw).toHaveBeenNthCalledWith( + 1, + 400, + `"${tableId}" is not a valid view id` + ) + }) + }) }) }) From cfeb6993cc82a973ac39efb1212ca07eaa85ae5e Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 24 Aug 2023 11:46:36 +0200 Subject: [PATCH 120/316] Test authorised view use cases --- packages/server/src/middleware/authorized.ts | 11 +++- .../src/middleware/tests/authorized.spec.ts | 57 ++++++++++++++++++- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/packages/server/src/middleware/authorized.ts b/packages/server/src/middleware/authorized.ts index 52ced1b9cc..35d373efbf 100644 --- a/packages/server/src/middleware/authorized.ts +++ b/packages/server/src/middleware/authorized.ts @@ -138,9 +138,16 @@ const authorized = } if (hasResource(ctx)) { - resourceRoles = await roles.getRequiredResourceRole(permLevel!, ctx) + const { resourceId, subResourceId } = ctx + resourceRoles = await roles.getRequiredResourceRole(permLevel!, { + resourceId, + subResourceId, + }) if (opts && opts.schema) { - otherLevelRoles = await roles.getRequiredResourceRole(otherLevel, ctx) + otherLevelRoles = await roles.getRequiredResourceRole(otherLevel, { + resourceId, + subResourceId, + }) } } diff --git a/packages/server/src/middleware/tests/authorized.spec.ts b/packages/server/src/middleware/tests/authorized.spec.ts index 4d6f281294..d47430e63f 100644 --- a/packages/server/src/middleware/tests/authorized.spec.ts +++ b/packages/server/src/middleware/tests/authorized.spec.ts @@ -1,3 +1,10 @@ +jest.mock("@budibase/backend-core", () => ({ + ...jest.requireActual("@budibase/backend-core"), + roles: { + ...jest.requireActual("@budibase/backend-core").roles, + getRequiredResourceRole: jest.fn().mockResolvedValue([]), + }, +})) jest.mock("../../environment", () => ({ prod: false, isTest: () => true, @@ -13,9 +20,14 @@ import { PermissionType, PermissionLevel } from "@budibase/types" import authorizedMiddleware from "../authorized" import env from "../../environment" import { generateTableID, generateViewID } from "../../db/utils" +import { roles } from "@budibase/backend-core" +import { mocks } from "@budibase/backend-core/tests" +import { initProMocks } from "../../tests/utilities/mocks/pro" const APP_ID = "" +initProMocks() + class TestConfiguration { middleware: (ctx: any, next: any) => Promise next: () => void @@ -80,7 +92,6 @@ class TestConfiguration { } describe("Authorization middleware", () => { - const next = jest.fn() let config: TestConfiguration afterEach(() => { @@ -89,6 +100,7 @@ describe("Authorization middleware", () => { beforeEach(() => { jest.clearAllMocks() + mocks.licenses.useCloudFree() config = new TestConfiguration() }) @@ -181,6 +193,11 @@ describe("Authorization middleware", () => { const tableId = generateTableID() const viewId = generateViewID(tableId) + const mockedGetRequiredResourceRole = + roles.getRequiredResourceRole as jest.MockedFunction< + typeof roles.getRequiredResourceRole + > + beforeEach(() => { config.setMiddlewareRequiredPermission( PermissionType.VIEW, @@ -188,13 +205,49 @@ describe("Authorization middleware", () => { ) config.setResourceId(viewId) + mockedGetRequiredResourceRole.mockResolvedValue(["PUBLIC"]) + config.setUser({ + _id: "user", role: { - _id: "", + _id: "PUBLIC", }, }) }) + it("will ignore view permissions if flag is off", async () => { + await config.executeMiddleware() + + expect(config.throw).not.toBeCalled() + expect(config.next).toHaveBeenCalled() + + expect(mockedGetRequiredResourceRole).toBeCalledTimes(1) + expect(mockedGetRequiredResourceRole).toBeCalledWith( + PermissionLevel.READ, + expect.objectContaining({ + resourceId: tableId, + subResourceId: undefined, + }) + ) + }) + + it("will use view permissions if flag is on", async () => { + mocks.licenses.useViewPermissions() + await config.executeMiddleware() + + expect(config.throw).not.toBeCalled() + expect(config.next).toHaveBeenCalled() + + expect(mockedGetRequiredResourceRole).toBeCalledTimes(1) + expect(mockedGetRequiredResourceRole).toBeCalledWith( + PermissionLevel.READ, + expect.objectContaining({ + resourceId: tableId, + subResourceId: viewId, + }) + ) + }) + it("throw an exception if the resource id is not provided", async () => { config.setResourceId(undefined) await config.executeMiddleware() From 3e70369832dffc9c4a925267b44b5d5ac5e05f54 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 24 Aug 2023 11:47:42 +0200 Subject: [PATCH 121/316] Use --- .../server/src/sdk/app/permissions/tests/permissions.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/server/src/sdk/app/permissions/tests/permissions.spec.ts b/packages/server/src/sdk/app/permissions/tests/permissions.spec.ts index 4c2768dde4..4c233e68fa 100644 --- a/packages/server/src/sdk/app/permissions/tests/permissions.spec.ts +++ b/packages/server/src/sdk/app/permissions/tests/permissions.spec.ts @@ -1,12 +1,13 @@ -import TestConfiguration from "../../../../tests/utilities/TestConfiguration" import { PermissionLevel } from "@budibase/types" import { mocks, structures } from "@budibase/backend-core/tests" import { resourceActionAllowed } from ".." import { generateViewID } from "../../../../db/utils" +import { initProMocks } from "../../../../tests/utilities/mocks/pro" + +initProMocks() describe("permissions sdk", () => { beforeEach(() => { - new TestConfiguration() mocks.licenses.useCloudFree() }) From 12be5a3d839cb03a463456c030cd0de98f927346 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 24 Aug 2023 11:47:51 +0200 Subject: [PATCH 122/316] Setuo init pro mocks --- packages/server/src/tests/utilities/mocks/pro.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 packages/server/src/tests/utilities/mocks/pro.ts diff --git a/packages/server/src/tests/utilities/mocks/pro.ts b/packages/server/src/tests/utilities/mocks/pro.ts new file mode 100644 index 0000000000..a365ff40c4 --- /dev/null +++ b/packages/server/src/tests/utilities/mocks/pro.ts @@ -0,0 +1,10 @@ +// init the licensing mock +import { mocks } from "@budibase/backend-core/tests" +import * as pro from "@budibase/pro" + +export const initProMocks = () => { + mocks.licenses.init(pro) + + // use unlimited license by default + mocks.licenses.useUnlimited() +} From c0581408e9d4e592be3191b1d704234a228bccdd Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 24 Aug 2023 11:48:38 +0200 Subject: [PATCH 123/316] Add extra tests --- .../src/api/routes/tests/permissions.spec.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/packages/server/src/api/routes/tests/permissions.spec.ts b/packages/server/src/api/routes/tests/permissions.spec.ts index 5261259b77..3fa21cb677 100644 --- a/packages/server/src/api/routes/tests/permissions.spec.ts +++ b/packages/server/src/api/routes/tests/permissions.spec.ts @@ -15,6 +15,7 @@ import { ViewV2, } from "@budibase/types" import * as setup from "./utilities" +import { mocks } from "@budibase/backend-core/tests" const { basicRow } = setup.structures const { BUILTIN_ROLE_IDS } = roles @@ -37,6 +38,7 @@ describe("/permission", () => { }) beforeEach(async () => { + mocks.licenses.useCloudFree() mockedSdk.resourceActionAllowed.mockResolvedValue({ allowed: true }) table = (await config.createTable()) as typeof table @@ -181,6 +183,8 @@ describe("/permission", () => { resourceId: table._id, level: PermissionLevel.READ, }) + // replicate changes before checking permissions + await config.publish() await config.api.viewV2.search(view.id, undefined, { expectStatus: 403, @@ -188,6 +192,47 @@ describe("/permission", () => { }) }) + it("should ignore the view permissions if the flag is not on", async () => { + await config.api.permission.set({ + roleId: STD_ROLE_ID, + resourceId: view.id, + level: PermissionLevel.READ, + }) + await config.api.permission.revoke({ + roleId: STD_ROLE_ID, + resourceId: table._id, + level: PermissionLevel.READ, + }) + // replicate changes before checking permissions + await config.publish() + + await config.api.viewV2.search(view.id, undefined, { + expectStatus: 403, + usePublicUser: true, + }) + }) + + it("should use the view permissions if the flag is on", async () => { + mocks.licenses.useViewPermissions() + await config.api.permission.set({ + roleId: STD_ROLE_ID, + resourceId: view.id, + level: PermissionLevel.READ, + }) + await config.api.permission.revoke({ + roleId: STD_ROLE_ID, + resourceId: table._id, + level: PermissionLevel.READ, + }) + // replicate changes before checking permissions + await config.publish() + + const res = await config.api.viewV2.search(view.id, undefined, { + usePublicUser: true, + }) + expect(res.body.rows[0]._id).toEqual(row._id) + }) + it("shouldn't allow writing from a public user", async () => { const res = await request .post(`/api/${table._id}/rows`) From 8d5f6cf7af9a7efec7f2f28b78e55c0b152ede78 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Thu, 24 Aug 2023 11:38:12 +0100 Subject: [PATCH 124/316] add api for per app builder and help function --- packages/frontend-core/src/api/user.js | 22 +++++++++++++++++++ packages/frontend-core/src/constants.js | 2 ++ .../shared-core/src/sdk/documents/users.ts | 7 ++++++ 3 files changed, 31 insertions(+) diff --git a/packages/frontend-core/src/api/user.js b/packages/frontend-core/src/api/user.js index d8723a649c..d0560239ba 100644 --- a/packages/frontend-core/src/api/user.js +++ b/packages/frontend-core/src/api/user.js @@ -250,4 +250,26 @@ export const buildUserEndpoints = API => ({ url: `/api/global/users/count/${appId}`, }) }, + + /** + * Adds a per app builder to the selected app + * @param appId the applications id + * @param userId The id of the user to add as a builder + */ + addAppBuilder: async ({ userId, appId }) => { + return await API.post({ + url: `/api/global/users/${userId}/app/${appId}/builder`, + }) + }, + + /** + * Removes a per app builder to the selected app + * @param appId the applications id + * @param userId The id of the user to remove as a builder + */ + removeAppBuilder: async ({ userId, appId }) => { + return await API.delete({ + url: `/api/global/users/${userId}/app/${appId}/builder`, + }) + }, }) diff --git a/packages/frontend-core/src/constants.js b/packages/frontend-core/src/constants.js index 0497a392f3..2a04886762 100644 --- a/packages/frontend-core/src/constants.js +++ b/packages/frontend-core/src/constants.js @@ -71,6 +71,7 @@ export const Features = { BRANDING: "branding", SCIM: "scim", SYNC_AUTOMATIONS: "syncAutomations", + APP_BUILDERS: "appBuilders", } // Role IDs @@ -80,6 +81,7 @@ export const Roles = { BASIC: "BASIC", PUBLIC: "PUBLIC", BUILDER: "BUILDER", + CREATOR: "CREATOR", } export const Themes = [ diff --git a/packages/shared-core/src/sdk/documents/users.ts b/packages/shared-core/src/sdk/documents/users.ts index 0f0983c6f7..1be8845656 100644 --- a/packages/shared-core/src/sdk/documents/users.ts +++ b/packages/shared-core/src/sdk/documents/users.ts @@ -35,6 +35,13 @@ export function isAdminOrBuilder( return isBuilder(user, appId) || isAdmin(user) } +export function isAdminOrGlobalBuilder( + user: User | ContextUser, + appId?: string +): boolean { + return isGlobalBuilder(user) || isAdmin(user) +} + // check if they are a builder within an app (not necessarily a global builder) export function hasAppBuilderPermissions(user?: User | ContextUser): boolean { if (!user) { From f7e43a766dc1c5167ba65f0099bd77ce34f3ea88 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Thu, 24 Aug 2023 11:38:50 +0100 Subject: [PATCH 125/316] add tag support to picker --- packages/bbui/src/Form/Core/Picker.svelte | 20 +++++++++++++++++++- packages/bbui/src/Form/Core/Select.svelte | 3 ++- packages/bbui/src/Form/Select.svelte | 3 ++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/bbui/src/Form/Core/Picker.svelte b/packages/bbui/src/Form/Core/Picker.svelte index aada17b318..6658ab0df5 100644 --- a/packages/bbui/src/Form/Core/Picker.svelte +++ b/packages/bbui/src/Form/Core/Picker.svelte @@ -8,6 +8,8 @@ import Icon from "../../Icon/Icon.svelte" import StatusLight from "../../StatusLight/StatusLight.svelte" import Popover from "../../Popover/Popover.svelte" + import Tags from "../../Tags/Tags.svelte" + import Tag from "../../Tags/Tag.svelte" export let id = null export let disabled = false @@ -37,6 +39,7 @@ export let customPopoverHeight export let align = "left" export let footer = null + export let tag = null const dispatch = createEventDispatcher() @@ -99,7 +102,7 @@ bind:this={button} > {#if fieldIcon} - {#if !useOptionIconImage} + {#if !useOptionIconImage}x @@ -217,6 +220,13 @@ {getOptionLabel(option, idx)} + {#if option.tag} + + + {option.tag} + + + {/if} .spectrum-Icon) { + margin-top: 2px; + } diff --git a/packages/bbui/src/Form/Core/Select.svelte b/packages/bbui/src/Form/Core/Select.svelte index 2fad886910..e79ead7e8c 100644 --- a/packages/bbui/src/Form/Core/Select.svelte +++ b/packages/bbui/src/Form/Core/Select.svelte @@ -21,7 +21,7 @@ export let sort = false export let align export let footer = null - + export let tag = null const dispatch = createEventDispatcher() let open = false @@ -83,6 +83,7 @@ {isOptionEnabled} {autocomplete} {sort} + {tag} isPlaceholder={value == null || value === ""} placeholderOption={placeholder === false ? null : placeholder} isOptionSelected={option => option === value} diff --git a/packages/bbui/src/Form/Select.svelte b/packages/bbui/src/Form/Select.svelte index e87496652d..a9214320f9 100644 --- a/packages/bbui/src/Form/Select.svelte +++ b/packages/bbui/src/Form/Select.svelte @@ -25,7 +25,7 @@ export let customPopoverHeight export let align export let footer = null - + export let tag = null const dispatch = createEventDispatcher() const onChange = e => { value = e.detail @@ -61,6 +61,7 @@ {isOptionEnabled} {autocomplete} {customPopoverHeight} + {tag} on:change={onChange} on:click /> From 6b14353dcbd59a79fab19a7d817d12841e719497 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Thu, 24 Aug 2023 11:40:00 +0100 Subject: [PATCH 126/316] update side panel to enable selecting of creator role --- .../src/components/common/RoleSelect.svelte | 42 ++++++++++++++-- .../_components/BuilderSidePanel.svelte | 49 ++++++++++++------- .../builder/src/stores/portal/licensing.js | 4 ++ packages/builder/src/stores/portal/users.js | 10 ++++ 4 files changed, 83 insertions(+), 22 deletions(-) diff --git a/packages/builder/src/components/common/RoleSelect.svelte b/packages/builder/src/components/common/RoleSelect.svelte index 09a67cb6fe..62be915dcb 100644 --- a/packages/builder/src/components/common/RoleSelect.svelte +++ b/packages/builder/src/components/common/RoleSelect.svelte @@ -1,6 +1,8 @@
    {user.email}
    -
    - {userTitle(user)} -
{ + addAppBuilder(user._id) + }} on:change={e => { onUpdateUser(user, e.detail) }} @@ -614,7 +625,7 @@ }} autoWidth align="right" - allowedRoles={user.isAdminOrBuilder + allowedRoles={user.isAdminOrGlobalBuilder ? [Constants.Roles.ADMIN] : null} /> diff --git a/packages/builder/src/stores/portal/licensing.js b/packages/builder/src/stores/portal/licensing.js index 4a59f29f52..c42a2006c3 100644 --- a/packages/builder/src/stores/portal/licensing.js +++ b/packages/builder/src/stores/portal/licensing.js @@ -125,6 +125,9 @@ export const createLicensingStore = () => { const syncAutomationsEnabled = license.features.includes( Constants.Features.SYNC_AUTOMATIONS ) + const perAppBuildersEnabled = license.features.includes( + Constants.Features.APP_BUILDERS + ) store.update(state => { return { ...state, @@ -140,6 +143,7 @@ export const createLicensingStore = () => { auditLogsEnabled, enforceableSSO, syncAutomationsEnabled, + perAppBuildersEnabled, } }) }, diff --git a/packages/builder/src/stores/portal/users.js b/packages/builder/src/stores/portal/users.js index 992f6a5418..9cd5ed54ea 100644 --- a/packages/builder/src/stores/portal/users.js +++ b/packages/builder/src/stores/portal/users.js @@ -112,6 +112,14 @@ export function createUsersStore() { return await API.saveUser(user) } + async function addAppBuilder(userId, appId) { + return await API.addAppBuilder({ userId, appId }) + } + + async function removeAppBuilder(userId, appId) { + return await API.removeAppBuilder({ userId, appId }) + } + const getUserRole = user => sdk.users.isAdmin(user) ? "admin" @@ -139,6 +147,8 @@ export function createUsersStore() { getInvites, updateInvite, getUserCountByApp, + addAppBuilder, + removeAppBuilder, // any operation that adds or deletes users acceptInvite, create: refreshUsage(create), From 409df71c127f6c9258ab12af3efa7d31e73dc65b Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 24 Aug 2023 12:11:22 +0100 Subject: [PATCH 127/316] Add hover color to divider in screen list --- .../design/[screenId]/_components/ScreenList/index.svelte | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/index.svelte index c5475b9c16..ef5911c0f8 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/index.svelte @@ -297,8 +297,12 @@ width: 100%; top: 50%; transform: translateY(-50%); + transition: background 130ms ease-out; } .divider:hover { cursor: row-resize; } + .divider:hover:after { + background: var(--spectrum-global-color-gray-300); + } From 50e3a66f92034d4999cfadced13ef59388e5fcca Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 24 Aug 2023 12:26:46 +0100 Subject: [PATCH 128/316] Fix notifications in grid block in client apps --- packages/frontend-core/src/components/grid/stores/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend-core/src/components/grid/stores/index.js b/packages/frontend-core/src/components/grid/stores/index.js index cb858b7293..7b73ea8be6 100644 --- a/packages/frontend-core/src/components/grid/stores/index.js +++ b/packages/frontend-core/src/components/grid/stores/index.js @@ -20,7 +20,6 @@ import * as ViewV2 from "./viewV2" import * as Datasource from "./datasource" const DependencyOrderedStores = [ - Notifications, Sort, Filter, Bounds, @@ -40,6 +39,7 @@ const DependencyOrderedStores = [ Pagination, Clipboard, Config, + Notifications, ] export const attachStores = context => { From 826255dfe213fccf9dd1b3659091bd111e1328c0 Mon Sep 17 00:00:00 2001 From: Jonny McCullagh Date: Thu, 24 Aug 2023 13:52:17 +0100 Subject: [PATCH 129/316] trigger a feature branch on pull request (#11584) trigger a feature branch on pull request --- .github/workflows/deploy-featurebranch.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/deploy-featurebranch.yml diff --git a/.github/workflows/deploy-featurebranch.yml b/.github/workflows/deploy-featurebranch.yml new file mode 100644 index 0000000000..9057d32c4c --- /dev/null +++ b/.github/workflows/deploy-featurebranch.yml @@ -0,0 +1,19 @@ +name: deploy-featurebranch + +on: + pull_request: + branches: + - develop + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: passeidireto/trigger-external-workflow-action@main + env: + BRANCH: ${{ github.head_ref }} + with: + repository: budibase/budibase-deploys + event: featurebranch-qa-deploy + github_pat: ${{ secrets.GH_ACCESS_TOKEN }} From fec20e526e4a2b82652787df8b0a50a6046f4427 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 24 Aug 2023 12:52:39 +0000 Subject: [PATCH 130/316] Bump version to 2.9.31 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 1af0927e16..27d07555d4 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.9.30", + "version": "2.9.31", "npmClient": "yarn", "packages": [ "packages/*" From 1ec2faf74d3561d52727b2477952238de9096b9e Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 24 Aug 2023 14:39:53 +0100 Subject: [PATCH 131/316] List refinement, Form Block UX updates for action type. Bug fixes for FormBlock bindings. TableBlock UX updates and Component Setting updates --- packages/bbui/src/Popover/Popover.svelte | 14 ++ .../builder/src/builderStore/dataBinding.js | 86 ++++++- .../src/builderStore/store/frontend.js | 89 +++----- .../bindings/DrawerBindableInput.svelte | 2 + .../ButtonActionEditor.svelte | 2 +- ...ttingsList.svelte => DraggableList.svelte} | 106 +++++---- .../EditFieldPopover.svelte | 207 +++++++---------- .../FieldConfiguration.svelte | 209 ++++++++---------- .../FieldConfiguration/FieldSetting.svelte | 56 +++++ .../controls/FieldConfiguration/utils.js | 46 ++++ .../OptionsEditor/OptionsEditor.svelte | 15 +- .../settings/controls/PropertyControl.svelte | 2 + .../ValidationEditor/ValidationEditor.svelte | 7 +- .../settings/ComponentInfoSection.svelte | 38 +--- .../settings/ComponentSettingsPanel.svelte | 5 +- .../settings/ComponentSettingsSection.svelte | 62 +++--- .../_components/settings/InfoDisplay.svelte | 61 +++++ packages/client/manifest.json | 20 +- .../components/app/blocks/TableBlock.svelte | 9 +- .../app/blocks/form/FormBlock.svelte | 75 +++---- .../app/blocks/form/InnerFormBlock.svelte | 40 ++-- 21 files changed, 666 insertions(+), 485 deletions(-) rename packages/builder/src/components/design/settings/controls/{SettingsList.svelte => DraggableList.svelte} (55%) create mode 100644 packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte create mode 100644 packages/builder/src/components/design/settings/controls/FieldConfiguration/utils.js create mode 100644 packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/InfoDisplay.svelte diff --git a/packages/bbui/src/Popover/Popover.svelte b/packages/bbui/src/Popover/Popover.svelte index 6706bf7a8b..198f96c672 100644 --- a/packages/bbui/src/Popover/Popover.svelte +++ b/packages/bbui/src/Popover/Popover.svelte @@ -23,6 +23,9 @@ export let animate = true export let customZindex + export let showPopover = true + export let clickOutsideOverride = false + $: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum" export const show = () => { @@ -36,6 +39,9 @@ } const handleOutsideClick = e => { + if (clickOutsideOverride) { + return + } if (open) { // Stop propagation if the source is the anchor let node = e.target @@ -54,6 +60,9 @@ } function handleEscape(e) { + if (!clickOutsideOverride) { + return + } if (open && e.key === "Escape") { hide() } @@ -79,6 +88,7 @@ on:keydown={handleEscape} class="spectrum-Popover is-open" class:customZindex + class:hide-popover={open && !showPopover} role="presentation" style="height: {customHeight}; --customZindex: {customZindex};" transition:fly|local={{ y: -20, duration: animate ? 200 : 0 }} @@ -89,6 +99,10 @@ {/if} diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte index 859f019a26..94fd98c707 100644 --- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte @@ -1,86 +1,58 @@ - @@ -93,69 +65,60 @@ }} /> - - - - - - + on:drawerShow={e => { + drawers = [...drawers, e.detail] + }} + on:drawerHide={e => { + drawers = drawers.slice(0, -1) + }} + /> + + + + diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte index 922b994c5a..596bb95639 100644 --- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte @@ -1,127 +1,76 @@
- + {#if fieldList?.length} + + {/if}
diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/utils.js b/packages/builder/src/components/design/settings/controls/FieldConfiguration/utils.js new file mode 100644 index 0000000000..d4a8963dba --- /dev/null +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/utils.js @@ -0,0 +1,46 @@ +export const convertOldFieldFormat = fields => { + if (!fields) { + return [] + } + const converted = fields.map(field => { + if (typeof field === "string") { + // existed but was a string + return { + field, + active: true, + } + } else if (typeof field?.active != "boolean") { + // existed but had no state + return { + field: field.name, + active: true, + } + } else { + return field + } + }) + return converted +} + +export const getComponentForField = (field, schema) => { + if (!field || !schema?.[field]) { + return null + } + const type = schema[field].type + return FieldTypeToComponentMap[type] +} + +export const FieldTypeToComponentMap = { + string: "stringfield", + number: "numberfield", + bigint: "bigintfield", + options: "optionsfield", + array: "multifieldselect", + boolean: "booleanfield", + longform: "longformfield", + datetime: "datetimefield", + attachment: "attachmentfield", + link: "relationshipfield", + json: "jsonfield", + barcodeqr: "codescanner", +} diff --git a/packages/builder/src/components/design/settings/controls/OptionsEditor/OptionsEditor.svelte b/packages/builder/src/components/design/settings/controls/OptionsEditor/OptionsEditor.svelte index 1201edd31e..c626081042 100644 --- a/packages/builder/src/components/design/settings/controls/OptionsEditor/OptionsEditor.svelte +++ b/packages/builder/src/components/design/settings/controls/OptionsEditor/OptionsEditor.svelte @@ -24,11 +24,22 @@ } -Define Options - +
+
+
Define Options
+
+ Define the options for this picker. + + diff --git a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte index 5125c3bade..c8135b4f61 100644 --- a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte +++ b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte @@ -100,6 +100,8 @@ {key} {type} {...props} + on:drawerHide + on:drawerShow />
{#if info} diff --git a/packages/builder/src/components/design/settings/controls/ValidationEditor/ValidationEditor.svelte b/packages/builder/src/components/design/settings/controls/ValidationEditor/ValidationEditor.svelte index 6db24e8d69..96953b56b8 100644 --- a/packages/builder/src/components/design/settings/controls/ValidationEditor/ValidationEditor.svelte +++ b/packages/builder/src/components/design/settings/controls/ValidationEditor/ValidationEditor.svelte @@ -5,9 +5,8 @@ export let value = [] export let bindings = [] - export let componentDefinition + export let componentInstance export let type - const dispatch = createEventDispatcher() let drawer @@ -31,7 +30,7 @@ {text}
- + Configure validation rules for this field. @@ -41,7 +40,7 @@ bind:rules={value} {type} {bindings} - {componentDefinition} + fieldName={componentInstance?.field} /> diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentInfoSection.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentInfoSection.svelte index f0288f0059..e73e6d7841 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentInfoSection.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentInfoSection.svelte @@ -1,36 +1,12 @@ - -
-
- - {componentDefinition.name} -
- {componentDefinition.info} -
+ + - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsPanel.svelte index 2ff605cc77..581e69cfaf 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsPanel.svelte @@ -5,7 +5,7 @@ import DesignSection from "./DesignSection.svelte" import CustomStylesSection from "./CustomStylesSection.svelte" import ConditionalUISection from "./ConditionalUISection.svelte" - import ComponentInfoSection from "./ComponentInfoSection.svelte" + import { getBindableProperties, getComponentBindableProperties, @@ -55,9 +55,6 @@
{#if section == "settings"} - {#if componentDefinition?.info} - - {/if} { const settings = definition?.settings ?? [] - console.log( - "ComponentSettingsSection::definition?.settings", - definition?.settings - ) const generalSettings = settings.filter(setting => !setting.section) const customSections = settings.filter(setting => setting.section) let sections = [ @@ -51,23 +50,22 @@ } const updateSetting = async (setting, value) => { - if (typeof onUpdateSetting === "function") { - onUpdateSetting(setting, value) - } else { - try { + try { + if (typeof onUpdateSetting === "function") { + await onUpdateSetting(setting, value) + } else { await store.actions.components.updateSetting(setting.key, value) - - // Send event if required - if (setting.sendEvents) { - analytics.captureEvent(Events.COMPONENT_UPDATED, { - name: componentInstance._component, - setting: setting.key, - value, - }) - } - } catch (error) { - notifications.error("Error updating component prop") } + // Send event if required + if (setting.sendEvents) { + analytics.captureEvent(Events.COMPONENT_UPDATED, { + name: componentInstance._component, + setting: setting.key, + value, + }) + } + } catch (error) { + notifications.error("Error updating component prop") } } @@ -106,7 +104,7 @@ } } - return true + return typeof setting.visible == "boolean" ? setting.visible : true } const canRenderControl = (instance, setting, isScreen) => { @@ -125,9 +123,22 @@ {#each sections as section, idx (section.name)} {#if section.visible} - + + {#if section.info} + + {:else if idx === 0 && section.name === "General" && componentDefinition.info} + + {/if}
- {#if idx === 0 && !componentInstance._component.endsWith("/layout") && !isScreen} + {#if idx === 0 && !componentInstance._component.endsWith("/layout") && !isScreen && showInstanceName} {/if} {/each} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/InfoDisplay.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/InfoDisplay.svelte new file mode 100644 index 0000000000..a48f5d92b8 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/InfoDisplay.svelte @@ -0,0 +1,61 @@ + + +
+ {#if title} +
+ + {title || ""} +
+ {@html body} + {:else} + + + + {@html body} + {/if} +
+ + diff --git a/packages/client/manifest.json b/packages/client/manifest.json index a03b6be8af..d1f718aaf3 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -5282,6 +5282,11 @@ }, { "section": true, + "dependsOn": { + "setting": "actionType", + "value": "Create", + "invert": true + }, "name": "Row details", "info": "How to pass a row ID using bindings", "settings": [ @@ -5289,23 +5294,13 @@ "type": "text", "label": "Row ID", "key": "rowId", - "nested": true, - "dependsOn": { - "setting": "actionType", - "value": "Create", - "invert": true - } + "nested": true }, { "type": "text", "label": "Empty text", "key": "noRowsMessage", - "defaultValue": "We couldn't find a row to display", - "dependsOn": { - "setting": "actionType", - "value": "Create", - "invert": true - } + "defaultValue": "We couldn't find a row to display" } ] }, @@ -5399,6 +5394,7 @@ { "type": "fieldConfiguration", "key": "fields", + "nested": true, "selectAllFields": true }, { diff --git a/packages/client/src/components/app/blocks/TableBlock.svelte b/packages/client/src/components/app/blocks/TableBlock.svelte index efc83fd5ac..e07c26544c 100644 --- a/packages/client/src/components/app/blocks/TableBlock.svelte +++ b/packages/client/src/components/app/blocks/TableBlock.svelte @@ -45,6 +45,9 @@ let enrichedSearchColumns let schemaLoaded = false + // Accommodate old config to ensure delete button does not reappear + $: deleteLabel = sidePanelShowDelete === false ? "" : sidePanelDeleteLabel + $: fetchSchema(dataSource) $: enrichSearchColumns(searchColumns, schema).then( val => (enrichedSearchColumns = val) @@ -245,10 +248,8 @@ bind:id={detailsFormBlockId} props={{ dataSource, - showSaveButton: true, - showDeleteButton: sidePanelShowDelete, - saveButtonLabel: sidePanelSaveLabel, - deleteButtonLabel: sidePanelDeleteLabel, + saveButtonLabel: sidePanelSaveLabel || "Save", //always show + deleteButtonLabel: deleteLabel, //respect config actionType: "Update", rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`, fields: sidePanelFields || normalFields, diff --git a/packages/client/src/components/app/blocks/form/FormBlock.svelte b/packages/client/src/components/app/blocks/form/FormBlock.svelte index c6a149e57c..5d57d10ab6 100644 --- a/packages/client/src/components/app/blocks/form/FormBlock.svelte +++ b/packages/client/src/components/app/blocks/form/FormBlock.svelte @@ -12,55 +12,59 @@ export let fields export let labelPosition export let title + export let showDeleteButton + export let showSaveButton export let saveButtonLabel export let deleteButtonLabel - export let showSaveButton - export let showDeleteButton export let rowId export let actionUrl export let noRowsMessage export let notificationOverride + // Accommodate old config to ensure delete button does not reappear + $: deleteLabel = showDeleteButton === false ? "" : deleteButtonLabel?.trim() + $: saveLabel = showSaveButton === false ? "" : saveButtonLabel?.trim() + const { fetchDatasourceSchema } = getContext("sdk") const convertOldFieldFormat = fields => { - return typeof fields?.[0] === "string" - ? fields.map(field => ({ + if (!fields) { + return [] + } + return fields.map(field => { + if (typeof field === "string") { + // existed but was a string + return { name: field, - displayName: field, active: true, - })) - : fields - } - - //All settings need to derive from the block config now - - // Parse the fields here too. Not present means false. - const getDefaultFields = (fields, schema) => { - let formFields - if (schema && (!fields || fields.length === 0)) { - const defaultFields = [] - - Object.values(schema).forEach(field => { - if (field.autocolumn) return - - defaultFields.push({ - name: field.name, - displayName: field.name, - active: true, - }) - }) - formFields = [...defaultFields] - } else { - formFields = (fields || []).map(field => { + } + } else { + // existed but had no state return { ...field, active: typeof field?.active != "boolean" ? true : field?.active, } - }) - } + } + }) + } - return formFields.filter(field => field.active) + const getDefaultFields = (fields, schema) => { + if (!schema) { + return [] + } + let defaultFields = [] + + if (!fields || fields.length === 0) { + Object.values(schema) + .filter(field => !field.autocolumn) + .forEach(field => { + defaultFields.push({ + name: field.name, + active: true, + }) + }) + } + return [...fields, ...defaultFields].filter(field => field.active) } let schema @@ -94,15 +98,12 @@ fields: fieldsOrDefault, labelPosition, title, - saveButtonLabel, - deleteButtonLabel, - showSaveButton, - showDeleteButton, + saveButtonLabel: saveLabel, + deleteButtonLabel: deleteLabel, schema, repeaterId, notificationOverride, } - const fetchSchema = async () => { schema = (await fetchDatasourceSchema(dataSource)) || {} } diff --git a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte index 5415768dc9..1f69645280 100644 --- a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte +++ b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte @@ -13,8 +13,6 @@ export let title export let saveButtonLabel export let deleteButtonLabel - export let showSaveButton - export let showDeleteButton export let schema export let repeaterId export let notificationOverride @@ -100,18 +98,33 @@ }, ] - $: renderDeleteButton = showDeleteButton && actionType === "Update" - $: renderSaveButton = showSaveButton && actionType !== "View" + $: renderDeleteButton = deleteButtonLabel && actionType === "Update" + $: renderSaveButton = saveButtonLabel && actionType !== "View" $: renderButtons = renderDeleteButton || renderSaveButton $: renderHeader = renderButtons || title const getComponentForField = field => { - if (!field || !schema?.[field]) { + const fieldSchemaName = field.field || field.name + if (!fieldSchemaName || !schema?.[fieldSchemaName]) { return null } - const type = schema[field].type + const type = schema[fieldSchemaName].type return FieldTypeToComponentMap[type] } + + const getPropsForField = field => { + let fieldProps = field._component + ? { + ...field, + } + : { + field: field.name, + label: field.name, + placeholder: field.name, + _instanceName: field.name, + } + return fieldProps + } {#if fields?.length} @@ -175,7 +188,7 @@ {#each fields as field, idx} - {#if getComponentForField(field.name)} + {#if getComponentForField(field) && field.active} {/if} From ee058a4c26a1f2031190a78dfc80bd012141918a Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 24 Aug 2023 14:05:10 +0000 Subject: [PATCH 132/316] Bump version to 2.9.32 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 27d07555d4..3d6bf559b8 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.9.31", + "version": "2.9.32", "npmClient": "yarn", "packages": [ "packages/*" From b6e675e3ffc1f957ca457cc4874e0668a3641874 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 24 Aug 2023 15:15:15 +0100 Subject: [PATCH 133/316] Migrate DS+ settings without keys --- .../settings/controls/TableSelect.svelte | 21 +++++++++---------- .../src/components/app/DataProvider.svelte | 18 +++++++++++++++- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/packages/builder/src/components/design/settings/controls/TableSelect.svelte b/packages/builder/src/components/design/settings/controls/TableSelect.svelte index 03b739e09f..ced1151969 100644 --- a/packages/builder/src/components/design/settings/controls/TableSelect.svelte +++ b/packages/builder/src/components/design/settings/controls/TableSelect.svelte @@ -1,6 +1,6 @@ table.name} - getOptionValue={table => table._id} + {options} + getOptionLabel={x => x.label} + getOptionValue={x => x.resourceId} /> diff --git a/packages/builder/src/components/design/settings/controls/TableSelect.svelte b/packages/builder/src/components/design/settings/controls/TableSelect.svelte index ced1151969..4a6f33202b 100644 --- a/packages/builder/src/components/design/settings/controls/TableSelect.svelte +++ b/packages/builder/src/components/design/settings/controls/TableSelect.svelte @@ -1,7 +1,7 @@
diff --git a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/DuplicateRow.svelte b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/DuplicateRow.svelte index bb253da56a..db1fff47f2 100644 --- a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/DuplicateRow.svelte +++ b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/DuplicateRow.svelte @@ -4,7 +4,7 @@ import { tables } from "stores/backend" import { getContextProviderComponents, - getSchemaForTable, + getSchemaForDatasourcePlus, } from "builderStore/dataBinding" import SaveFields from "./SaveFields.svelte" @@ -60,7 +60,7 @@ } const getSchemaFields = (asset, tableId) => { - const { schema } = getSchemaForTable(tableId) + const { schema } = getSchemaForDatasourcePlus(tableId) delete schema._id delete schema._rev return Object.values(schema || {}) diff --git a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/SaveRow.svelte b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/SaveRow.svelte index d16a279c68..c1917ad90f 100644 --- a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/SaveRow.svelte +++ b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/SaveRow.svelte @@ -1,10 +1,10 @@
- + - {/if} + + update("navWidth", e.detail)} - /> - {/if} -
- -
- update("hideLogo", !e.detail)} - /> - {#if !$store.navigation.hideLogo} -
- -
- update("logoUrl", e.detail)} - updateOnChange={false} - /> - {/if} -
- -
- update("hideTitle", !e.detail)} - /> - {#if !$store.navigation.hideTitle} -
- -
- update("title", e.detail)} - updateOnChange={false} - /> - {/if} -
- -
- update("navBackground", e.detail)} - /> -
- -
- update("navTextColor", e.detail)} - /> -
-
- {/if} - - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/ThemePanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/ThemePanel.svelte deleted file mode 100644 index 13a008fae0..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/ThemePanel.svelte +++ /dev/null @@ -1,78 +0,0 @@ - - -
- - These settings apply to all screens -
- - - - - - - update("buttonBorderRadius", e.detail)} - /> - - update("primaryColor", val)} - props={{ - spectrumTheme: $store.theme, - }} - /> - update("primaryColorHover", val)} - props={{ - spectrumTheme: $store.theme, - }} - /> - - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/index.svelte deleted file mode 100644 index 778fa303cc..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/index.svelte +++ /dev/null @@ -1,51 +0,0 @@ - - - -
-
- {#each tabs as tab} - { - activeTab = tab - }} - > - {capitalise(tab)} - - {/each} -
-
- - {#if activeTab === "theme"} - - {:else} - - {/if} - -
- - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_layout.svelte deleted file mode 100644 index 19cb1d8dff..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_layout.svelte +++ /dev/null @@ -1,52 +0,0 @@ - - -{#if routeComponentId === `${$store.selectedScreenId}-screen`} - -{:else if routeComponentId === `${$store.selectedScreenId}-navigation`} - -{:else} - -{/if} - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/index.svelte deleted file mode 100644 index 0ff63d1ead..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/index.svelte +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte index 09f97302fd..785b221239 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte @@ -1,16 +1,32 @@
- + -
- Screens -
-
- -
-
- -
-
-
- {#if filteredScreens?.length} - {#each filteredScreens as screen (screen._id)} - store.actions.screens.select(screen._id)} - rightAlignIcon - showTooltip - selectedBy={$userSelectedResourceMap[screen._id]} - > - -
- -
-
- {/each} - {:else} - -
- There aren't any screens matching that route -
-
- {/if} -
- -
screensHeight.set("210px")} - /> -
- - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_fallback.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_fallback.svelte new file mode 100644 index 0000000000..00165e4ee9 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_fallback.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_layout.svelte index 0e630b4f39..8bc0dcc3e5 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_layout.svelte @@ -1,10 +1,14 @@ -{#if $selectedScreen} -
-
- - - -
+
+
+ + $goto("./screens")} + /> + $goto("./components")} + /> + $goto("./theme")} + /> + $goto("./navigation")} + /> + {#if $store.layouts?.length} + $goto("./layouts")} + /> + {/if} +
-{/if} + +
+ {#if $selectedScreen} + + + {/if} +
+
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentDropdownMenu.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentDropdownMenu.svelte similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentDropdownMenu.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentDropdownMenu.svelte diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentKeyHandler.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentKeyHandler.svelte similarity index 90% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentKeyHandler.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentKeyHandler.svelte index b6957f64f3..4813757727 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentKeyHandler.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentKeyHandler.svelte @@ -5,6 +5,7 @@ import { goto, isActive } from "@roxi/routify" import { notifications } from "@budibase/bbui" import ConfirmDialog from "components/common/ConfirmDialog.svelte" + import { isBuilderInputFocused } from "helpers" let confirmDeleteDialog let confirmEjectDialog @@ -36,7 +37,7 @@ confirmEjectDialog.show() }, ["Ctrl+Enter"]: () => { - $goto(`./:componentId/new`) + $goto("./new") }, ["Delete"]: component => { // Don't show confirmation for the screen itself @@ -53,8 +54,8 @@ store.actions.components.selectNext() }, ["Escape"]: () => { - if ($isActive(`./:componentId/new`)) { - $goto(`./${$store.selectedComponentId}`) + if ($isActive("./new")) { + $goto("./") } }, } @@ -84,13 +85,10 @@ const handler = keyHandlers[key] if (!handler) { return false - } - - if (event && key !== "Escape") { + } else if (event) { event.preventDefault() event.stopPropagation() } - return await handler(component) } catch (error) { notifications.error(error || "Error handling key press") @@ -103,13 +101,7 @@ return } // Ignore events when typing - const activeTag = document.activeElement?.tagName.toLowerCase() - const inCodeEditor = - document.activeElement?.classList?.contains("cm-content") - if ( - (inCodeEditor || ["input", "textarea"].indexOf(activeTag) !== -1) && - e.key !== "Escape" - ) { + if (isBuilderInputFocused(e)) { return } // Key events are always for the selected component diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte new file mode 100644 index 0000000000..9513753d76 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte @@ -0,0 +1,90 @@ + + + +
+ +
+ +
    +
  • + { + $store.selectedComponentId = $selectedScreen?.props._id + }} + id={`component-${$selectedScreen?.props._id}`} + selectedBy={$userSelectedResourceMap[$selectedScreen?.props._id]} + > + + + + + + {#if $dndStore.dragging && $dndStore.valid} + + {#if $dndStore.dropPosition !== DropPosition.INSIDE} + + {/if} + {/if} +
  • +
+
+
+ + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentScrollWrapper.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentScrollWrapper.svelte similarity index 93% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentScrollWrapper.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentScrollWrapper.svelte index 484b56f21c..15ba7acecf 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentScrollWrapper.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentScrollWrapper.svelte @@ -9,14 +9,14 @@ if (!bounds) { return } - const sidebarWidth = 310 + const sidebarWidth = 259 const navItemHeight = 32 const { scrollLeft, scrollTop, offsetHeight } = scrollRef let scrollBounds = scrollRef.getBoundingClientRect() let newOffsets = {} // Calculate left offset - const offsetX = bounds.left + bounds.width + scrollLeft + 16 + const offsetX = bounds.left + bounds.width + scrollLeft - 36 if (offsetX > sidebarWidth) { newOffsets.left = offsetX - sidebarWidth } else { @@ -64,7 +64,6 @@
div { + padding: var(--spacing-xl) 0; flex: 1 1 auto; overflow: auto; height: 0; diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentTree.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentTree.svelte similarity index 98% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentTree.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentTree.svelte index 0e23f82946..18319e4b33 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentTree.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentTree.svelte @@ -107,7 +107,6 @@ id={`component-${component._id}`} > + import { syncURLToState } from "helpers/urlStateSync" + import { store, selectedScreen } from "builderStore" + import * as routify from "@roxi/routify" + import { onDestroy } from "svelte" + import { findComponent } from "builderStore/componentUtils" + import ComponentListPanel from "./_components/navigation/ComponentListPanel.svelte" + import ComponentSettingsPanel from "./_components/settings/ComponentSettingsPanel.svelte" + + $: componentId = $store.selectedComponentId + $: store.actions.websocket.selectResource(componentId) + + const cleanUrl = url => { + // Strip trailing slashes + if (url?.endsWith("/index")) { + url = url.replace("/index", "") + } + // Hide new component panel whenever component ID changes + if (url?.endsWith("/new")) { + url = url.replace("/new", "") + } + return { url } + } + + // Keep URL and state in sync for selected component ID + const stopSyncing = syncURLToState({ + urlParam: "componentId", + stateKey: "selectedComponentId", + validate: id => !!findComponent($selectedScreen.props, id), + fallbackUrl: "../", + store, + routify, + beforeNavigate: cleanUrl, + }) + + onDestroy(stopSyncing) + + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/index.svelte new file mode 100644 index 0000000000..9b5d05fe57 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/index.svelte @@ -0,0 +1,4 @@ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/NewComponentPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/NewComponentPanel.svelte similarity index 98% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/NewComponentPanel.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/NewComponentPanel.svelte index 248bbc8141..7dca5b792b 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/NewComponentPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/NewComponentPanel.svelte @@ -31,10 +31,6 @@ $: orderMap = createComponentOrderMap(componentList) const getAllowedComponents = (allComponents, screen, component) => { - // Default to using the root screen container if no component specified - if (!component) { - component = screen.props - } const path = findComponentPath(screen?.props, component?._id) if (!path?.length) { return [] diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/componentStructure.json similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/componentStructure.json diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/index.svelte similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/index.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/index.svelte diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/index.svelte new file mode 100644 index 0000000000..f8c4cc0868 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/index.svelte @@ -0,0 +1,18 @@ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/index.svelte index c4ed7d949c..f5e3806bd6 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/index.svelte @@ -1,6 +1,5 @@ diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutDropdownMenu.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutDropdownMenu.svelte new file mode 100644 index 0000000000..cba68f899d --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutDropdownMenu.svelte @@ -0,0 +1,41 @@ + + + +
+ +
+ Delete +
+ + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutListPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutListPanel.svelte new file mode 100644 index 0000000000..cc895317fd --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutListPanel.svelte @@ -0,0 +1,29 @@ + + + +
+ {#each $store.layouts as layout (layout._id)} + store.actions.layouts.select(layout._id)} + > + + + {/each} +
+
+ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutSettingsPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutSettingsPanel.svelte new file mode 100644 index 0000000000..bfc2f94f43 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutSettingsPanel.svelte @@ -0,0 +1,53 @@ + + + + + + Custom layouts are being deprecated. They will be removed in a future + release. + + + You can save the content of this layout by pressing the button below. + + + This will copy all components inside your layout, which you can then paste + into a screen. + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_layout.svelte new file mode 100644 index 0000000000..c82fefab3e --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_layout.svelte @@ -0,0 +1,20 @@ + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/index.svelte new file mode 100644 index 0000000000..4d39403bc3 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/index.svelte @@ -0,0 +1,7 @@ + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/_layout.svelte new file mode 100644 index 0000000000..1333c6afe3 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/_layout.svelte @@ -0,0 +1,12 @@ + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/index.svelte new file mode 100644 index 0000000000..09d45f8fde --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/index.svelte @@ -0,0 +1,12 @@ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel.svelte new file mode 100644 index 0000000000..614e1eed80 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel.svelte @@ -0,0 +1,33 @@ + + + + + {#if $selectedScreen.layoutId} + + You can't preview your navigation settings using this screen as it uses + a custom layout, which is deprecated + + {/if} + + Your navigation is configured for all the screens within your app. + + + You can hide and show your navigation for each screen in the screen + settings. + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Navigation/LinksDrawer.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationLinksDrawer.svelte similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Navigation/LinksDrawer.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationLinksDrawer.svelte diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Navigation/LinksEditor.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationLinksEditor.svelte similarity index 75% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Navigation/LinksEditor.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationLinksEditor.svelte index b081ea6d72..895c82495d 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Navigation/LinksEditor.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationLinksEditor.svelte @@ -1,6 +1,6 @@ - - + + Configure the links in your navigation bar. diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationSettingsPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationSettingsPanel.svelte new file mode 100644 index 0000000000..c6d43984b2 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationSettingsPanel.svelte @@ -0,0 +1,110 @@ + + + + + + + + + update("navigation", "Top")} + /> + update("navigation", "Left")} + /> + + + {#if $store.navigation.navigation === "Top"} + update("sticky", e.detail)} + /> + update("logoUrl", e.detail)} + placeholder="Add logo URL" + updateOnChange={false} + /> + {/if} + + + update("hideTitle", !e.detail)} + /> + {#if !$store.navigation.hideTitle} + update("title", e.detail)} + placeholder="Add title" + updateOnChange={false} + /> + {/if} + + + + update("navBackground", e.detail)} + /> + + + + update("navTextColor", e.detail)} + /> + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/index.svelte new file mode 100644 index 0000000000..fc2e03d8e8 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/index.svelte @@ -0,0 +1,7 @@ + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/RoleIndicator.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/RoleIndicator.svelte similarity index 87% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/RoleIndicator.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/RoleIndicator.svelte index 694abe444a..2555b0b2a2 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/RoleIndicator.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/RoleIndicator.svelte @@ -26,7 +26,7 @@ {#if showTooltip}
- +
{/if}
@@ -38,11 +38,13 @@ .tooltip { z-index: 1; position: absolute; - bottom: -5px; - left: 13px; + top: 50%; + left: calc(50% - 8px); + transform: translateX(-100%) translateY(-50%); display: flex; flex-direction: row; justify-content: flex-end; + width: 200px; pointer-events: none; } .tooltip :global(.spectrum-Tooltip) { diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/DropdownMenu.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenDropdownMenu.svelte similarity index 97% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/DropdownMenu.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenDropdownMenu.svelte index c117c7e9dd..fd5ddd9459 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/DropdownMenu.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenDropdownMenu.svelte @@ -56,7 +56,7 @@ const deleteScreen = async () => { try { await store.actions.screens.delete(screen) - notifications.success("Deleted screen successfully") + notifications.success("Deleted screen successfully.") } catch (err) { notifications.error("Error deleting screen") } diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenListPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenListPanel.svelte new file mode 100644 index 0000000000..6362af3073 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenListPanel.svelte @@ -0,0 +1,75 @@ + + + + + + (searchString = e.detail)} + /> + role.name} - getOptionValue={role => role._id} - getOptionColour={getColor} - getOptionIcon={getIcon} - isOptionEnabled={option => { - if (option._id == CreatorID && !$licensing.perAppBuildersEnabled) { - return false - } else { - return true - } - }} - {placeholder} - {error} -/> +{#if fancySelect} + role.name} + getOptionValue={role => role._id} + getOptionColour={getColor} + getOptionIcon={getIcon} + isOptionEnabled={option => { + if (option._id == CreatorID && !$licensing.perAppBuildersEnabled) { + return false + } else { + return true + } + }} + {placeholder} + {error} + /> +{:else} + x.name} getOptionValue={x => x.key} diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.js index 2c07f8f431..ed0549eeca 100644 --- a/packages/builder/src/constants/backend/index.js +++ b/packages/builder/src/constants/backend/index.js @@ -287,3 +287,9 @@ export const DatasourceTypes = { GRAPH: "Graph", API: "API", } + +export const ROW_EXPORT_FORMATS = { + CSV: "csv", + JSON: "json", + JSON_WITH_SCHEMA: "jsonWithSchema", +} diff --git a/packages/client/src/components/app/forms/InnerForm.svelte b/packages/client/src/components/app/forms/InnerForm.svelte index c0b4b2b8a8..233ea79812 100644 --- a/packages/client/src/components/app/forms/InnerForm.svelte +++ b/packages/client/src/components/app/forms/InnerForm.svelte @@ -136,7 +136,7 @@ // Check arrays - remove any values not present in the field schema and // convert any values supplied to strings if (Array.isArray(value) && type === "array" && schema) { - const options = schema?.constraints.inclusion || [] + const options = schema?.constraints?.inclusion || [] return value.map(opt => String(opt)).filter(opt => options.includes(opt)) } return value diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 7f6f494621..a3e8d55661 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -92,7 +92,7 @@ export async function fetchView(ctx: any) { () => sdk.rows.fetchView(tableId, viewName, { calculation, - group, + group: calculation ? group : null, field, }), { diff --git a/packages/server/src/api/controllers/view/exporters.ts b/packages/server/src/api/controllers/view/exporters.ts index ec0aca95a9..e707cad0cc 100644 --- a/packages/server/src/api/controllers/view/exporters.ts +++ b/packages/server/src/api/controllers/view/exporters.ts @@ -27,7 +27,7 @@ export function json(rows: Row[]) { export function jsonWithSchema(schema: TableSchema, rows: Row[]) { const newSchema: TableSchema = {} Object.values(schema).forEach(column => { - if (!column.autocolumn) { + if (!column.autocolumn && column.name) { newSchema[column.name] = column } }) From 471a5838a340f927bad256946f5340ca88be233b Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 31 Aug 2023 08:50:06 +0100 Subject: [PATCH 209/316] Ensure null IDs are pruned from draggable list to avoid crashing --- .../settings/controls/DraggableList.svelte | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/builder/src/components/design/settings/controls/DraggableList.svelte b/packages/builder/src/components/design/settings/controls/DraggableList.svelte index b1f0e83654..c8395b2a1f 100644 --- a/packages/builder/src/components/design/settings/controls/DraggableList.svelte +++ b/packages/builder/src/components/design/settings/controls/DraggableList.svelte @@ -33,17 +33,19 @@ let anchors = {} let draggableItems = [] - const buildDragable = items => { - return items.map(item => { - return { - id: listItemKey ? item[listItemKey] : generate(), - item, - } - }) + const buildDraggable = items => { + return items + .map(item => { + return { + id: listItemKey ? item[listItemKey] : generate(), + item, + } + }) + .filter(item => item.id) } $: if (items) { - draggableItems = buildDragable(items) + draggableItems = buildDraggable(items) } const updateRowOrder = e => { From 8c889b873ca33944b5cb3203888adba1afa6f136 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 31 Aug 2023 08:50:36 +0100 Subject: [PATCH 210/316] Filter out invalid fields from fieldconfiguration (we don't allow formula and nested JSON fields in forms) --- .../controls/FieldConfiguration/FieldConfiguration.svelte | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte index 255f46ec7b..f12e8d27ae 100644 --- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte @@ -99,6 +99,9 @@ } const type = getComponentForField(instance.field, schema) + if (!type) { + return null + } instance._component = `@budibase/standard-components/${type}` const pseudoComponentInstance = store.actions.components.createInstance( @@ -116,7 +119,9 @@ } $: if (sanitisedFields) { - fieldList = [...sanitisedFields, ...unconfigured].map(buildSudoInstance) + fieldList = [...sanitisedFields, ...unconfigured] + .map(buildSudoInstance) + .filter(x => x != null) } const processItemUpdate = e => { From 2f36b2f805fdc3ccb3bd26b1b49ecdbfc0b40d03 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 31 Aug 2023 08:51:00 +0100 Subject: [PATCH 211/316] Ensure the default export value is only set once --- .../src/components/backend/DataTable/modals/ExportModal.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte b/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte index 0fc5fe96e0..09f76d3522 100644 --- a/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte @@ -42,7 +42,7 @@ let exportFormat let filterLookup - $: if (options) { + $: if (options && !exportFormat) { exportFormat = Array.isArray(options) ? options[0]?.key : [] } From e13ea88e42a01d22f082dafed9e32bd3f3dcdc73 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 31 Aug 2023 07:55:21 +0000 Subject: [PATCH 212/316] Bump version to 2.9.33-alpha.6 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 2ce9ac962f..694a22ccc9 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.9.33-alpha.5", + "version": "2.9.33-alpha.6", "npmClient": "yarn", "packages": [ "packages/*" From d36c7d744fd0294a9012c6c0569d5dde1777d615 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 31 Aug 2023 10:24:52 +0200 Subject: [PATCH 213/316] Rename --- .../backend/DataTable/buttons/ManageAccessButton.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte index f6a74784fa..5a7e274eb8 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte @@ -10,13 +10,13 @@ let modal let resourcePermissions - async function openDropdown() { + async function openModal() { resourcePermissions = await permissions.forResource(resourceId) modal.show() } - + Access From dcd8c3b2896ed5da567c64d305e2b62df8357f33 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 31 Aug 2023 10:36:17 +0200 Subject: [PATCH 214/316] Move permissions code to sdk --- .../server/src/api/controllers/permission.ts | 26 +------------ .../server/src/sdk/app/permissions/index.ts | 38 ++++++++++++++++++- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/packages/server/src/api/controllers/permission.ts b/packages/server/src/api/controllers/permission.ts index 8314f29398..ef138ee5c2 100644 --- a/packages/server/src/api/controllers/permission.ts +++ b/packages/server/src/api/controllers/permission.ts @@ -147,31 +147,7 @@ export async function fetch(ctx: UserCtx) { export async function getResourcePerms(ctx: UserCtx) { const resourceId = ctx.params.resourceId - const db = context.getAppDB() - const body = await db.allDocs( - getRoleParams(null, { - include_docs: true, - }) - ) - const rolesList = body.rows.map(row => row.doc) - let permissions: Record = {} - for (let level of SUPPORTED_LEVELS) { - // update the various roleIds in the resource permissions - for (let role of rolesList) { - const rolePerms = roles.checkForRoleResourceArray( - role.permissions, - resourceId - ) - if ( - rolePerms && - rolePerms[resourceId] && - rolePerms[resourceId].indexOf(level) !== -1 - ) { - permissions[level] = roles.getExternalRoleID(role._id, role.version)! - } - } - } - ctx.body = Object.assign(getBasePermissions(resourceId), permissions) + ctx.body = await sdk.permissions.getResourcePerms(resourceId) } export async function addPermission(ctx: UserCtx) { diff --git a/packages/server/src/sdk/app/permissions/index.ts b/packages/server/src/sdk/app/permissions/index.ts index 2219120db6..144b4fab2b 100644 --- a/packages/server/src/sdk/app/permissions/index.ts +++ b/packages/server/src/sdk/app/permissions/index.ts @@ -1,10 +1,15 @@ +import { context, roles } from "@budibase/backend-core" +import { features } from "@budibase/pro" import { DocumentType, PermissionLevel, VirtualDocumentType, } from "@budibase/types" -import { isViewID } from "../../../db/utils" -import { features } from "@budibase/pro" +import { getRoleParams, isViewID } from "../../../db/utils" +import { + CURRENTLY_SUPPORTED_LEVELS, + getBasePermissions, +} from "../../../utilities/security" type ResourceActionAllowedResult = | { allowed: true } @@ -35,3 +40,32 @@ export async function resourceActionAllowed({ resourceType: VirtualDocumentType.VIEW, } } + +export async function getResourcePerms(resourceId: string) { + const db = context.getAppDB() + const body = await db.allDocs( + getRoleParams(null, { + include_docs: true, + }) + ) + const rolesList = body.rows.map(row => row.doc) + let permissions: Record = {} + for (let level of CURRENTLY_SUPPORTED_LEVELS) { + // update the various roleIds in the resource permissions + for (let role of rolesList) { + const rolePerms = roles.checkForRoleResourceArray( + role.permissions, + resourceId + ) + if ( + rolePerms && + rolePerms[resourceId] && + rolePerms[resourceId].indexOf(level) !== -1 + ) { + permissions[level] = roles.getExternalRoleID(role._id, role.version)! + } + } + } + + return Object.assign(getBasePermissions(resourceId), permissions) +} From 566f9ecd22fc6caaaf0b9cb0ac332148db53607e Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 31 Aug 2023 10:53:25 +0200 Subject: [PATCH 215/316] Create remove permission js api --- packages/builder/src/stores/backend/permissions.js | 7 +++++++ packages/frontend-core/src/api/permissions.js | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/packages/builder/src/stores/backend/permissions.js b/packages/builder/src/stores/backend/permissions.js index aaab406bc9..ea7bb347e2 100644 --- a/packages/builder/src/stores/backend/permissions.js +++ b/packages/builder/src/stores/backend/permissions.js @@ -13,6 +13,13 @@ export function createPermissionStore() { level, }) }, + remove: async ({ level, role, resource }) => { + return await API.removePermissionFromResource({ + resourceId: resource, + roleId: role, + level, + }) + }, forResource: async resourceId => { return await API.getPermissionForResource(resourceId) }, diff --git a/packages/frontend-core/src/api/permissions.js b/packages/frontend-core/src/api/permissions.js index 5407cb3ce5..9ba0be23cd 100644 --- a/packages/frontend-core/src/api/permissions.js +++ b/packages/frontend-core/src/api/permissions.js @@ -21,4 +21,17 @@ export const buildPermissionsEndpoints = API => ({ url: `/api/permission/${roleId}/${resourceId}/${level}`, }) }, + + /** + * Remove the the permissions for a certain resource + * @param resourceId the ID of the resource to update + * @param roleId the ID of the role to update the permissions of + * @param level the level to remove the role for this resource + * @return {Promise<*>} + */ + removePermissionFromResource: async ({ resourceId, roleId, level }) => { + return await API.delete({ + url: `/api/permission/${roleId}/${resourceId}/${level}`, + }) + }, }) From ddf9e734f403d68c2fc5c62580ece1d3d06bcb44 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Thu, 31 Aug 2023 10:36:27 +0100 Subject: [PATCH 216/316] pr comments and bug fixes --- .../src/components/common/RoleSelect.svelte | 11 ++--- .../_components/BuilderSidePanel.svelte | 47 +++++++++---------- .../portal/users/groups/[groupId].svelte | 8 +++- .../_components/GroupAppsTableRenderer.svelte | 12 ++++- .../portal/users/users/[userId].svelte | 18 +++++-- .../_components/AppsTableRenderer.svelte | 21 ++++++--- packages/builder/src/stores/portal/groups.js | 6 ++- packages/frontend-core/src/api/user.js | 4 +- .../src/api/controllers/global/users.ts | 1 + 9 files changed, 81 insertions(+), 47 deletions(-) diff --git a/packages/builder/src/components/common/RoleSelect.svelte b/packages/builder/src/components/common/RoleSelect.svelte index 3d84ef2e22..82752554d5 100644 --- a/packages/builder/src/components/common/RoleSelect.svelte +++ b/packages/builder/src/components/common/RoleSelect.svelte @@ -23,7 +23,6 @@ const dispatch = createEventDispatcher() const RemoveID = "remove" - const CreatorID = "CREATOR" $: options = getOptions( $roles, @@ -47,7 +46,7 @@ if (allowCreator) { newRoles = [ { - _id: CreatorID, + _id: Constants.Roles.CREATOR, name: "Creator", tag: !$licensing.perAppBuildersEnabled && @@ -88,8 +87,6 @@ const onChange = e => { if (allowRemove && e.detail === RemoveID) { dispatch("remove") - } else if (e.detail === CreatorID) { - dispatch("addcreator") } else { dispatch("change", e.detail) } @@ -112,7 +109,8 @@ getOptionColour={getColor} getOptionIcon={getIcon} isOptionEnabled={option => - option._id !== CreatorID || $licensing.perAppBuildersEnabled} + option._id !== Constants.Roles.CREATOR || + $licensing.perAppBuildersEnabled} {placeholder} {error} /> @@ -131,7 +129,8 @@ getOptionColour={getColor} getOptionIcon={getIcon} isOptionEnabled={option => - option._id !== CreatorID || $licensing.perAppBuildersEnabled} + option._id !== Constants.Roles.CREATOR || + $licensing.perAppBuildersEnabled} {placeholder} {error} /> diff --git a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte index b304140a8d..3e6741e920 100644 --- a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte @@ -81,17 +81,15 @@ filteredInvites.sort(sortInviteRoles) return } - filteredInvites = appInvites - .filter(invite => { - const inviteInfo = invite.info?.apps - if (!query && inviteInfo && prodAppId) { - return Object.keys(inviteInfo).includes(prodAppId) - } - return invite.email.includes(query) - }) - .sort(sortInviteRoles) + filteredInvites = appInvites.filter(invite => { + const inviteInfo = invite.info?.apps + if (!query && inviteInfo && prodAppId) { + return Object.keys(inviteInfo).includes(prodAppId) + } + return invite.email.includes(query) + }) + filteredInvites.sort(sortInviteRoles) } - $: filterByAppAccess, prodAppId, filterInvites(query) $: if (searchFocus === true) { filterByAppAccess = false @@ -122,21 +120,22 @@ await usersFetch.refresh() filteredUsers = $usersFetch.rows + .filter(user => !user?.admin?.global) // filter out global admins .map(user => { const isAdminOrGlobalBuilder = sdk.users.isAdminOrGlobalBuilder( user, prodAppId ) const isAppBuilder = sdk.users.hasAppBuilderPermissions(user, prodAppId) - let role = undefined + let role if (isAdminOrGlobalBuilder) { role = Constants.Roles.ADMIN } else if (isAppBuilder) { role = Constants.Roles.CREATOR } else { - const appRole = Object.keys(user.roles).find(x => x === prodAppId) + const appRole = user.roles[prodAppId] if (appRole) { - role = user.roles[appRole] + role = appRole } } @@ -617,9 +616,6 @@ allowPublic={false} allowCreator={true} quiet={true} - on:addcreator={() => { - onUpdateUserInvite(invite, Constants.Roles.CREATOR) - }} on:change={e => { onUpdateUserInvite(invite, e.detail) }} @@ -673,10 +669,11 @@ quiet={true} allowCreator={true} on:change={e => { - onUpdateGroup(group, e.detail) - }} - on:addcreator={() => { - addGroupAppBuilder(group._id) + if (e.detail === Constants.Roles.CREATOR) { + addGroupAppBuilder(group._id) + } else { + onUpdateGroup(group, e.detail) + } }} on:remove={() => { onUpdateGroup(group) @@ -712,11 +709,13 @@ allowPublic={false} allowCreator={true} quiet={true} - on:addcreator={() => { - addAppBuilder(user._id) - }} + on:addcreator={() => {}} on:change={e => { - onUpdateUser(user, e.detail) + if (e.detail === Constants.Roles.CREATOR) { + addAppBuilder(user._id) + } else { + onUpdateUser(user, e.detail) + } }} on:remove={() => { onUpdateUser(user) diff --git a/packages/builder/src/pages/builder/portal/users/groups/[groupId].svelte b/packages/builder/src/pages/builder/portal/users/groups/[groupId].svelte index 6a4364c414..793ba5c09f 100644 --- a/packages/builder/src/pages/builder/portal/users/groups/[groupId].svelte +++ b/packages/builder/src/pages/builder/portal/users/groups/[groupId].svelte @@ -21,6 +21,7 @@ import GroupIcon from "./_components/GroupIcon.svelte" import GroupUsers from "./_components/GroupUsers.svelte" import { sdk } from "@budibase/shared-core" + import { Constants } from "@budibase/frontend-core" export let groupId @@ -45,7 +46,7 @@ let loaded = false let editModal, deleteModal - + $: console.log(group) $: scimEnabled = $features.isScimEnabled $: readonly = !sdk.users.isAdmin($auth.user) || scimEnabled $: group = $groups.find(x => x._id === groupId) @@ -57,8 +58,11 @@ ) .map(app => ({ ...app, - role: group?.roles?.[apps.getProdAppID(app.devId)], + role: group?.builder?.apps.includes(apps.getProdAppID(app.devId)) + ? Constants.Roles.CREATOR + : group?.roles?.[apps.getProdAppID(app.devId)], })) + $: console.log(groupApps) $: { if (loaded && !group?._id) { $goto("./") diff --git a/packages/builder/src/pages/builder/portal/users/groups/_components/GroupAppsTableRenderer.svelte b/packages/builder/src/pages/builder/portal/users/groups/_components/GroupAppsTableRenderer.svelte index 51f4d7f77c..48b1bfbce6 100644 --- a/packages/builder/src/pages/builder/portal/users/groups/_components/GroupAppsTableRenderer.svelte +++ b/packages/builder/src/pages/builder/portal/users/groups/_components/GroupAppsTableRenderer.svelte @@ -1,9 +1,19 @@
diff --git a/packages/builder/src/pages/builder/portal/users/users/[userId].svelte b/packages/builder/src/pages/builder/portal/users/users/[userId].svelte index 5ae6d7cb9d..e1f1c0e3a9 100644 --- a/packages/builder/src/pages/builder/portal/users/users/[userId].svelte +++ b/packages/builder/src/pages/builder/portal/users/users/[userId].svelte @@ -120,11 +120,7 @@ name: app.name, devId: app.devId, icon: app.icon, - role: privileged - ? Constants.Roles.ADMIN - : user?.builder?.apps.includes(prodAppId) - ? Constants.Roles.CREATOR - : roles[prodAppId], + role: getRole(prodAppId, roles), } }) } @@ -137,6 +133,18 @@ return groups.filter(group => group.name?.toLowerCase().includes(search)) } + const getRole = (prodAppId, roles) => { + if (privileged) { + return Constants.Roles.ADMIN + } + + if (user?.builder?.apps.includes(prodAppId)) { + return Constants.Roles.CREATOR + } + + return roles[prodAppId] + } + const getNameLabel = user => { const { firstName, lastName, email } = user || {} if (!firstName && !lastName) { diff --git a/packages/builder/src/pages/builder/portal/users/users/_components/AppsTableRenderer.svelte b/packages/builder/src/pages/builder/portal/users/users/_components/AppsTableRenderer.svelte index b454d2668c..b7bb810aba 100644 --- a/packages/builder/src/pages/builder/portal/users/users/_components/AppsTableRenderer.svelte +++ b/packages/builder/src/pages/builder/portal/users/users/_components/AppsTableRenderer.svelte @@ -5,13 +5,22 @@ export let value export let row - + $: console.log(row) $: priviliged = sdk.users.isAdminOrBuilder(row) - $: count = priviliged - ? $apps.length - : sdk.users.hasAppBuilderPermissions(row) - ? row?.builder?.apps?.length - : value?.length || 0 + $: count = getCount(row) + + const getCount = () => { + if (priviliged) { + return $apps.length + } else { + return sdk.users.hasAppBuilderPermissions(row) + ? row.builder.apps.length + + Object.keys(row.roles || {}).filter(appId => + row.builder.apps.includes(appId) + ).length + : value?.length || 0 + } + }
diff --git a/packages/builder/src/stores/portal/groups.js b/packages/builder/src/stores/portal/groups.js index a37a216a7f..a505029627 100644 --- a/packages/builder/src/stores/portal/groups.js +++ b/packages/builder/src/stores/portal/groups.js @@ -78,7 +78,11 @@ export function createGroupsStore() { }, getGroupAppIds: group => { - return Object.keys(group?.roles || {}) + let groupAppIds = Object.keys(group?.roles || {}) + if (group?.builder?.apps) { + groupAppIds = groupAppIds.concat(group.builder.apps) + } + return groupAppIds }, addGroupAppBuilder: async (groupId, appId) => { diff --git a/packages/frontend-core/src/api/user.js b/packages/frontend-core/src/api/user.js index 27b17ca60b..329e9cc499 100644 --- a/packages/frontend-core/src/api/user.js +++ b/packages/frontend-core/src/api/user.js @@ -163,7 +163,7 @@ export const buildUserEndpoints = API => ({ admin: admin ? { global: true } : undefined, builder: builder ? { global: true } : undefined, apps: apps ? apps : undefined, - appBuilders: appBuilders ? appBuilders : undefined, + appBuilders, }, } }), @@ -181,7 +181,7 @@ export const buildUserEndpoints = API => ({ url: `/api/global/users/invite/update/${invite.code}`, body: { apps: invite.apps, - appBuilders: invite.appBuilders ? invite.appBuilders : undefined, + appBuilders: invite.appBuilders, }, }) }, diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts index 5cf6bf857f..0ccf9a356f 100644 --- a/packages/worker/src/api/controllers/global/users.ts +++ b/packages/worker/src/api/controllers/global/users.ts @@ -417,6 +417,7 @@ export const inviteAccept = async ( if (info.appBuilders) { builder.apps = info.appBuilders request.builder = builder + delete info.appBuilders } delete info.apps request = { From 61e2aad44b57bad9a8cb5a27b3f58ad1341c2905 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Thu, 31 Aug 2023 10:59:50 +0100 Subject: [PATCH 217/316] remove more reference of developer --- .../src/pages/builder/portal/users/users/[userId].svelte | 2 +- packages/builder/src/stores/portal/users.js | 6 +----- packages/frontend-core/src/constants.js | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/users/users/[userId].svelte b/packages/builder/src/pages/builder/portal/users/users/[userId].svelte index e1f1c0e3a9..ce280c6ea0 100644 --- a/packages/builder/src/pages/builder/portal/users/users/[userId].svelte +++ b/packages/builder/src/pages/builder/portal/users/users/[userId].svelte @@ -138,7 +138,7 @@ return Constants.Roles.ADMIN } - if (user?.builder?.apps.includes(prodAppId)) { + if (user?.builder?.apps?.includes(prodAppId)) { return Constants.Roles.CREATOR } diff --git a/packages/builder/src/stores/portal/users.js b/packages/builder/src/stores/portal/users.js index 9cd5ed54ea..996a94013b 100644 --- a/packages/builder/src/stores/portal/users.js +++ b/packages/builder/src/stores/portal/users.js @@ -121,11 +121,7 @@ export function createUsersStore() { } const getUserRole = user => - sdk.users.isAdmin(user) - ? "admin" - : sdk.users.isBuilder(user) - ? "developer" - : "appUser" + sdk.users.isAdminOrGlobalBuilder(user) ? "admin" : "appUser" const refreshUsage = fn => diff --git a/packages/frontend-core/src/constants.js b/packages/frontend-core/src/constants.js index 7313140c57..906ad16423 100644 --- a/packages/frontend-core/src/constants.js +++ b/packages/frontend-core/src/constants.js @@ -24,7 +24,6 @@ export const BudibaseRoles = { export const BudibaseRoleOptions = [ { label: "Member", value: BudibaseRoles.AppUser }, - { label: "Developer", value: BudibaseRoles.Developer }, { label: "Admin", value: BudibaseRoles.Admin }, ] From 33adc9960b4dce1f6444d542767baaf7030278c7 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Thu, 31 Aug 2023 11:01:24 +0100 Subject: [PATCH 218/316] more developer references removed --- .../src/pages/builder/portal/users/users/[userId].svelte | 6 +----- .../portal/users/users/_components/RoleTableRenderer.svelte | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/users/users/[userId].svelte b/packages/builder/src/pages/builder/portal/users/users/[userId].svelte index ce280c6ea0..2a74cd9de5 100644 --- a/packages/builder/src/pages/builder/portal/users/users/[userId].svelte +++ b/packages/builder/src/pages/builder/portal/users/users/[userId].svelte @@ -98,11 +98,7 @@ return y._id === userId }) }) - $: globalRole = sdk.users.isAdmin(user) - ? "admin" - : sdk.users.isBuilder(user) - ? "developer" - : "appUser" + $: globalRole = sdk.users.isAdmin(user) ? "admin" : "appUser" const getAvailableApps = (appList, privileged, roles) => { let availableApps = appList.slice() diff --git a/packages/builder/src/pages/builder/portal/users/users/_components/RoleTableRenderer.svelte b/packages/builder/src/pages/builder/portal/users/users/_components/RoleTableRenderer.svelte index fe7acee6c4..8217c85c5e 100644 --- a/packages/builder/src/pages/builder/portal/users/users/_components/RoleTableRenderer.svelte +++ b/packages/builder/src/pages/builder/portal/users/users/_components/RoleTableRenderer.svelte @@ -5,7 +5,7 @@ export let row const TooltipMap = { - appUser: "Only has access to published apps", + appUser: "Only has access to assigned apps", developer: "Access to the app builder", admin: "Full access", } From 1210af3563eb7a2d61389189c57baf5ab6f6ace4 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 31 Aug 2023 11:12:19 +0100 Subject: [PATCH 219/316] Fix: Remove unused usage of `@budibase/types` in frontend-core --- packages/frontend-core/src/constants.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/frontend-core/src/constants.js b/packages/frontend-core/src/constants.js index 93a857ef9d..b049375787 100644 --- a/packages/frontend-core/src/constants.js +++ b/packages/frontend-core/src/constants.js @@ -2,7 +2,6 @@ * Operator options for lucene queries */ export { OperatorOptions, SqlNumberTypeRangeMap } from "@budibase/shared-core" -export { Feature as Features } from "@budibase/types" // Cookie names export const Cookies = { From 72b8b01ba8bc23f2760dd02593006b2a3bc7b76c Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 31 Aug 2023 10:16:37 +0000 Subject: [PATCH 220/316] Bump version to 2.9.33-alpha.7 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 694a22ccc9..ef7d4ec57a 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.9.33-alpha.6", + "version": "2.9.33-alpha.7", "npmClient": "yarn", "packages": [ "packages/*" From bbc484e2c454eb6fa083c2e89730e9f7021dbaba Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 31 Aug 2023 13:00:51 +0200 Subject: [PATCH 221/316] Handle frontend levels --- packages/builder/src/stores/backend/permissions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/stores/backend/permissions.js b/packages/builder/src/stores/backend/permissions.js index ea7bb347e2..0807c1cf10 100644 --- a/packages/builder/src/stores/backend/permissions.js +++ b/packages/builder/src/stores/backend/permissions.js @@ -21,7 +21,7 @@ export function createPermissionStore() { }) }, forResource: async resourceId => { - return await API.getPermissionForResource(resourceId) + return (await API.getPermissionForResource(resourceId)).permissions }, } } From a56712f4d7c79f2baea12277ad04109eca958f12 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 31 Aug 2023 13:01:17 +0200 Subject: [PATCH 222/316] Change shape --- .../server/src/api/controllers/permission.ts | 4 +- .../server/src/sdk/app/permissions/index.ts | 54 +++++++++++++++---- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/packages/server/src/api/controllers/permission.ts b/packages/server/src/api/controllers/permission.ts index ef138ee5c2..ea9b9a4596 100644 --- a/packages/server/src/api/controllers/permission.ts +++ b/packages/server/src/api/controllers/permission.ts @@ -147,7 +147,9 @@ export async function fetch(ctx: UserCtx) { export async function getResourcePerms(ctx: UserCtx) { const resourceId = ctx.params.resourceId - ctx.body = await sdk.permissions.getResourcePerms(resourceId) + ctx.body = { + permissions: await sdk.permissions.getResourcePerms(resourceId), + } } export async function addPermission(ctx: UserCtx) { diff --git a/packages/server/src/sdk/app/permissions/index.ts b/packages/server/src/sdk/app/permissions/index.ts index 144b4fab2b..cb4ce93250 100644 --- a/packages/server/src/sdk/app/permissions/index.ts +++ b/packages/server/src/sdk/app/permissions/index.ts @@ -3,9 +3,14 @@ import { features } from "@budibase/pro" import { DocumentType, PermissionLevel, + Role, VirtualDocumentType, } from "@budibase/types" -import { getRoleParams, isViewID } from "../../../db/utils" +import { + extractViewInfoFromID, + getRoleParams, + isViewID, +} from "../../../db/utils" import { CURRENTLY_SUPPORTED_LEVELS, getBasePermissions, @@ -41,15 +46,31 @@ export async function resourceActionAllowed({ } } -export async function getResourcePerms(resourceId: string) { +type ResourcePermissions = Record< + string, + { + role: string + inherited?: boolean | undefined + } +> + +export async function getResourcePerms( + resourceId: string +): Promise { const db = context.getAppDB() const body = await db.allDocs( getRoleParams(null, { include_docs: true, }) ) - const rolesList = body.rows.map(row => row.doc) - let permissions: Record = {} + const rolesList = body.rows.map(row => row.doc) + let permissions: Record = {} + + let parentResourceToCheck + if (isViewID(resourceId) && (await features.isViewPermissionEnabled())) { + parentResourceToCheck = extractViewInfoFromID(resourceId).tableId + } + for (let level of CURRENTLY_SUPPORTED_LEVELS) { // update the various roleIds in the resource permissions for (let role of rolesList) { @@ -57,15 +78,28 @@ export async function getResourcePerms(resourceId: string) { role.permissions, resourceId ) - if ( - rolePerms && - rolePerms[resourceId] && - rolePerms[resourceId].indexOf(level) !== -1 + if (rolePerms[resourceId]?.indexOf(level) > -1) { + permissions[level] = { + role: roles.getExternalRoleID(role._id!, role.version), + } + } else if ( + parentResourceToCheck && + rolePerms[parentResourceToCheck]?.indexOf(level) > -1 ) { - permissions[level] = roles.getExternalRoleID(role._id, role.version)! + permissions[level] = { + role: roles.getExternalRoleID(role._id!, role.version), + inherited: true, + } } } } - return Object.assign(getBasePermissions(resourceId), permissions) + const basePermissions = Object.entries( + getBasePermissions(resourceId) + ).reduce((p, [level, role]) => { + p[level] = { role } + return p + }, {}) + const result = Object.assign(basePermissions, permissions) + return result } From 2308d99e4c851da0c2cc163c979af5171bc39575 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 31 Aug 2023 11:23:02 +0000 Subject: [PATCH 223/316] Bump version to 2.9.33-alpha.8 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index ef7d4ec57a..22aea7d0ae 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.9.33-alpha.7", + "version": "2.9.33-alpha.8", "npmClient": "yarn", "packages": [ "packages/*" From fc5c63df55f4483054b7741f226267e3cb771950 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 31 Aug 2023 14:49:08 +0100 Subject: [PATCH 224/316] Revert "Fix: Remove unused usage of `@budibase/types` in frontend-core" --- packages/frontend-core/src/constants.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend-core/src/constants.js b/packages/frontend-core/src/constants.js index 198c8985d4..198d88196b 100644 --- a/packages/frontend-core/src/constants.js +++ b/packages/frontend-core/src/constants.js @@ -2,6 +2,7 @@ * Operator options for lucene queries */ export { OperatorOptions, SqlNumberTypeRangeMap } from "@budibase/shared-core" +export { Feature as Features } from "@budibase/types" // Cookie names export const Cookies = { From cd30c45ca5c03dc52f6758840b0b24d2f9d6d936 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 31 Aug 2023 14:50:28 +0100 Subject: [PATCH 225/316] Fix for error seen when creating blank screen --- .../src/builderStore/store/screenTemplates/rowListScreen.js | 3 +++ .../design/_components/NewScreen/CreateScreenModal.svelte | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js index 968d6deb4a..54dd38ac63 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js @@ -3,6 +3,9 @@ import { Screen } from "./utils/Screen" import { Component } from "./utils/Component" export default function (datasources) { + if (!Array.isArray(datasources)) { + return [] + } return datasources.map(datasource => { return { name: `${datasource.name} - List`, diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte index dd611041ed..9a96242b30 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte @@ -110,7 +110,7 @@ if (mode === "table") { datasourceModal.show() } else if (mode === "blank") { - let templates = getTemplates($store, $tables.list) + let templates = getTemplates($tables.list) const blankScreenTemplate = templates.find( t => t.id === "createFromScratch" ) From 5d483cb6474cf0b6d513cde32cd0d7eedd2ff838 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 31 Aug 2023 14:05:32 +0000 Subject: [PATCH 226/316] Bump version to 2.9.33-alpha.9 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 22aea7d0ae..c7a5f2a2a6 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.9.33-alpha.8", + "version": "2.9.33-alpha.9", "npmClient": "yarn", "packages": [ "packages/*" From 00a4cb68bd660312fd75eec193a2fc5dc7dd3f53 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 31 Aug 2023 14:44:25 +0000 Subject: [PATCH 227/316] Bump version to 2.9.33-alpha.10 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index c7a5f2a2a6..6a6ce0b266 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.9.33-alpha.9", + "version": "2.9.33-alpha.10", "npmClient": "yarn", "packages": [ "packages/*" From 774ff745b6a640f7b7e5ff0cf9904a04146ce02d Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 1 Sep 2023 09:40:29 +0200 Subject: [PATCH 228/316] Return role origin --- .../server/src/api/controllers/permission.ts | 29 +++++++++++++++-- .../server/src/sdk/app/permissions/index.ts | 31 ++++++++++--------- packages/types/src/api/web/app/index.ts | 1 + packages/types/src/api/web/app/permission.ts | 4 +++ 4 files changed, 48 insertions(+), 17 deletions(-) create mode 100644 packages/types/src/api/web/app/permission.ts diff --git a/packages/server/src/api/controllers/permission.ts b/packages/server/src/api/controllers/permission.ts index ea9b9a4596..8cc5bcec74 100644 --- a/packages/server/src/api/controllers/permission.ts +++ b/packages/server/src/api/controllers/permission.ts @@ -1,5 +1,11 @@ import { permissions, roles, context, HTTPError } from "@budibase/backend-core" -import { UserCtx, Database, Role, PermissionLevel } from "@budibase/types" +import { + UserCtx, + Database, + Role, + PermissionLevel, + GetResourcePermsResponse, +} from "@budibase/types" import { getRoleParams } from "../../db/utils" import { CURRENTLY_SUPPORTED_LEVELS, @@ -145,10 +151,27 @@ export async function fetch(ctx: UserCtx) { ctx.body = finalPermissions } -export async function getResourcePerms(ctx: UserCtx) { +export async function getResourcePerms( + ctx: UserCtx +) { const resourceId = ctx.params.resourceId + const resourcePermissions = await sdk.permissions.getResourcePerms(resourceId) + ctx.body = { - permissions: await sdk.permissions.getResourcePerms(resourceId), + permissions: Object.entries(resourcePermissions).reduce( + (p, [level, role]) => { + p[level] = role.role + return p + }, + {} as Record + ), + permissionType: Object.entries(resourcePermissions).reduce( + (p, [level, role]) => { + p[level] = role.type + return p + }, + {} as Record + ), } } diff --git a/packages/server/src/sdk/app/permissions/index.ts b/packages/server/src/sdk/app/permissions/index.ts index cb4ce93250..b1ebbd2ac8 100644 --- a/packages/server/src/sdk/app/permissions/index.ts +++ b/packages/server/src/sdk/app/permissions/index.ts @@ -46,12 +46,15 @@ export async function resourceActionAllowed({ } } +enum PermissionType { + EXPLICIT = "explicit", + INHERITED = "inherited", + BASE = "base", +} + type ResourcePermissions = Record< string, - { - role: string - inherited?: boolean | undefined - } + { role: string; type: PermissionType } > export async function getResourcePerms( @@ -64,11 +67,13 @@ export async function getResourcePerms( }) ) const rolesList = body.rows.map(row => row.doc) - let permissions: Record = {} + let permissions: ResourcePermissions = {} - let parentResourceToCheck + let permsToInherit: ResourcePermissions | undefined if (isViewID(resourceId) && (await features.isViewPermissionEnabled())) { - parentResourceToCheck = extractViewInfoFromID(resourceId).tableId + permsToInherit = await getResourcePerms( + extractViewInfoFromID(resourceId).tableId + ) } for (let level of CURRENTLY_SUPPORTED_LEVELS) { @@ -81,14 +86,12 @@ export async function getResourcePerms( if (rolePerms[resourceId]?.indexOf(level) > -1) { permissions[level] = { role: roles.getExternalRoleID(role._id!, role.version), + type: PermissionType.EXPLICIT, } - } else if ( - parentResourceToCheck && - rolePerms[parentResourceToCheck]?.indexOf(level) > -1 - ) { + } else if (permsToInherit && permsToInherit[level]) { permissions[level] = { - role: roles.getExternalRoleID(role._id!, role.version), - inherited: true, + role: permsToInherit[level].role, + type: PermissionType.INHERITED, } } } @@ -97,7 +100,7 @@ export async function getResourcePerms( const basePermissions = Object.entries( getBasePermissions(resourceId) ).reduce((p, [level, role]) => { - p[level] = { role } + p[level] = { role, type: PermissionType.BASE } return p }, {}) const result = Object.assign(basePermissions, permissions) diff --git a/packages/types/src/api/web/app/index.ts b/packages/types/src/api/web/app/index.ts index e7b4b87aa9..276d7fa7c1 100644 --- a/packages/types/src/api/web/app/index.ts +++ b/packages/types/src/api/web/app/index.ts @@ -4,3 +4,4 @@ export * from "./row" export * from "./view" export * from "./rows" export * from "./table" +export * from "./permission" diff --git a/packages/types/src/api/web/app/permission.ts b/packages/types/src/api/web/app/permission.ts new file mode 100644 index 0000000000..52f6bdcabb --- /dev/null +++ b/packages/types/src/api/web/app/permission.ts @@ -0,0 +1,4 @@ +export interface GetResourcePermsResponse { + permissions: Record + permissionType: Record +} From 5d870fb41a2dd1920419d37ea6259451a2cdb655 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 1 Sep 2023 09:50:55 +0200 Subject: [PATCH 229/316] Rename --- .../server/src/api/controllers/permission.ts | 8 +++++ .../server/src/sdk/app/permissions/index.ts | 31 ++++++++++--------- packages/types/src/api/web/app/permission.ts | 1 + 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/packages/server/src/api/controllers/permission.ts b/packages/server/src/api/controllers/permission.ts index 8cc5bcec74..6702c24450 100644 --- a/packages/server/src/api/controllers/permission.ts +++ b/packages/server/src/api/controllers/permission.ts @@ -156,6 +156,8 @@ export async function getResourcePerms( ) { const resourceId = ctx.params.resourceId const resourcePermissions = await sdk.permissions.getResourcePerms(resourceId) + const inheritablePermissions = + await sdk.permissions.getInheritablePermissions(resourceId) ctx.body = { permissions: Object.entries(resourcePermissions).reduce( @@ -172,6 +174,12 @@ export async function getResourcePerms( }, {} as Record ), + inheritablePermissions: + inheritablePermissions && + Object.entries(inheritablePermissions).reduce((p, [level, role]) => { + p[level] = role.role + return p + }, {} as Record), } } diff --git a/packages/server/src/sdk/app/permissions/index.ts b/packages/server/src/sdk/app/permissions/index.ts index b1ebbd2ac8..ce6ac8c98a 100644 --- a/packages/server/src/sdk/app/permissions/index.ts +++ b/packages/server/src/sdk/app/permissions/index.ts @@ -46,17 +46,25 @@ export async function resourceActionAllowed({ } } -enum PermissionType { - EXPLICIT = "explicit", - INHERITED = "inherited", - BASE = "base", +enum PermissionSource { + EXPLICIT = "EXPLICIT", + INHERITED = "INHERITED", + BASE = "BASE", } type ResourcePermissions = Record< string, - { role: string; type: PermissionType } + { role: string; type: PermissionSource } > +export async function getInheritablePermissions( + resourceId: string +): Promise { + if (isViewID(resourceId) && (await features.isViewPermissionEnabled())) { + return await getResourcePerms(extractViewInfoFromID(resourceId).tableId) + } +} + export async function getResourcePerms( resourceId: string ): Promise { @@ -69,12 +77,7 @@ export async function getResourcePerms( const rolesList = body.rows.map(row => row.doc) let permissions: ResourcePermissions = {} - let permsToInherit: ResourcePermissions | undefined - if (isViewID(resourceId) && (await features.isViewPermissionEnabled())) { - permsToInherit = await getResourcePerms( - extractViewInfoFromID(resourceId).tableId - ) - } + const permsToInherit = await getInheritablePermissions(resourceId) for (let level of CURRENTLY_SUPPORTED_LEVELS) { // update the various roleIds in the resource permissions @@ -86,12 +89,12 @@ export async function getResourcePerms( if (rolePerms[resourceId]?.indexOf(level) > -1) { permissions[level] = { role: roles.getExternalRoleID(role._id!, role.version), - type: PermissionType.EXPLICIT, + type: PermissionSource.EXPLICIT, } } else if (permsToInherit && permsToInherit[level]) { permissions[level] = { role: permsToInherit[level].role, - type: PermissionType.INHERITED, + type: PermissionSource.INHERITED, } } } @@ -100,7 +103,7 @@ export async function getResourcePerms( const basePermissions = Object.entries( getBasePermissions(resourceId) ).reduce((p, [level, role]) => { - p[level] = { role, type: PermissionType.BASE } + p[level] = { role, type: PermissionSource.BASE } return p }, {}) const result = Object.assign(basePermissions, permissions) diff --git a/packages/types/src/api/web/app/permission.ts b/packages/types/src/api/web/app/permission.ts index 52f6bdcabb..73ad47d83f 100644 --- a/packages/types/src/api/web/app/permission.ts +++ b/packages/types/src/api/web/app/permission.ts @@ -1,4 +1,5 @@ export interface GetResourcePermsResponse { permissions: Record permissionType: Record + inheritablePermissions?: Record } From 3192bbe4ddc865959c05ae80325b0fe967123443 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 1 Sep 2023 09:56:12 +0200 Subject: [PATCH 230/316] Clean --- .../backend/DataTable/buttons/ManageAccessButton.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte index 5a7e274eb8..1de31bf83b 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte @@ -23,7 +23,6 @@ From bdaf179f20e2681db2033e5cc078242ed4c6b006 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 1 Sep 2023 10:16:49 +0200 Subject: [PATCH 231/316] Remove placeholder --- .../backend/DataTable/modals/ManageAccessModal.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte b/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte index 678fb3b1c5..ff2db2088e 100644 --- a/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte @@ -54,7 +54,7 @@ {#each Object.keys(permissions) as level} Date: Fri, 1 Sep 2023 11:33:59 +0200 Subject: [PATCH 234/316] Change api interfaces --- .../FlowChart/FlowItem.svelte | 2 +- .../buttons/ManageAccessButton.svelte | 7 +--- .../grid/GridManageAccessButton.svelte | 14 +------- .../DataTable/modals/ManageAccessModal.svelte | 35 +++++++++---------- .../integration/AccessLevelSelect.svelte | 2 +- .../server/src/api/controllers/permission.ts | 26 ++++++-------- .../server/src/sdk/app/permissions/index.ts | 33 +++++++++++++---- packages/types/src/api/web/app/permission.ts | 15 +++++--- 8 files changed, 69 insertions(+), 65 deletions(-) diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte index 7a02433411..85c3776fdb 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte @@ -73,7 +73,7 @@ if (!perms["execute"]) { role = "BASIC" } else { - role = perms["execute"] + role = perms["execute"].role } } diff --git a/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte index 8742eaef76..5c0b7df742 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte @@ -5,7 +5,6 @@ export let resourceId export let disabled = false - export let requiresLicence let modal let resourcePermissions @@ -20,9 +19,5 @@ Access - + diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte index e4c48528f4..0cd008bab1 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte @@ -1,5 +1,4 @@ - + diff --git a/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte b/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte index 0e792410ca..5b0f114534 100644 --- a/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte @@ -15,7 +15,6 @@ export let resourceId export let permissions - export let requiresLicence async function changePermission(level, role) { try { @@ -41,24 +40,20 @@ } } - $: computedPermissions = Object.keys(permissions.permissions).reduce( - (p, c) => { - p[c] = { - selected: - permissions.permissionType[c] === "INHERITED" - ? "inherited" - : permissions.permissions[c], + $: computedPermissions = Object.entries(permissions.permissions).reduce( + (p, [level, roleInfo]) => { + p[level] = { + selectedValue: + roleInfo.permissionType === "INHERITED" ? "inherited" : roleInfo.role, options: [...get(roles)], } - if (permissions.inheritablePermissions) { - p[c].inheritOption = permissions.inheritablePermissions[c] - p[c].options.unshift({ + if (roleInfo.inheritablePermission) { + p[level].inheritOption = roleInfo.inheritablePermission + p[level].options.unshift({ _id: "inherited", name: `Inherit (${ - get(roles).find( - x => x._id === permissions.inheritablePermissions[c] - ).name + get(roles).find(x => x._id === roleInfo.inheritablePermission).name })`, }) } @@ -66,21 +61,23 @@ }, {} ) + + $: requiresPlanToModify = permissions.requiresPlanToModify Manage Access - {#if requiresLicence} + {#if requiresPlanToModify} - {requiresLicence.tier} + {requiresPlanToModify} {/if} - {#if requiresLicence} - {requiresLicence.message} + {#if requiresPlanToModify} + {requiresPlanToModify} {:else} Specify the minimum access level role for this data.
@@ -90,7 +87,7 @@ - + - + {#if mounted} + + {/if} update("navWidth", e.detail)} + /> + {/if} +
+ +
+ update("hideLogo", !e.detail)} + /> + {#if !$store.navigation.hideLogo} +
+ +
+ update("logoUrl", e.detail)} + updateOnChange={false} + /> + {/if} +
+ +
+ update("hideTitle", !e.detail)} + /> + {#if !$store.navigation.hideTitle} +
+ +
+ update("title", e.detail)} + updateOnChange={false} + /> + {/if} +
+ +
+ update("navBackground", e.detail)} + /> +
+ +
+ update("navTextColor", e.detail)} + /> +
+
+ {/if} + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/_components/AppThemeSelect.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/AppThemeSelect.svelte similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/_components/AppThemeSelect.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/AppThemeSelect.svelte diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/_components/ButtonRoundnessSelect.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/ButtonRoundnessSelect.svelte similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/theme/_components/ButtonRoundnessSelect.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/ButtonRoundnessSelect.svelte diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte similarity index 72% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte index 8982cd20de..a08ded8eee 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenSettingsPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte @@ -1,12 +1,8 @@ - - - {#if $selectedScreen.layoutId} - - This screen uses a custom layout, which is deprecated - - {/if} - {#each screenSettings as setting (setting.key)} - setScreenSetting(setting, val)} - props={{ ...setting.props, error: errors[setting.key] }} - {bindings} - /> - {/each} - - - +{#if $selectedScreen.layoutId} + + This screen uses a custom layout, which is deprecated + +{/if} +{#each screenSettings as setting (setting.key)} + setScreenSetting(setting, val)} + props={{ ...setting.props, error: errors[setting.key] }} + {bindings} + /> +{/each} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/ThemePanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/ThemePanel.svelte new file mode 100644 index 0000000000..13a008fae0 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/ThemePanel.svelte @@ -0,0 +1,78 @@ + + +
+ + These settings apply to all screens +
+ + + + + + + update("buttonBorderRadius", e.detail)} + /> + + update("primaryColor", val)} + props={{ + spectrumTheme: $store.theme, + }} + /> + update("primaryColorHover", val)} + props={{ + spectrumTheme: $store.theme, + }} + /> + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/index.svelte new file mode 100644 index 0000000000..778fa303cc --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/index.svelte @@ -0,0 +1,51 @@ + + + +
+
+ {#each tabs as tab} + { + activeTab = tab + }} + > + {capitalise(tab)} + + {/each} +
+
+ + {#if activeTab === "theme"} + + {:else} + + {/if} + +
+ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_layout.svelte new file mode 100644 index 0000000000..19cb1d8dff --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_layout.svelte @@ -0,0 +1,52 @@ + + +{#if routeComponentId === `${$store.selectedScreenId}-screen`} + +{:else if routeComponentId === `${$store.selectedScreenId}-navigation`} + +{:else} + +{/if} + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/index.svelte new file mode 100644 index 0000000000..0ff63d1ead --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/index.svelte @@ -0,0 +1 @@ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/NewComponentPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/NewComponentPanel.svelte similarity index 98% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/NewComponentPanel.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/NewComponentPanel.svelte index 7dca5b792b..248bbc8141 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/NewComponentPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/NewComponentPanel.svelte @@ -31,6 +31,10 @@ $: orderMap = createComponentOrderMap(componentList) const getAllowedComponents = (allComponents, screen, component) => { + // Default to using the root screen container if no component specified + if (!component) { + component = screen.props + } const path = findComponentPath(screen?.props, component?._id) if (!path?.length) { return [] diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/componentStructure.json b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/componentStructure.json rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/index.svelte similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/index.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/index.svelte diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte index 785b221239..09f97302fd 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte @@ -1,32 +1,16 @@
- +
+ Screens +
+
+ +
+
+ +
+
+
+ {#if filteredScreens?.length} + {#each filteredScreens as screen (screen._id)} + store.actions.screens.select(screen._id)} + rightAlignIcon + showTooltip + selectedBy={$userSelectedResourceMap[screen._id]} + > + +
+ +
+
+ {/each} + {:else} + +
+ There aren't any screens matching that route +
+
+ {/if} +
+ +
screensHeight.set("210px")} + /> +
+ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_fallback.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_fallback.svelte deleted file mode 100644 index 00165e4ee9..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_fallback.svelte +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_layout.svelte index 8bc0dcc3e5..0e630b4f39 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_layout.svelte @@ -1,14 +1,10 @@ -
-
- - $goto("./screens")} - /> - $goto("./components")} - /> - $goto("./theme")} - /> - $goto("./navigation")} - /> - {#if $store.layouts?.length} - $goto("./layouts")} - /> - {/if} - -
- -
- {#if $selectedScreen} - +{#if $selectedScreen} +
+
+ - {/if} + +
-
+{/if} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte deleted file mode 100644 index 9513753d76..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte +++ /dev/null @@ -1,90 +0,0 @@ - - - -
- -
- -
    -
  • - { - $store.selectedComponentId = $selectedScreen?.props._id - }} - id={`component-${$selectedScreen?.props._id}`} - selectedBy={$userSelectedResourceMap[$selectedScreen?.props._id]} - > - - - - - - {#if $dndStore.dragging && $dndStore.valid} - - {#if $dndStore.dropPosition !== DropPosition.INSIDE} - - {/if} - {/if} -
  • -
-
-
- - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_layout.svelte deleted file mode 100644 index 860258c940..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_layout.svelte +++ /dev/null @@ -1,41 +0,0 @@ - - - - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/index.svelte deleted file mode 100644 index 9b5d05fe57..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/index.svelte +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/index.svelte deleted file mode 100644 index f8c4cc0868..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/index.svelte +++ /dev/null @@ -1,18 +0,0 @@ - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/index.svelte index f5e3806bd6..c4ed7d949c 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/index.svelte @@ -1,5 +1,6 @@ diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutDropdownMenu.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutDropdownMenu.svelte deleted file mode 100644 index cba68f899d..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutDropdownMenu.svelte +++ /dev/null @@ -1,41 +0,0 @@ - - - -
- -
- Delete -
- - - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutListPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutListPanel.svelte deleted file mode 100644 index cc895317fd..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutListPanel.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - - -
- {#each $store.layouts as layout (layout._id)} - store.actions.layouts.select(layout._id)} - > - - - {/each} -
-
- - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutSettingsPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutSettingsPanel.svelte deleted file mode 100644 index bfc2f94f43..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_components/LayoutSettingsPanel.svelte +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - Custom layouts are being deprecated. They will be removed in a future - release. - - - You can save the content of this layout by pressing the button below. - - - This will copy all components inside your layout, which you can then paste - into a screen. - - - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_layout.svelte deleted file mode 100644 index c82fefab3e..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/_layout.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/index.svelte deleted file mode 100644 index 4d39403bc3..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/[layoutId]/index.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/_layout.svelte deleted file mode 100644 index 1333c6afe3..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/_layout.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/index.svelte deleted file mode 100644 index 09d45f8fde..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/layouts/index.svelte +++ /dev/null @@ -1,12 +0,0 @@ - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel.svelte deleted file mode 100644 index 614e1eed80..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationInfoPanel.svelte +++ /dev/null @@ -1,33 +0,0 @@ - - - - - {#if $selectedScreen.layoutId} - - You can't preview your navigation settings using this screen as it uses - a custom layout, which is deprecated - - {/if} - - Your navigation is configured for all the screens within your app. - - - You can hide and show your navigation for each screen in the screen - settings. - - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationSettingsPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationSettingsPanel.svelte deleted file mode 100644 index c6d43984b2..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/_components/NavigationSettingsPanel.svelte +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - update("navigation", "Top")} - /> - update("navigation", "Left")} - /> - - - {#if $store.navigation.navigation === "Top"} - update("sticky", e.detail)} - /> - update("logoUrl", e.detail)} - placeholder="Add logo URL" - updateOnChange={false} - /> - {/if} - - - update("hideTitle", !e.detail)} - /> - {#if !$store.navigation.hideTitle} - update("title", e.detail)} - placeholder="Add title" - updateOnChange={false} - /> - {/if} - - - - update("navBackground", e.detail)} - /> - - - - update("navTextColor", e.detail)} - /> - - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/index.svelte deleted file mode 100644 index fc2e03d8e8..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/navigation/index.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenListPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenListPanel.svelte deleted file mode 100644 index 6362af3073..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenListPanel.svelte +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - (searchString = e.detail)} - /> - + {#if searching} +
+ +
+ {/if} +
+ {/if} +
{/if} + + diff --git a/packages/frontend-core/src/index.js b/packages/frontend-core/src/index.js index 01bf05c69e..b0afc0c25d 100644 --- a/packages/frontend-core/src/index.js +++ b/packages/frontend-core/src/index.js @@ -1,5 +1,6 @@ export { createAPIClient } from "./api" export { fetchData } from "./fetch/fetchData" +export { Utils } from "./utils" export * as Constants from "./constants" export * from "./stores" export * from "./utils" From 066d8ff8d3f7a6e431bc68688e8ad955166aa352 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Wed, 6 Sep 2023 15:38:30 +0000 Subject: [PATCH 315/316] Bump version to 2.9.39-alpha.10 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 07de18b1d3..dc6caf3326 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.9.39-alpha.9", + "version": "2.9.39-alpha.10", "npmClient": "yarn", "packages": [ "packages/*" From ceabccaaea1d0a8e3f598ec2136f8026236789c5 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Wed, 6 Sep 2023 17:30:39 +0000 Subject: [PATCH 316/316] Bump version to 2.9.39-alpha.11 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index dc6caf3326..7f0341ceb5 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.9.39-alpha.10", + "version": "2.9.39-alpha.11", "npmClient": "yarn", "packages": [ "packages/*"