allow execution of external connector queries from button clicks

This commit is contained in:
Martin McKeaveney 2021-01-04 18:57:16 +00:00
parent d27a264c96
commit 755fa0ac4a
18 changed files with 147 additions and 97 deletions

View File

@ -1,7 +1,6 @@
import { writable, get } from "svelte/store" import { writable, get } from "svelte/store"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import api from "../api" import api from "../api"
import { backendUiStore } from ".."
const INITIAL_BACKEND_UI_STATE = { const INITIAL_BACKEND_UI_STATE = {
tables: [], tables: [],
@ -63,6 +62,7 @@ export const getBackendUiStore = () => {
select: async datasourceId => { select: async datasourceId => {
store.update(state => { store.update(state => {
state.selectedDatasourceId = datasourceId state.selectedDatasourceId = datasourceId
state.selectedQueryId = null
return state return state
}) })
}, },
@ -123,6 +123,7 @@ export const getBackendUiStore = () => {
queries: { queries: {
select: queryId => select: queryId =>
store.update(state => { store.update(state => {
state.selectedDatasourceId = null
state.selectedQueryId = queryId state.selectedQueryId = queryId
return state return state
}), }),

View File

@ -6,21 +6,13 @@
import Table from "./Table.svelte" import Table from "./Table.svelte"
import CreateQueryButton from "components/backend/DataTable/buttons/CreateQueryButton.svelte" import CreateQueryButton from "components/backend/DataTable/buttons/CreateQueryButton.svelte"
export let datasourceId export let datasource
export let query = {} export let query = {}
let data = [] let data = []
let loading = false let loading = false
let error = false let error = false
$: datasourceId = $params.selectedDatasource
// TODO: refactor
// $: query = $backendUiStore.datasources.find(
// ds => ds._id === $params.selectedDatasource
// ).queries[$params.query]
$: title = query.name
$: schema = query.schema
async function fetchData() { async function fetchData() {
try { try {
loading = true loading = true
@ -46,8 +38,8 @@
{#if error} {#if error}
<div class="errors">{error}</div> <div class="errors">{error}</div>
{/if} {/if}
<Table {title} {schema} {data} {loading}> <Table title={query.name} schema={query.schema} {data} {loading}>
<CreateQueryButton {query} /> <CreateQueryButton {query} {datasource} />
</Table> </Table>
<style> <style>

View File

@ -236,6 +236,7 @@
} }
:global(.ag-filter) { :global(.ag-filter) {
background: var(--background);
padding: var(--spacing-s); padding: var(--spacing-s);
outline: none; outline: none;
box-sizing: border-box; box-sizing: border-box;

View File

@ -16,9 +16,9 @@
import CreateEditQuery from "components/backend/DataTable/modals/CreateEditQuery.svelte" import CreateEditQuery from "components/backend/DataTable/modals/CreateEditQuery.svelte"
export let datasource export let datasource
export let query = {}
let modal let modal
let query = {}
let fields = [] let fields = []
async function saveQuery() { async function saveQuery() {
@ -30,14 +30,12 @@
notifier.danger(`Error creating query. ${err.message}`) notifier.danger(`Error creating query. ${err.message}`)
} }
} }
$: console.log(query)
</script> </script>
<div> <div>
<Button text small on:click={modal.show}> <Button text small on:click={modal.show}>
<Icon name="filter" /> <Icon name="filter" />
{query ? 'Edit' : 'Create'} Query {$backendUiStore.selectedQueryId ? 'Edit' : 'Create'} Query
</Button> </Button>
</div> </div>
<Modal bind:this={modal}> <Modal bind:this={modal}>
@ -45,7 +43,7 @@
confirmText="Save" confirmText="Save"
cancelText="Cancel" cancelText="Cancel"
onConfirm={saveQuery} onConfirm={saveQuery}
title="Create New Query"> title={query ? 'Edit Query' : 'Create New Query'}>
<CreateEditQuery {datasource} bind:query /> <CreateEditQuery {datasource} bind:query />
</ModalContent> </ModalContent>
</Modal> </Modal>

View File

@ -31,6 +31,8 @@
export let query export let query
export let fields = [] export let fields = []
console.log(query)
let config = {} let config = {}
let queryType let queryType
let previewTab = "PREVIEW" let previewTab = "PREVIEW"

View File

@ -8,8 +8,6 @@
import { Modal, Switcher } from "@budibase/bbui" import { Modal, Switcher } from "@budibase/bbui"
import NavItem from "components/common/NavItem.svelte" import NavItem from "components/common/NavItem.svelte"
let modal
$: selectedView = $: selectedView =
$backendUiStore.selectedView && $backendUiStore.selectedView.name $backendUiStore.selectedView && $backendUiStore.selectedView.name
@ -34,12 +32,6 @@
</script> </script>
{#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id} {#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id}
<div class="title">
<i
data-cy="new-datasource"
on:click={modal.show}
class="ri-add-circle-fill" />
</div>
<div class="hierarchy-items-container"> <div class="hierarchy-items-container">
{#each $backendUiStore.datasources as datasource, idx} {#each $backendUiStore.datasources as datasource, idx}
<NavItem <NavItem
@ -64,23 +56,3 @@
{/each} {/each}
</div> </div>
{/if} {/if}
<Modal bind:this={modal}>
<CreateDatasourceModal />
</Modal>
<style>
.title {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.title i {
font-size: 20px;
}
.title i:hover {
cursor: pointer;
color: var(--blue);
}
</style>

View File

@ -2,14 +2,11 @@
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
import { TableNames } from "constants" import { TableNames } from "constants"
import CreateTableModal from "./modals/CreateTableModal.svelte"
import EditTablePopover from "./popovers/EditTablePopover.svelte" import EditTablePopover from "./popovers/EditTablePopover.svelte"
import EditViewPopover from "./popovers/EditViewPopover.svelte" import EditViewPopover from "./popovers/EditViewPopover.svelte"
import { Modal, Switcher } from "@budibase/bbui" import { Switcher } from "@budibase/bbui"
import NavItem from "components/common/NavItem.svelte" import NavItem from "components/common/NavItem.svelte"
let modal
$: selectedView = $: selectedView =
$backendUiStore.selectedView && $backendUiStore.selectedView.name $backendUiStore.selectedView && $backendUiStore.selectedView.name
@ -35,9 +32,6 @@
</script> </script>
{#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id} {#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id}
<div class="title">
<i data-cy="new-table" on:click={modal.show} class="ri-add-circle-fill" />
</div>
<div class="hierarchy-items-container"> <div class="hierarchy-items-container">
{#each $backendUiStore.tables as table, idx} {#each $backendUiStore.tables as table, idx}
<NavItem <NavItem
@ -64,27 +58,3 @@
{/each} {/each}
</div> </div>
{/if} {/if}
<Modal bind:this={modal}>
<CreateTableModal />
</Modal>
<style>
.title {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.title h1 {
font-size: var(--font-size-m);
font-weight: 500;
margin: 0;
}
.title i {
font-size: 20px;
}
.title i:hover {
cursor: pointer;
color: var(--blue);
}
</style>

View File

@ -13,9 +13,6 @@
tables: $backendUiStore.tables, tables: $backendUiStore.tables,
}) })
// just wraps binding in {{ ... }}
const toBindingExpression = bindingPath => `{{ ${bindingPath} }}`
const tableFields = tableId => { const tableFields = tableId => {
const table = $backendUiStore.tables.find(m => m._id === tableId) const table = $backendUiStore.tables.find(m => m._id === tableId)
@ -63,12 +60,4 @@
grid-column-start: 2; grid-column-start: 2;
grid-column-end: 6; grid-column-end: 6;
} }
.cannot-use {
color: var(--red);
font-size: var(--font-size-s);
text-align: center;
width: 70%;
margin: auto;
}
</style> </style>

View File

@ -42,8 +42,6 @@
typeof tableInfo === "string" ? tableInfo : tableInfo.tableId typeof tableInfo === "string" ? tableInfo : tableInfo.tableId
} }
} }
console.log(parameters)
} }
} }
</script> </script>

View File

@ -0,0 +1,32 @@
<script>
import { Select, Label, Spacer } from "@budibase/bbui"
import { backendUiStore } from "builderStore"
export let parameters
$: datasource = $backendUiStore.datasources.find(
ds => ds._id === parameters.datasourceId
)
</script>
<div class="root">
<Label size="m" color="dark">Datasource</Label>
<Select secondary bind:value={parameters.datasourceId}>
<option value="" />
{#each $backendUiStore.datasources as datasource}
<option value={datasource._id}>{datasource.name}</option>
{/each}
</Select>
<Spacer medium />
{#if parameters.datasourceId}
<Label size="m" color="dark">Query</Label>
<Select secondary bind:value={parameters.queryId}>
<option value="" />
{#each Object.keys(datasource.queries) as query}
<option value={query}>{datasource.queries[query].name}</option>
{/each}
</Select>
{/if}
</div>

View File

@ -1,6 +1,7 @@
import NavigateTo from "./NavigateTo.svelte" import NavigateTo from "./NavigateTo.svelte"
import SaveRow from "./SaveRow.svelte" import SaveRow from "./SaveRow.svelte"
import DeleteRow from "./DeleteRow.svelte" import DeleteRow from "./DeleteRow.svelte"
import ExecuteQuery from "./ExecuteQuery.svelte"
// defines what actions are available, when adding a new one // defines what actions are available, when adding a new one
// the component is the setup panel for the action // the component is the setup panel for the action
@ -20,4 +21,8 @@ export default [
name: "Navigate To", name: "Navigate To",
component: NavigateTo, component: NavigateTo,
}, },
{
name: "Execute Query",
component: ExecuteQuery,
},
] ]

View File

@ -1,7 +1,10 @@
<script> <script>
import { Switcher } from "@budibase/bbui" import { params } from "@sveltech/routify"
import { Switcher, Modal } from "@budibase/bbui"
import TableNavigator from "components/backend/TableNavigator/TableNavigator.svelte" import TableNavigator from "components/backend/TableNavigator/TableNavigator.svelte"
import DatasourceNavigator from "components/backend/DatasourceNavigator/DatasourceNavigator.svelte" import DatasourceNavigator from "components/backend/DatasourceNavigator/DatasourceNavigator.svelte"
import CreateDatasourceModal from "components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte"
import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte"
const tabs = [ const tabs = [
{ {
@ -14,17 +17,31 @@
}, },
] ]
let tab = "table" let tab = $params.selectedDatasource ? "datasource" : "table"
let modal
</script> </script>
<!-- routify:options index=0 --> <!-- routify:options index=0 -->
<div class="root"> <div class="root">
<div class="nav"> <div class="nav">
<Switcher headings={tabs} bind:value={tab}> <Switcher headings={tabs} bind:value={tab}>
<div class="title">
<i
data-cy={`new-${tab}`}
class="ri-add-circle-fill"
on:click={modal.show} />
</div>
{#if tab === 'table'} {#if tab === 'table'}
<TableNavigator /> <TableNavigator />
<Modal bind:this={modal}>
<CreateTableModal />
</Modal>
{:else if tab === 'datasource'} {:else if tab === 'datasource'}
<DatasourceNavigator /> <DatasourceNavigator />
<Modal bind:this={modal}>
<CreateDatasourceModal />
</Modal>
{/if} {/if}
</Switcher> </Switcher>
</div> </div>
@ -40,6 +57,7 @@
grid-template-columns: 260px minmax(0, 1fr); grid-template-columns: 260px minmax(0, 1fr);
background: var(--grey-2); background: var(--grey-2);
} }
.content { .content {
flex: 1 1 auto; flex: 1 1 auto;
padding: var(--spacing-l) 40px; padding: var(--spacing-l) 40px;
@ -50,6 +68,7 @@
align-items: stretch; align-items: stretch;
gap: var(--spacing-l); gap: var(--spacing-l);
} }
.nav { .nav {
overflow-y: auto; overflow-y: auto;
background: var(--background); background: var(--background);
@ -59,5 +78,18 @@
justify-content: flex-start; justify-content: flex-start;
align-items: stretch; align-items: stretch;
gap: var(--spacing-l); gap: var(--spacing-l);
position: relative;
}
i {
font-size: 20px;
position: absolute;
top: var(--spacing-l);
right: var(--spacing-xl);
}
i:hover {
cursor: pointer;
color: var(--blue);
} }
</style> </style>

View File

@ -10,6 +10,6 @@
$: query = datasource && datasource.queries[$params.query] $: query = datasource && datasource.queries[$params.query]
</script> </script>
{#if $backendUiStore.selectedDatabase._id && datasource} {#if $backendUiStore.selectedDatabase._id && datasource && query}
<ExternalDataSourceTable {query} datasourceId={datasource._id} /> <ExternalDataSourceTable {query} {datasource} />
{/if} {/if}

View File

@ -36,7 +36,7 @@
margin-bottom: var(--spacing-s); margin-bottom: var(--spacing-s);
} }
section { section {
background: white; background: var(--background);
border-radius: var(--border-radius-m); border-radius: var(--border-radius-m);
padding: var(--spacing-xl); padding: var(--spacing-xl);
} }

View File

@ -9,3 +9,14 @@ export const fetchQueryData = async ({ datasourceId, queryId }) => {
}) })
return response.rows return response.rows
} }
/**
* Executes a query against an external data connector.
*/
export const executeQuery = async ({ datasourceId, queryId }) => {
const response = await API.post({
url: `/api/datasources/${datasourceId}/queries/${queryId}`,
// body: params,
})
return response.rows
}

