From 453386696fd60c4b3f3b6035eb36a37b73d7a55a Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 24 Jan 2022 12:37:22 +0000 Subject: [PATCH] Refactor all backend stores and their usages to use new core API and handle errors --- packages/builder/src/builderStore/api.js | 6 -- .../DataTable/modals/CalculateModal.svelte | 10 +- .../DataTable/modals/CreateEditColumn.svelte | 26 +++-- .../DataTable/modals/CreateViewModal.svelte | 22 ++-- .../DataTable/modals/FilterModal.svelte | 14 ++- .../DataTable/modals/GroupByModal.svelte | 8 +- .../DataTable/modals/ManageAccessModal.svelte | 20 ++-- .../DatasourceNavigator.svelte | 9 +- .../CreateExternalTableModal.svelte | 14 ++- .../PlusConfigForm.svelte | 4 +- .../modals/CreateDatasourceModal.svelte | 10 +- .../modals/DatasourceConfigModal.svelte | 2 +- .../modals/ImportRestQueriesModal.svelte | 4 +- .../popovers/EditDatasourcePopover.svelte | 40 +++---- .../popovers/EditQueryPopover.svelte | 26 +++-- .../popovers/EditTablePopover.svelte | 4 +- .../popovers/EditViewPopover.svelte | 14 ++- .../integration/AccessLevelSelect.svelte | 24 +++-- .../components/integration/QueryViewer.svelte | 11 +- .../rest/[query]/index.svelte | 34 ++++-- .../builder/src/stores/backend/datasources.js | 55 +++++----- packages/builder/src/stores/backend/flags.js | 28 ++--- .../src/stores/backend/integrations.js | 8 +- .../builder/src/stores/backend/permissions.js | 14 +-- .../builder/src/stores/backend/queries.js | 102 ++++++++---------- packages/builder/src/stores/backend/roles.js | 30 +++--- packages/builder/src/stores/backend/tables.js | 50 +++++---- packages/builder/src/stores/backend/views.js | 12 +-- packages/builder/src/stores/portal/email.js | 56 +++++----- packages/frontend-core/src/api/datasources.js | 57 ++++++++++ packages/frontend-core/src/api/flags.js | 25 +++++ packages/frontend-core/src/api/index.js | 10 ++ packages/frontend-core/src/api/permissions.js | 24 +++++ packages/frontend-core/src/api/queries.js | 61 +++++++++++ packages/frontend-core/src/api/roles.js | 32 ++++++ packages/frontend-core/src/api/tables.js | 31 ++++++ packages/frontend-core/src/api/templates.js | 28 +++++ packages/frontend-core/src/api/views.js | 29 ++++- 38 files changed, 635 insertions(+), 319 deletions(-) create mode 100644 packages/frontend-core/src/api/datasources.js create mode 100644 packages/frontend-core/src/api/flags.js create mode 100644 packages/frontend-core/src/api/permissions.js create mode 100644 packages/frontend-core/src/api/roles.js create mode 100644 packages/frontend-core/src/api/templates.js diff --git a/packages/builder/src/builderStore/api.js b/packages/builder/src/builderStore/api.js index bd7cccbc5a..3dd6c28031 100644 --- a/packages/builder/src/builderStore/api.js +++ b/packages/builder/src/builderStore/api.js @@ -5,7 +5,6 @@ import { } from "@budibase/frontend-core" import { store } from "./index" import { get } from "svelte/store" -import { notifications } from "@budibase/bbui" export const API = createAPIClient({ attachHeaders: headers => { @@ -25,11 +24,6 @@ export const API = createAPIClient({ return } - // Show a notification for any errors - if (message) { - notifications.error(`Error fetching ${url}: ${message}`) - } - // Log all errors to console console.error(`HTTP ${status} on ${method}:${url}:\n\t${message}`) diff --git a/packages/builder/src/components/backend/DataTable/modals/CalculateModal.svelte b/packages/builder/src/components/backend/DataTable/modals/CalculateModal.svelte index 50d44eca88..81f54032f6 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CalculateModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CalculateModal.svelte @@ -38,9 +38,13 @@ }) function saveView() { - views.save(view) - notifications.success(`View ${view.name} saved.`) - analytics.captureEvent(Events.VIEW.ADDED_CALCULATE, { field: view.field }) + try { + views.save(view) + notifications.success(`View ${view.name} saved`) + analytics.captureEvent(Events.VIEW.ADDED_CALCULATE, { field: view.field }) + } catch (error) { + notifications.error("Error saving view") + } } diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 1fa5c6e073..385af3983c 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -122,7 +122,7 @@ }) dispatch("updatecolumns") } catch (err) { - notifications.error(err) + notifications.error("Error saving column") } } @@ -131,17 +131,21 @@ } function deleteColumn() { - field.name = deleteColName - if (field.name === $tables.selected.primaryDisplay) { - notifications.error("You cannot delete the display column") - } else { - tables.deleteField(field) - notifications.success(`Column ${field.name} deleted.`) - confirmDeleteDialog.hide() - hide() - deletion = false + try { + field.name = deleteColName + if (field.name === $tables.selected.primaryDisplay) { + notifications.error("You cannot delete the display column") + } else { + tables.deleteField(field) + notifications.success(`Column ${field.name} deleted.`) + confirmDeleteDialog.hide() + hide() + deletion = false + dispatch("updatecolumns") + } + } catch (error) { + notifications.error("Error deleting column") } - dispatch("updatecolumns") } function handleTypeChange(event) { diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte index 2f6ec51233..2ea0c1b63b 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte @@ -12,17 +12,21 @@ function saveView() { if (views.includes(name)) { - notifications.error(`View exists with name ${name}.`) + notifications.error(`View exists with name ${name}`) return } - viewsStore.save({ - name, - tableId: $tables.selected._id, - field, - }) - notifications.success(`View ${name} created`) - analytics.captureEvent(Events.VIEW.CREATED, { name }) - $goto(`../../view/${name}`) + try { + viewsStore.save({ + name, + tableId: $tables.selected._id, + field, + }) + notifications.success(`View ${name} created`) + analytics.captureEvent(Events.VIEW.CREATED, { name }) + $goto(`../../view/${name}`) + } catch (error) { + notifications.error("Error creating view") + } } diff --git a/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte b/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte index c413ee16ce..1209ea7e5f 100644 --- a/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte @@ -72,11 +72,15 @@ $: schema = viewTable && viewTable.schema ? viewTable.schema : {} function saveView() { - views.save(view) - notifications.success(`View ${view.name} saved.`) - analytics.captureEvent(Events.VIEW.ADDED_FILTER, { - filters: JSON.stringify(view.filters), - }) + try { + views.save(view) + notifications.success(`View ${view.name} saved`) + analytics.captureEvent(Events.VIEW.ADDED_FILTER, { + filters: JSON.stringify(view.filters), + }) + } catch (error) { + notifcations.error("Error saving view") + } } function removeFilter(idx) { diff --git a/packages/builder/src/components/backend/DataTable/modals/GroupByModal.svelte b/packages/builder/src/components/backend/DataTable/modals/GroupByModal.svelte index b0df0ef1da..59fc4a2c5d 100644 --- a/packages/builder/src/components/backend/DataTable/modals/GroupByModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/GroupByModal.svelte @@ -19,8 +19,12 @@ .map(([key]) => key) function saveView() { - views.save(view) - notifications.success(`View ${view.name} saved.`) + try { + views.save(view) + notifications.success(`View ${view.name} saved`) + } catch (error) { + notifications.error("Error saving view") + } } diff --git a/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte b/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte index 0998a5be2e..aa6dbc93e0 100644 --- a/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte @@ -14,15 +14,19 @@ export let permissions async function changePermission(level, role) { - await permissionsStore.save({ - level, - role, - resource: resourceId, - }) + try { + await permissionsStore.save({ + level, + role, + resource: resourceId, + }) - // Show updated permissions in UI: REMOVE - permissions = await permissionsStore.forResource(resourceId) - notifications.success("Updated permissions.") + // Show updated permissions in UI: REMOVE + permissions = await permissionsStore.forResource(resourceId) + notifications.success("Updated permissions") + } catch (error) { + notifications.error("Error updating permissions") + } } diff --git a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte index af345ddcdf..b40686afb5 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte @@ -10,6 +10,7 @@ import TableNavigator from "components/backend/TableNavigator/TableNavigator.svelte" import { customQueryIconText, customQueryIconColor } from "helpers/data/utils" import ICONS from "./icons" + import { notifications } from "@budibase/bbui" let openDataSources = [] $: enrichedDataSources = Array.isArray($datasources.list) @@ -64,8 +65,12 @@ } onMount(() => { - datasources.fetch() - queries.fetch() + try { + datasources.fetch() + queries.fetch() + } catch (error) { + notifications.error("Error fetching datasources and queries") + } }) const containsActiveEntity = datasource => { diff --git a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/CreateExternalTableModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/CreateExternalTableModal.svelte index 52402a0396..f6cd6af758 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/CreateExternalTableModal.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/CreateExternalTableModal.svelte @@ -1,5 +1,5 @@ diff --git a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte index 819fb32e45..1148695712 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte @@ -90,8 +90,8 @@ await datasources.updateSchema(datasource) notifications.success(`Datasource ${name} tables updated successfully.`) await tables.fetch() - } catch (err) { - notifications.error(`Error updating datasource schema: ${err}`) + } catch (error) { + notifications.error("Error updating datasource schema") } } diff --git a/packages/builder/src/components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte index d1a3d7c302..267b0495ec 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte @@ -62,9 +62,13 @@ externalDatasourceModal.hide() internalTableModal.show() } else if (integration.type === IntegrationTypes.REST) { - // skip modal for rest, create straight away - const resp = await createRestDatasource(integration) - $goto(`./datasource/${resp._id}`) + try { + // Skip modal for rest, create straight away + const resp = await createRestDatasource(integration) + $goto(`./datasource/${resp._id}`) + } catch (error) { + notifications.error("Error creating datasource") + } } else { externalDatasourceModal.show() } diff --git a/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte index da8c0515b7..06e00ff5d0 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte @@ -20,7 +20,7 @@ $goto(`./datasource/${resp._id}`) notifications.success(`Datasource updated successfully.`) } catch (err) { - notifications.error(`Error saving datasource: ${err}`) + notifications.error("Error saving datasource") } } diff --git a/packages/builder/src/components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte index cd6cef42d1..86cc25a0f3 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte @@ -79,8 +79,8 @@ }) return true - } catch (err) { - notifications.error(`Error importing: ${err}`) + } catch (error) { + notifications.error("Error importing queries") return false } } diff --git a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte index 1354c31b87..ae0023f682 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte @@ -12,24 +12,28 @@ let updateDatasourceDialog async function deleteDatasource() { - let wasSelectedSource = $datasources.selected - if (!wasSelectedSource && $queries.selected) { - const queryId = $queries.selected - wasSelectedSource = $datasources.list.find(ds => - queryId.includes(ds._id) - )?._id - } - const wasSelectedTable = $tables.selected - await datasources.delete(datasource) - notifications.success("Datasource deleted") - // navigate to first index page if the source you are deleting is selected - const entities = Object.values(datasource?.entities || {}) - if ( - wasSelectedSource === datasource._id || - (entities && - entities.find(entity => entity._id === wasSelectedTable?._id)) - ) { - $goto("./datasource") + try { + let wasSelectedSource = $datasources.selected + if (!wasSelectedSource && $queries.selected) { + const queryId = $queries.selected + wasSelectedSource = $datasources.list.find(ds => + queryId.includes(ds._id) + )?._id + } + const wasSelectedTable = $tables.selected + await datasources.delete(datasource) + notifications.success("Datasource deleted") + // Navigate to first index page if the source you are deleting is selected + const entities = Object.values(datasource?.entities || {}) + if ( + wasSelectedSource === datasource._id || + (entities && + entities.find(entity => entity._id === wasSelectedTable?._id)) + ) { + $goto("./datasource") + } + } catch (error) { + notifications.error("Error deleting datasource") } } diff --git a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditQueryPopover.svelte b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditQueryPopover.svelte index b15746735b..e18deab2dd 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditQueryPopover.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditQueryPopover.svelte @@ -10,26 +10,30 @@ let confirmDeleteDialog async function deleteQuery() { - const wasSelectedQuery = $queries.selected - // need to calculate this before the query is deleted - const navigateToDatasource = wasSelectedQuery === query._id + try { + const wasSelectedQuery = $queries.selected + // need to calculate this before the query is deleted + const navigateToDatasource = wasSelectedQuery === query._id - await queries.delete(query) - await datasources.fetch() + await queries.delete(query) + await datasources.fetch() - if (navigateToDatasource) { - await datasources.select(query.datasourceId) - $goto(`./datasource/${query.datasourceId}`) + if (navigateToDatasource) { + await datasources.select(query.datasourceId) + $goto(`./datasource/${query.datasourceId}`) + } + notifications.success("Query deleted") + } catch (error) { + notifications.error("Error deleting query") } - notifications.success("Query deleted") } async function duplicateQuery() { try { const newQuery = await queries.duplicate(query) onClickQuery(newQuery) - } catch (e) { - notifications.error(e.message) + } catch (error) { + notifications.error("Error duplicating query") } } diff --git a/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte b/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte index 0211a37557..b4035ba6e4 100644 --- a/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte +++ b/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte @@ -49,8 +49,8 @@ if (wasSelectedTable && wasSelectedTable._id === table._id) { $goto("./table") } - } catch (err) { - notifications.error(err) + } catch (error) { + notifications.error("Error deleting table") } } diff --git a/packages/builder/src/components/backend/TableNavigator/popovers/EditViewPopover.svelte b/packages/builder/src/components/backend/TableNavigator/popovers/EditViewPopover.svelte index 8384fc1d90..0ab5d4326f 100644 --- a/packages/builder/src/components/backend/TableNavigator/popovers/EditViewPopover.svelte +++ b/packages/builder/src/components/backend/TableNavigator/popovers/EditViewPopover.svelte @@ -27,11 +27,15 @@ } async function deleteView() { - const name = view.name - const id = view.tableId - await views.delete(name) - notifications.success("View deleted") - $goto(`./table/${id}`) + try { + const name = view.name + const id = view.tableId + await views.delete(name) + notifications.success("View deleted") + $goto(`./table/${id}`) + } catch (error) { + notifications.error("Error deleting view") + } } diff --git a/packages/builder/src/components/integration/AccessLevelSelect.svelte b/packages/builder/src/components/integration/AccessLevelSelect.svelte index 88814ed648..24c1e9b069 100644 --- a/packages/builder/src/components/integration/AccessLevelSelect.svelte +++ b/packages/builder/src/components/integration/AccessLevelSelect.svelte @@ -1,5 +1,5 @@ diff --git a/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte index 808c3a49ec..85ae8a11f5 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte @@ -112,14 +112,13 @@ const { _id } = await queries.save(toSave.datasourceId, toSave) saveId = _id query = getSelectedQuery() - notifications.success(`Request saved successfully.`) - + notifications.success(`Request saved successfully`) if (dynamicVariables) { datasource.config.dynamicVariables = rebuildVariables(saveId) datasource = await datasources.save(datasource) } } catch (err) { - notifications.error(`Error saving query. ${err.message}`) + notifications.error(`Error saving query`) } } @@ -127,14 +126,14 @@ try { response = await queries.preview(buildQuery(query)) if (response.rows.length === 0) { - notifications.info("Request did not return any data.") + notifications.info("Request did not return any data") } else { response.info = response.info || { code: 200 } schema = response.schema - notifications.success("Request sent successfully.") + notifications.success("Request sent successfully") } - } catch (err) { - notifications.error(err) + } catch (error) { + notifications.error("Error running query") } } @@ -226,10 +225,24 @@ ) } + const updateFlag = async (flag, value) => { + try { + await flags.updateFlag(flag, value) + } catch (error) { + notifications.error("Error updating flag") + } + } + onMount(async () => { query = getSelectedQuery() - // clear any unsaved changes to the datasource - await datasources.init() + + try { + // Clear any unsaved changes to the datasource + await datasources.init() + } catch (error) { + notifications.error("Error getting datasources") + } + datasource = $datasources.list.find(ds => ds._id === query?.datasourceId) const datasourceUrl = datasource?.config.url const qs = query?.fields.queryString @@ -393,8 +406,7 @@ window.open( "https://docs.budibase.com/building-apps/data/transformers" )} - on:change={() => - flags.updateFlag("queryTransformerBanner", true)} + on:change={() => updateFlag("queryTransformerBanner", true)} > Add a JavaScript function to transform the query result. diff --git a/packages/builder/src/stores/backend/datasources.js b/packages/builder/src/stores/backend/datasources.js index 7810c3a950..2423394c6a 100644 --- a/packages/builder/src/stores/backend/datasources.js +++ b/packages/builder/src/stores/backend/datasources.js @@ -1,6 +1,6 @@ import { writable, get } from "svelte/store" import { queries, tables, views } from "./" -import api from "../../builderStore/api" +import { API } from "api" export const INITIAL_DATASOURCE_VALUES = { list: [], @@ -13,23 +13,20 @@ export function createDatasourcesStore() { const { subscribe, update, set } = store async function updateDatasource(response) { - if (response.status !== 200) { - throw new Error(await response.text()) - } - - const { datasource, error } = await response.json() + const { datasource, error } = response update(state => { const currentIdx = state.list.findIndex(ds => ds._id === datasource._id) - const sources = state.list - if (currentIdx >= 0) { sources.splice(currentIdx, 1, datasource) } else { sources.push(datasource) } - - return { list: sources, selected: datasource._id, schemaError: error } + return { + list: sources, + selected: datasource._id, + schemaError: error, + } }) return datasource } @@ -38,25 +35,25 @@ export function createDatasourcesStore() { subscribe, update, init: async () => { - const response = await api.get(`/api/datasources`) - const json = await response.json() - set({ list: json, selected: null }) + const datasources = await API.getDatasources() + set({ + list: datasources, + selected: null, + }) }, fetch: async () => { - const response = await api.get(`/api/datasources`) - const json = await response.json() + const datasources = await API.getDatasources() // Clear selected if it no longer exists, otherwise keep it const selected = get(store).selected let nextSelected = null - if (selected && json.find(source => source._id === selected)) { + if (selected && datasources.find(source => source._id === selected)) { nextSelected = selected } - update(state => ({ ...state, list: json, selected: nextSelected })) - return json + update(state => ({ ...state, list: datasources, selected: nextSelected })) }, - select: async datasourceId => { + select: datasourceId => { update(state => ({ ...state, selected: datasourceId })) queries.unselect() tables.unselect() @@ -66,37 +63,33 @@ export function createDatasourcesStore() { update(state => ({ ...state, selected: null })) }, updateSchema: async datasource => { - let url = `/api/datasources/${datasource._id}/schema` - - const response = await api.post(url) - return updateDatasource(response) + const response = await API.buildDatasourceSchema(datasource?._id) + return await updateDatasource(response) }, save: async (body, fetchSchema = false) => { let response if (body._id) { - response = await api.put(`/api/datasources/${body._id}`, body) + response = await API.updateDatasource(body) } else { - response = await api.post("/api/datasources", { + response = await API.createDatasource({ datasource: body, fetchSchema, }) } - return updateDatasource(response) }, delete: async datasource => { - const response = await api.delete( - `/api/datasources/${datasource._id}/${datasource._rev}` - ) + await API.deleteDatasource({ + datasourceId: datasource?._id, + datasourceRev: datasource?._rev, + }) update(state => { const sources = state.list.filter( existing => existing._id !== datasource._id ) return { list: sources, selected: null } }) - await queries.fetch() - return response }, removeSchemaError: () => { update(state => { diff --git a/packages/builder/src/stores/backend/flags.js b/packages/builder/src/stores/backend/flags.js index 7e5adcd00f..449d010640 100644 --- a/packages/builder/src/stores/backend/flags.js +++ b/packages/builder/src/stores/backend/flags.js @@ -1,37 +1,27 @@ import { writable } from "svelte/store" -import api from "builderStore/api" +import { API } from "api" export function createFlagsStore() { const { subscribe, set } = writable({}) - return { - subscribe, + const actions = { fetch: async () => { - const { doc, response } = await getFlags() - set(doc) - return response + const flags = await API.getFlags() + set(flags) }, updateFlag: async (flag, value) => { - const response = await api.post("/api/users/flags", { + await API.updateFlag({ flag, value, }) - if (response.status === 200) { - const { doc } = await getFlags() - set(doc) - } - return response + await actions.fetch() }, } -} -async function getFlags() { - const response = await api.get("/api/users/flags") - let doc = {} - if (response.status === 200) { - doc = await response.json() + return { + subscribe, + ...actions, } - return { doc, response } } export const flags = createFlagsStore() diff --git a/packages/builder/src/stores/backend/integrations.js b/packages/builder/src/stores/backend/integrations.js index c19b09f1fb..717b656c72 100644 --- a/packages/builder/src/stores/backend/integrations.js +++ b/packages/builder/src/stores/backend/integrations.js @@ -7,12 +7,8 @@ const createIntegrationsStore = () => { return { ...store, init: async () => { - try { - const integrations = await API.getIntegrations() - store.set(integrations) - } catch (error) { - store.set(null) - } + const integrations = await API.getIntegrations() + store.set(integrations) }, } } diff --git a/packages/builder/src/stores/backend/permissions.js b/packages/builder/src/stores/backend/permissions.js index 29159494ed..aaab406bc9 100644 --- a/packages/builder/src/stores/backend/permissions.js +++ b/packages/builder/src/stores/backend/permissions.js @@ -1,5 +1,5 @@ import { writable } from "svelte/store" -import api from "builderStore/api" +import { API } from "api" export function createPermissionStore() { const { subscribe } = writable([]) @@ -7,14 +7,14 @@ export function createPermissionStore() { return { subscribe, save: async ({ level, role, resource }) => { - const response = await api.post( - `/api/permission/${role}/${resource}/${level}` - ) - return await response.json() + return await API.updatePermissionForResource({ + resourceId: resource, + roleId: role, + level, + }) }, forResource: async resourceId => { - const response = await api.get(`/api/permission/${resourceId}`) - return await response.json() + return await API.getPermissionForResource(resourceId) }, } } diff --git a/packages/builder/src/stores/backend/queries.js b/packages/builder/src/stores/backend/queries.js index 2018933ffc..6e30cb21f8 100644 --- a/packages/builder/src/stores/backend/queries.js +++ b/packages/builder/src/stores/backend/queries.js @@ -1,6 +1,6 @@ import { writable, get } from "svelte/store" import { datasources, integrations, tables, views } from "./" -import api from "builderStore/api" +import { API } from "api" import { duplicateName } from "../../helpers/duplicate" const sortQueries = queryList => { @@ -15,23 +15,26 @@ export function createQueriesStore() { const actions = { init: async () => { - const response = await api.get(`/api/queries`) - const json = await response.json() - set({ list: json, selected: null }) + const queries = await API.getQueries() + set({ + list: queries, + selected: null, + }) }, fetch: async () => { - const response = await api.get(`/api/queries`) - const json = await response.json() - sortQueries(json) - update(state => ({ ...state, list: json })) - return json + const queries = await API.getQueries() + sortQueries(queries) + update(state => ({ + ...state, + list: queries, + })) }, save: async (datasourceId, query) => { const _integrations = get(integrations) const dataSource = get(datasources).list.filter( ds => ds._id === datasourceId ) - // check if readable attribute is found + // Check if readable attribute is found if (dataSource.length !== 0) { const integration = _integrations[dataSource[0].source] const readable = integration.query[query.queryVerb].readable @@ -40,34 +43,28 @@ export function createQueriesStore() { } } query.datasourceId = datasourceId - const response = await api.post(`/api/queries`, query) - if (response.status !== 200) { - throw new Error("Failed saving query.") - } - const json = await response.json() + const savedQuery = await API.saveQuery(query) update(state => { - const currentIdx = state.list.findIndex(query => query._id === json._id) - + const idx = state.list.findIndex(query => query._id === savedQuery._id) const queries = state.list - - if (currentIdx >= 0) { - queries.splice(currentIdx, 1, json) + if (idx >= 0) { + queries.splice(idx, 1, savedQuery) } else { - queries.push(json) + queries.push(savedQuery) } sortQueries(queries) - return { list: queries, selected: json._id } + return { + list: queries, + selected: savedQuery._id, + } }) - return json + return savedQuery }, - import: async body => { - const response = await api.post(`/api/queries/import`, body) - - if (response.status !== 200) { - throw new Error(response.message) - } - - return response.json() + import: async (data, datasourceId) => { + return await API.importQueries({ + datasourceId, + data, + }) }, select: query => { update(state => ({ ...state, selected: query._id })) @@ -79,48 +76,37 @@ export function createQueriesStore() { update(state => ({ ...state, selected: null })) }, preview: async query => { - const response = await api.post("/api/queries/preview", { - fields: query.fields, - queryVerb: query.queryVerb, - transformer: query.transformer, - parameters: query.parameters.reduce( - (acc, next) => ({ - ...acc, - [next.name]: next.default, - }), - {} - ), - datasourceId: query.datasourceId, - queryId: query._id || undefined, + const parameters = query.parameters.reduce( + (acc, next) => ({ + ...acc, + [next.name]: next.default, + }), + {} + ) + const result = await API.previewQuery({ + ...query, + parameters, }) - - if (response.status !== 200) { - const error = await response.text() - throw `Query error: ${error}` - } - - const json = await response.json() // Assume all the fields are strings and create a basic schema from the // unique fields returned by the server const schema = {} - for (let field of json.schemaFields) { + for (let field of result.schemaFields) { schema[field] = "string" } - return { ...json, schema, rows: json.rows || [] } + return { ...result, schema, rows: result.rows || [] } }, delete: async query => { - const response = await api.delete( - `/api/queries/${query._id}/${query._rev}` - ) + await API.deleteQuery({ + queryId: query?._id, + queryRev: query?._rev, + }) update(state => { state.list = state.list.filter(existing => existing._id !== query._id) if (state.selected === query._id) { state.selected = null } - return state }) - return response }, duplicate: async query => { let list = get(store).list diff --git a/packages/builder/src/stores/backend/roles.js b/packages/builder/src/stores/backend/roles.js index 1a1a9c04c5..0c3cdbce5a 100644 --- a/packages/builder/src/stores/backend/roles.js +++ b/packages/builder/src/stores/backend/roles.js @@ -1,30 +1,32 @@ import { writable } from "svelte/store" -import api from "builderStore/api" +import { API } from "api" export function createRolesStore() { const { subscribe, update, set } = writable([]) - return { - subscribe, + const actions = { fetch: async () => { - set(await getRoles()) + const roles = await API.getRoles() + set(roles) }, delete: async role => { - const response = await api.delete(`/api/roles/${role._id}/${role._rev}`) + await API.deleteRole({ + roleId: role?._id, + roleRev: role?._rev, + }) update(state => state.filter(existing => existing._id !== role._id)) - return response }, save: async role => { - const response = await api.post("/api/roles", role) - set(await getRoles()) - return response + const savedRole = await API.saveRole(role) + await actions.fetch() + return savedRole }, } + + return { + subscribe, + ...actions, + } } -async function getRoles() { - const response = await api.get("/api/roles") - return await response.json() -} - export const roles = createRolesStore() diff --git a/packages/builder/src/stores/backend/tables.js b/packages/builder/src/stores/backend/tables.js index 02db48c549..f6d20037cb 100644 --- a/packages/builder/src/stores/backend/tables.js +++ b/packages/builder/src/stores/backend/tables.js @@ -1,7 +1,7 @@ import { get, writable } from "svelte/store" import { datasources, queries, views } from "./" import { cloneDeep } from "lodash/fp" -import api from "builderStore/api" +import { API } from "api" import { SWITCHABLE_TYPES } from "../../constants/backend" export function createTablesStore() { @@ -9,10 +9,11 @@ export function createTablesStore() { const { subscribe, update, set } = store async function fetch() { - const tablesResponse = await api.get(`/api/tables`) - const tables = await tablesResponse.json() - update(state => ({ ...state, list: tables })) - return tables + const tables = await API.getTables() + update(state => ({ + ...state, + list: tables, + })) } async function select(table) { @@ -38,16 +39,16 @@ export function createTablesStore() { const oldTable = get(store).list.filter(t => t._id === table._id)[0] const fieldNames = [] - // update any renamed schema keys to reflect their names + // Update any renamed schema keys to reflect their names for (let key of Object.keys(updatedTable.schema)) { - // if field name has been seen before remove it + // If field name has been seen before remove it if (fieldNames.indexOf(key.toLowerCase()) !== -1) { delete updatedTable.schema[key] continue } const field = updatedTable.schema[key] const oldField = oldTable?.schema[key] - // if the type has changed then revert back to the old field + // If the type has changed then revert back to the old field if ( oldField != null && oldField?.type !== field.type && @@ -55,21 +56,17 @@ export function createTablesStore() { ) { updatedTable.schema[key] = oldField } - // field has been renamed + // Field has been renamed if (field.name && field.name !== key) { updatedTable.schema[field.name] = field updatedTable._rename = { old: key, updated: field.name } delete updatedTable.schema[key] } - // finally record this field has been used + // Finally record this field has been used fieldNames.push(key.toLowerCase()) } - const response = await api.post(`/api/tables`, updatedTable) - if (response.status !== 200) { - throw (await response.json()).message - } - const savedTable = await response.json() + const savedTable = await API.saveTable(updatedTable) await fetch() if (table.type === "external") { await datasources.fetch() @@ -91,21 +88,18 @@ export function createTablesStore() { }, save, init: async () => { - const response = await api.get("/api/tables") - const json = await response.json() + const tables = await API.getTables() set({ - list: json, + list: tables, selected: {}, draft: {}, }) }, delete: async table => { - const response = await api.delete( - `/api/tables/${table._id}/${table._rev}` - ) - if (response.status !== 200) { - throw (await response.json()).message - } + await API.deleteTable({ + tableId: table?._id, + tableRev: table?._rev, + }) update(state => ({ ...state, list: state.list.filter(existing => existing._id !== table._id), @@ -156,12 +150,16 @@ export function createTablesStore() { await promise } }, - deleteField: field => { + deleteField: async field => { + let promise update(state => { delete state.draft.schema[field.name] - save(state.draft) + promise = save(state.draft) return state }) + if (promise) { + await promise + } }, } } diff --git a/packages/builder/src/stores/backend/views.js b/packages/builder/src/stores/backend/views.js index 14c7bf92a4..849a66f671 100644 --- a/packages/builder/src/stores/backend/views.js +++ b/packages/builder/src/stores/backend/views.js @@ -1,6 +1,6 @@ import { writable, get } from "svelte/store" import { tables, datasources, queries } from "./" -import api from "builderStore/api" +import { API } from "api" export function createViewsStore() { const { subscribe, update } = writable({ @@ -11,7 +11,7 @@ export function createViewsStore() { return { subscribe, update, - select: async view => { + select: view => { update(state => ({ ...state, selected: view, @@ -27,16 +27,14 @@ export function createViewsStore() { })) }, delete: async view => { - await api.delete(`/api/views/${view}`) + await API.deleteView(view) await tables.fetch() }, save: async view => { - const response = await api.post(`/api/views`, view) - const json = await response.json() - + const savedView = await API.saveView(view) const viewMeta = { name: view.name, - ...json, + ...savedView, } const viewTable = get(tables).list.find( diff --git a/packages/builder/src/stores/portal/email.js b/packages/builder/src/stores/portal/email.js index a015480141..5bef63701f 100644 --- a/packages/builder/src/stores/portal/email.js +++ b/packages/builder/src/stores/portal/email.js @@ -1,5 +1,6 @@ import { writable } from "svelte/store" -import api from "builderStore/api" +import { API } from "api" +import { notifications } from "@budibase/bbui" export function createEmailStore() { const store = writable({}) @@ -8,34 +9,35 @@ export function createEmailStore() { subscribe: store.subscribe, templates: { fetch: async () => { - // fetch the email template definitions - const response = await api.get(`/api/global/template/definitions`) - const definitions = await response.json() - - // fetch the email templates themselves - const templatesResponse = await api.get(`/api/global/template/email`) - const templates = await templatesResponse.json() - - store.set({ - definitions, - templates, - }) + try { + // fetch the email template definitions and templates + const definitions = await API.getEmailTemplateDefinitions() + const templates = await API.getEmailTemplates() + store.set({ + definitions, + templates, + }) + } catch (error) { + notifications.error("Error fetching email templates") + store.set({}) + } }, save: async template => { - // Save your template config - const response = await api.post(`/api/global/template`, template) - const json = await response.json() - if (response.status !== 200) throw new Error(json.message) - template._rev = json._rev - template._id = json._id - - store.update(state => { - const currentIdx = state.templates.findIndex( - template => template.purpose === json.purpose - ) - state.templates.splice(currentIdx, 1, template) - return state - }) + try { + // Save your template config + const savedTemplate = await API.saveEmailTemplate(template) + template._rev = savedTemplate._rev + template._id = savedTemplate._id + store.update(state => { + const currentIdx = state.templates.findIndex( + template => template.purpose === savedTemplate.purpose + ) + state.templates.splice(currentIdx, 1, template) + return state + }) + } catch (error) { + notifications.error("Error saving email template") + } }, }, } diff --git a/packages/frontend-core/src/api/datasources.js b/packages/frontend-core/src/api/datasources.js new file mode 100644 index 0000000000..ff72fbf25b --- /dev/null +++ b/packages/frontend-core/src/api/datasources.js @@ -0,0 +1,57 @@ +export const buildDatasourceEndpoints = API => ({ + /** + * Gets a list of datasources. + */ + getDatasources: async () => { + return await API.get({ + url: "/api/datasources", + }) + }, + + /** + * Prompts the server to build the schema for a datasource. + * @param datasourceId the datasource ID to build the schema for + */ + buildDatasourceSchema: async datasourceId => { + return await API.post({ + url: `/api/datasources/${datasourceId}/schema`, + }) + }, + + /** + * Creates a datasource + * @param datasource the datasource to create + * @param fetchSchema whether to fetch the schema or not + */ + createDatasource: async ({ datasource, fetchSchema }) => { + return await API.post({ + url: "/api/datasources", + body: { + datasource, + fetchSchema, + }, + }) + }, + + /** + * Updates a datasource + * @param datasource the datasource to update + */ + updateDatasource: async datasource => { + return await API.put({ + url: `/api/datasources/${datasource._id}`, + body: datasource, + }) + }, + + /** + * Deletes a datasource. + * @param datasourceId the ID of the ddtasource to delete + * @param datasourceRev the rev of the datasource to delete + */ + deleteDatasource: async ({ datasourceId, datasourceRev }) => { + return await API.delete({ + url: `/api/datasources/${datasourceId}/${datasourceRev}`, + }) + }, +}) diff --git a/packages/frontend-core/src/api/flags.js b/packages/frontend-core/src/api/flags.js new file mode 100644 index 0000000000..bb545e83b9 --- /dev/null +++ b/packages/frontend-core/src/api/flags.js @@ -0,0 +1,25 @@ +export const buildFlagEndpoints = API => ({ + /** + * Gets the current user flags object. + */ + getFlags: async () => { + return await API.get({ + url: "/api/users/flags", + }) + }, + + /** + * Updates a flag for the current user. + * @param flag the flag to update + * @param value the value to set the flag to + */ + updateFlag: async ({ flag, value }) => { + return await API.post({ + url: "/api/users/flags", + body: { + flag, + value, + }, + }) + }, +}) diff --git a/packages/frontend-core/src/api/index.js b/packages/frontend-core/src/api/index.js index 91a400a389..9511a946b4 100644 --- a/packages/frontend-core/src/api/index.js +++ b/packages/frontend-core/src/api/index.js @@ -4,13 +4,18 @@ import { buildAppEndpoints } from "./app" import { buildAttachmentEndpoints } from "./attachments" import { buildAuthEndpoints } from "./auth" import { buildAutomationEndpoints } from "./automations" +import { buildDatasourceEndpoints } from "./datasources" +import { buildFlagEndpoints } from "./flags" import { buildHostingEndpoints } from "./hosting" +import { buildPermissionsEndpoints } from "./permissions" import { buildQueryEndpoints } from "./queries" import { buildRelationshipEndpoints } from "./relationships" +import { buildRoleEndpoints } from "./roles" import { buildRouteEndpoints } from "./routes" import { buildRowEndpoints } from "./rows" import { buildScreenEndpoints } from "./screens" import { buildTableEndpoints } from "./tables" +import { buildTemplateEndpoints } from "./templates" import { buildViewEndpoints } from "./views" const defaultAPIClientConfig = { @@ -184,13 +189,18 @@ export const createAPIClient = config => { ...buildAttachmentEndpoints(API), ...buildAuthEndpoints(API), ...buildAutomationEndpoints(API), + ...buildDatasourceEndpoints(API), + ...buildFlagEndpoints(API), ...buildHostingEndpoints(API), + ...buildPermissionsEndpoints(API), ...buildQueryEndpoints(API), ...buildRelationshipEndpoints(API), + ...buildRoleEndpoints(API), ...buildRouteEndpoints(API), ...buildRowEndpoints(API), ...buildScreenEndpoints(API), ...buildTableEndpoints(API), + ...buildTemplateEndpoints(API), ...buildViewEndpoints(API), } diff --git a/packages/frontend-core/src/api/permissions.js b/packages/frontend-core/src/api/permissions.js new file mode 100644 index 0000000000..5407cb3ce5 --- /dev/null +++ b/packages/frontend-core/src/api/permissions.js @@ -0,0 +1,24 @@ +export const buildPermissionsEndpoints = API => ({ + /** + * Gets the permission required to access a specific resource + * @param resourceId the resource ID to check + */ + getPermissionForResource: async resourceId => { + return await API.get({ + url: `/api/permission/${resourceId}`, + }) + }, + + /** + * Updates 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 assign the role for this resource + * @return {Promise<*>} + */ + updatePermissionForResource: async ({ resourceId, roleId, level }) => { + return await API.post({ + url: `/api/permission/${roleId}/${resourceId}/${level}`, + }) + }, +}) diff --git a/packages/frontend-core/src/api/queries.js b/packages/frontend-core/src/api/queries.js index 6c3ce900f9..f18ec7c4ec 100644 --- a/packages/frontend-core/src/api/queries.js +++ b/packages/frontend-core/src/api/queries.js @@ -1,6 +1,9 @@ export const buildQueryEndpoints = API => ({ /** * Executes a query against an external data connector. + * @param queryId the ID of the query to execute + * @param pagination pagination info for the query + * @param parameters parameters for the query */ executeQuery: async ({ queryId, pagination, parameters }) => { return await API.post({ @@ -14,6 +17,7 @@ export const buildQueryEndpoints = API => ({ /** * Fetches the definition of an external query. + * @param queryId the ID of thr query to fetch the definition of */ fetchQueryDefinition: async queryId => { return await API.get({ @@ -21,4 +25,61 @@ export const buildQueryEndpoints = API => ({ cache: true, }) }, + + /** + * Gets a list of queries + */ + getQueries: async () => { + return await API.get({ + url: "/api/queries", + }) + }, + + /** + * Saves a query. + * @param query the query to save + */ + saveQuery: async query => { + return await API.post({ + url: "/api/queries", + body: query, + }) + }, + + /** + * Deletes a query + * @param queryId the ID of the query to delete + * @param queryRev the rev of the query to delete + */ + deleteQuery: async ({ queryId, queryRev }) => { + return await API.delete({ + url: `/api/queries/${queryId}/${queryRev}`, + }) + }, + + /** + * Imports a set of queries into a certain datasource + * @param datasourceId the datasource ID to import queries into + * @param data the data string of the content to import + */ + importQueries: async ({ datasourceId, data }) => { + return await API.post({ + url: "/api/queries/import", + body: { + datasourceId, + data, + }, + }) + }, + + /** + * Runs a query with test parameters to see the result. + * @param query the query to run + */ + previewQuery: async query => { + return await API.post({ + url: "/api/queries/preview", + body: query, + }) + }, }) diff --git a/packages/frontend-core/src/api/roles.js b/packages/frontend-core/src/api/roles.js new file mode 100644 index 0000000000..15c27091c4 --- /dev/null +++ b/packages/frontend-core/src/api/roles.js @@ -0,0 +1,32 @@ +export const buildRoleEndpoints = API => ({ + /** + * Deletes a role. + * @param roleId the ID of the role to delete + * @param roleRev the rev of the role to delete + */ + deleteRole: async ({ roleId, roleRev }) => { + return await API.delete({ + url: `/api/roles/${roleId}/${roleRev}`, + }) + }, + + /** + * Saves a role. + * @param role the role to save + */ + saveRole: async role => { + return await API.post({ + url: "/api/roles", + body: role, + }) + }, + + /** + * Gets a list of roles. + */ + getRoles: async () => { + return await API.get({ + url: "/api/roles", + }) + }, +}) diff --git a/packages/frontend-core/src/api/tables.js b/packages/frontend-core/src/api/tables.js index 0785b2eed3..1f8b450a2a 100644 --- a/packages/frontend-core/src/api/tables.js +++ b/packages/frontend-core/src/api/tables.js @@ -79,4 +79,35 @@ export const buildTableEndpoints = API => ({ }, }) }, + + /** + * Gets a list o tables. + */ + getTables: async () => { + return await API.get({ + url: "/api/tables", + }) + }, + + /** + * Saves a table. + * @param table the table to save + */ + saveTable: async table => { + return await API.post({ + url: "/api/tables", + body: table, + }) + }, + + /** + * Deletes a table. + * @param tableId the ID of the table to delete + * @param tableRev the rev of the table to delete + */ + deleteTable: async ({ tableId, tableRev }) => { + return await API.delete({ + url: `/api/tables/${tableId}/${tableRev}`, + }) + }, }) diff --git a/packages/frontend-core/src/api/templates.js b/packages/frontend-core/src/api/templates.js new file mode 100644 index 0000000000..7cd1d78d7b --- /dev/null +++ b/packages/frontend-core/src/api/templates.js @@ -0,0 +1,28 @@ +export const buildTemplateEndpoints = API => ({ + /** + * Gets the list of email template definitions. + */ + getEmailTemplateDefinitions: async () => { + return await API.get({ url: "/api/global/template/definitions" }) + }, + + /** + * Gets the list of email templates. + */ + getEmailTemplates: async () => { + return await API.get({ url: "/api/global/template/email" }) + }, + + /** + * Saves an email template. + * @param template the template to save + */ + saveEmailTemplate: async template => { + return await API.post({ + url: "/api/global/template", + body: { + template, + }, + }) + }, +}) diff --git a/packages/frontend-core/src/api/views.js b/packages/frontend-core/src/api/views.js index 661268d957..237a66bc13 100644 --- a/packages/frontend-core/src/api/views.js +++ b/packages/frontend-core/src/api/views.js @@ -1,6 +1,10 @@ export const buildViewEndpoints = API => ({ /** - * Fetches all rows in a view. + * Fetches all rows in a view + * @param name the name of the view + * @param field the field to perform the calculation on + * @param groupBy the field to group by + * @param calculation the calculation to perform */ fetchViewData: async ({ name, field, groupBy, calculation }) => { const params = new URLSearchParams() @@ -9,7 +13,7 @@ export const buildViewEndpoints = API => ({ params.set("calculation", calculation) } if (groupBy) { - params.set("group", groupBy ? "true" : "false") + params.set("group", groupBy) } const QUERY_VIEW_URL = field ? `/api/views/${name}?${params}` @@ -31,4 +35,25 @@ export const buildViewEndpoints = API => ({ }, }) }, + + /** + * Saves a view. + * @param view the view to save + */ + saveView: async view => { + return await API.post({ + url: "/api/views", + body: view, + }) + }, + + /** + * Deletes a view. + * @param viewName the name of the view to delete + */ + deleteView: async viewName => { + return await API.delete({ + url: `/api/views/${viewName}`, + }) + }, })