View File

@ -1,6 +1,6 @@
import { enrichDataBinding } from "./enrichDataBinding" import { enrichDataBinding, enrichDataBindings } from "./enrichDataBinding"
import { routeStore } from "../store" import { routeStore } from "../store"
import { saveRow, deleteRow } from "../api" import { saveRow, deleteRow, executeQuery } from "../api"
const saveRowHandler = async (action, context) => { const saveRowHandler = async (action, context) => {
let draft = context[`${action.parameters.contextPath}_draft`] let draft = context[`${action.parameters.contextPath}_draft`]
@ -25,10 +25,27 @@ const navigationHandler = action => {
routeStore.actions.navigate(action.parameters.url) routeStore.actions.navigate(action.parameters.url)
} }
const queryExecutionHandler = async (action, context) => {
const { datasourceId, queryId, params } = action.parameters
console.log(context)
// TODO: allow context based bindings for query params
// const enrichedQueryParameters = enrichDataBindings(params, context)
// console.log({
// action,
// context,
// // enrichedQueryParameters,
// datasourceId,
// // queryId
// })
await executeQuery({ datasourceId, queryId })
}
const handlerMap = { const handlerMap = {
["Save Row"]: saveRowHandler, ["Save Row"]: saveRowHandler,
["Delete Row"]: deleteRowHandler, ["Delete Row"]: deleteRowHandler,
["Navigate To"]: navigationHandler, ["Navigate To"]: navigationHandler,
["Execute Query"]: queryExecutionHandler,
} }
/** /**

View File

@ -115,7 +115,7 @@ exports.previewQuery = async function(ctx) {
ctx.body = await new Integration(config, query).query() ctx.body = await new Integration(config, query).query()
} }
exports.executeQuery = async function(ctx) { exports.fetchQuery = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.user.appId)
const datasource = await db.get(ctx.params.datasourceId) const datasource = await db.get(ctx.params.datasourceId)
@ -139,3 +139,28 @@ exports.executeQuery = async function(ctx) {
rows, rows,
} }
} }
exports.executeQuery = async function(ctx) {
const db = new CouchDB(ctx.user.appId)
const datasource = await db.get(ctx.params.datasourceId)
const query = datasource.queries[ctx.params.queryId]
const Integration = integrations[datasource.source]
if (!Integration) {
ctx.throw(400, "Integration type does not exist.")
return
}
// TODO: allow the ability to POST parameters down when executing the query
// const customParams = ctx.request.body
const response = await new Integration(
datasource.config,
query.queryString
).query()
ctx.body = response
}

View File

@ -28,6 +28,11 @@ router
datasourceController.previewQuery datasourceController.previewQuery
) )
.get( .get(
"/api/datasources/:datasourceId/queries/:queryId",
authorized(BUILDER),
datasourceController.fetchQuery
)
.post(
"/api/datasources/:datasourceId/queries/:queryId", "/api/datasources/:datasourceId/queries/:queryId",
authorized(BUILDER), authorized(BUILDER),
datasourceController.executeQuery datasourceController.executeQuery