Allow dynamic query execution from front end components
This commit is contained in:
parent
963d8cb679
commit
8e40f4b5f8
|
@ -40,6 +40,7 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
libraries: null,
|
libraries: null,
|
||||||
appId: "",
|
appId: "",
|
||||||
routes: {},
|
routes: {},
|
||||||
|
bottomDrawerVisible: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFrontendStore = () => {
|
export const getFrontendStore = () => {
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
<script>
|
|
||||||
import { goto } from "@sveltech/routify"
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
TextButton as Button,
|
|
||||||
Icon,
|
|
||||||
Label,
|
|
||||||
Modal,
|
|
||||||
ModalContent,
|
|
||||||
TextArea,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { notifier } from "builderStore/store/notifications"
|
|
||||||
import { backendUiStore } from "builderStore"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
import EditIntegrationConfig from "../modals/EditIntegrationConfig.svelte"
|
|
||||||
// import CreateEditQuery from "components/backend/DataTable/modals/CreateEditQuery.svelte"
|
|
||||||
|
|
||||||
export let query = {}
|
|
||||||
export let edit
|
|
||||||
|
|
||||||
let modal
|
|
||||||
let fields = []
|
|
||||||
|
|
||||||
async function saveQuery() {
|
|
||||||
try {
|
|
||||||
await backendUiStore.actions.queries.save(query.datasourceId, query)
|
|
||||||
notifier.success(`Query created successfully.`)
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
notifier.danger(`Error creating query. ${err.message}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Button text small on:click={modal.show}>
|
|
||||||
<Icon name="filter" />
|
|
||||||
{edit ? 'Edit' : 'Create'} Query
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<!-- <Modal bind:this={modal}> -->
|
|
||||||
<!-- <ModalContent
|
|
||||||
confirmText="Save"
|
|
||||||
cancelText="Cancel"
|
|
||||||
onConfirm={saveQuery}
|
|
||||||
title={edit ? 'Edit Query' : 'Create New Query'}> -->
|
|
||||||
<!-- <CreateEditQuery bind:query /> -->
|
|
||||||
<!-- </ModalContent> -->
|
|
||||||
<!-- </Modal> -->
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
<script>
|
||||||
|
import { slide } from "svelte/transition"
|
||||||
|
import Portal from "svelte-portal"
|
||||||
|
|
||||||
|
export let title
|
||||||
|
export let onClose
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Portal>
|
||||||
|
<section class="drawer" transition:slide>
|
||||||
|
{#if title}
|
||||||
|
<heading>{title}</heading>
|
||||||
|
{/if}
|
||||||
|
<slot />
|
||||||
|
</section>
|
||||||
|
</Portal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.drawer {
|
||||||
|
height: 50vh;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 97%;
|
||||||
|
background: var(--background);
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
border-top: var(--border-light);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -12,6 +12,10 @@
|
||||||
|
|
||||||
// $: codemirror && codemirror.setValue(value)
|
// $: codemirror && codemirror.setValue(value)
|
||||||
|
|
||||||
|
console.log("Running init")
|
||||||
|
|
||||||
|
$: console.log("Running reactive")
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
codemirror = cm.fromTextArea(editor, {
|
codemirror = cm.fromTextArea(editor, {
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
<script>
|
||||||
|
import { TextArea, Label, Input, Heading } from "@budibase/bbui"
|
||||||
|
import Editor from "./QueryEditor.svelte"
|
||||||
|
import BindableInput from "components/userInterface/BindableInput.svelte"
|
||||||
|
|
||||||
|
export let parameters = []
|
||||||
|
export let bindings = []
|
||||||
|
export let customParams = {}
|
||||||
|
|
||||||
|
function newQueryParameter() {
|
||||||
|
parameters = [...parameters, {}]
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteQueryParameter(idx) {
|
||||||
|
parameters.splice(idx, 1)
|
||||||
|
parameters = parameters
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Heading extraSmall black>Parameters</Heading>
|
||||||
|
<div class="parameters">
|
||||||
|
<Label extraSmall grey>Parameter Name</Label>
|
||||||
|
<Label extraSmall grey>Default</Label>
|
||||||
|
<Label extraSmall grey>Value</Label>
|
||||||
|
<div />
|
||||||
|
{#each parameters as parameter, idx}
|
||||||
|
<Input thin bind:value={parameter.name} />
|
||||||
|
<Input thin bind:value={parameter.default} />
|
||||||
|
<BindableInput
|
||||||
|
type="string"
|
||||||
|
thin
|
||||||
|
bind:value={customParams[parameter.name]}
|
||||||
|
{bindings} />
|
||||||
|
<i
|
||||||
|
class="ri-close-circle-line delete"
|
||||||
|
on:click={() => deleteQueryParameter(idx)} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<i class="ri-add-circle-line add" on:click={newQueryParameter} />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.parameters {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 5%;
|
||||||
|
grid-gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add {
|
||||||
|
margin-top: var(--spacing-m);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -85,8 +85,18 @@
|
||||||
|
|
||||||
async function previewQuery() {
|
async function previewQuery() {
|
||||||
try {
|
try {
|
||||||
|
// parse all the parameters in the UI
|
||||||
|
// send them
|
||||||
|
|
||||||
const response = await api.post(`/api/queries/preview`, {
|
const response = await api.post(`/api/queries/preview`, {
|
||||||
parameters: query.parameters,
|
// TODO: revisit
|
||||||
|
parameters: query.parameters.reduce(
|
||||||
|
(acc, next) => ({
|
||||||
|
...acc,
|
||||||
|
[next.name]: next.default,
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
),
|
||||||
datasourceId: datasource._id,
|
datasourceId: datasource._id,
|
||||||
query: query.queryString,
|
query: query.queryString,
|
||||||
})
|
})
|
||||||
|
@ -96,7 +106,8 @@
|
||||||
|
|
||||||
data = json || []
|
data = json || []
|
||||||
|
|
||||||
// TODO: refactor
|
// Assume all the fields are strings and create a basic schema
|
||||||
|
// from the first record returned by the query
|
||||||
fields = Object.keys(json[0]).map(field => ({
|
fields = Object.keys(json[0]).map(field => ({
|
||||||
name: field,
|
name: field,
|
||||||
type: "STRING",
|
type: "STRING",
|
||||||
|
|
|
@ -1,65 +1,16 @@
|
||||||
<script>
|
<script>
|
||||||
import { TextArea, Label, Input, Heading } from "@budibase/bbui"
|
import { TextArea, Label, Input, Heading } from "@budibase/bbui"
|
||||||
import Editor from "./QueryEditor.svelte"
|
import Editor from "./QueryEditor.svelte"
|
||||||
|
import ParameterBuilder from "./QueryParameterBuilder.svelte"
|
||||||
const CAPTURE_VAR_INSIDE_MUSTACHE = /{{([^}]+)}}/g
|
|
||||||
|
|
||||||
const QueryTypes = {
|
const QueryTypes = {
|
||||||
SQL: "sql",
|
SQL: "sql",
|
||||||
}
|
}
|
||||||
|
|
||||||
export let query
|
export let query
|
||||||
|
|
||||||
// TODO: bind these to the query
|
|
||||||
let parameters = []
|
|
||||||
|
|
||||||
$: query.parameters = parameters.reduce(
|
|
||||||
(acc, next) => ({ [next.key]: next.value, ...acc }),
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
function newQueryParameter() {
|
|
||||||
parameters = [...parameters, {}]
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteQueryParameter(idx) {
|
|
||||||
parameters.splice(idx, 1)
|
|
||||||
parameters = parameters
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Heading extraSmall black>Parameters</Heading>
|
|
||||||
|
|
||||||
{#if query.queryType === QueryTypes.SQL}
|
{#if query.queryType === QueryTypes.SQL}
|
||||||
<section>
|
<ParameterBuilder bind:parameters={query.parameters} />
|
||||||
<div class="parameters">
|
|
||||||
<Label extraSmall grey>Parameter Name</Label>
|
|
||||||
<Label extraSmall grey>Default Value</Label>
|
|
||||||
<!-- CLEAR ALL PARAMS OR SOMETHING -->
|
|
||||||
<i class="ri-close-circle-line delete" on:click={console.log} />
|
|
||||||
{#each parameters as parameter, idx}
|
|
||||||
<Input thin bind:value={parameter.key} />
|
|
||||||
<Input thin bind:value={parameter.value} />
|
|
||||||
<i
|
|
||||||
class="ri-close-circle-line delete"
|
|
||||||
on:click={() => deleteQueryParameter(idx)} />
|
|
||||||
{/each}
|
|
||||||
<i class="ri-add-circle-line" on:click={newQueryParameter} />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<Editor label="Query" bind:value={query.queryString} />
|
<Editor label="Query" bind:value={query.queryString} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
|
||||||
.parameters {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 5%;
|
|
||||||
grid-gap: 10px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
|
||||||
margin-bottom: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
<script>
|
||||||
|
import BottomDrawer from "components/common/BottomDrawer.svelte"
|
||||||
|
import { store, backendUiStore } from "builderStore"
|
||||||
|
import { slide } from "svelte/transition"
|
||||||
|
import QueryInterface from "components/integration/QueryViewer.svelte"
|
||||||
|
|
||||||
|
$: query = $backendUiStore.queries.find(
|
||||||
|
query => query._id === $backendUiStore.selectedQueryId
|
||||||
|
)
|
||||||
|
|
||||||
|
function closeDatabindingDrawer() {
|
||||||
|
store.update(state => {
|
||||||
|
state.bindingDrawerVisible = false
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if query}
|
||||||
|
<BottomDrawer>
|
||||||
|
<div class="drawer-contents">
|
||||||
|
<i class="ri-close-fill close" on:click={closeDatabindingDrawer} />
|
||||||
|
<QueryInterface {query} />
|
||||||
|
</div>
|
||||||
|
</BottomDrawer>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
i {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--spacing-xl);
|
||||||
|
right: var(--spacing-xl);
|
||||||
|
font-size: var(--font-size-m);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,5 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { TextButton, Body, DropdownMenu, ModalContent } from "@budibase/bbui"
|
import {
|
||||||
|
Button,
|
||||||
|
TextButton,
|
||||||
|
Body,
|
||||||
|
DropdownMenu,
|
||||||
|
ModalContent,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { AddIcon, ArrowDownIcon } from "components/common/Icons/"
|
import { AddIcon, ArrowDownIcon } from "components/common/Icons/"
|
||||||
import actionTypes from "./actions"
|
import actionTypes from "./actions"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
@ -49,61 +55,58 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ModalContent title="Actions" confirmText="Save" onConfirm={saveEventData}>
|
<div>
|
||||||
<div slot="header">
|
<div bind:this={addActionButton}>
|
||||||
<div bind:this={addActionButton}>
|
<TextButton text small blue on:click={addActionDropdown.show}>
|
||||||
<TextButton text small blue on:click={addActionDropdown.show}>
|
<div style="height: 20px; width: 20px;">
|
||||||
<div style="height: 20px; width: 20px;">
|
<AddIcon />
|
||||||
<AddIcon />
|
|
||||||
</div>
|
|
||||||
Add Action
|
|
||||||
</TextButton>
|
|
||||||
</div>
|
|
||||||
<DropdownMenu
|
|
||||||
bind:this={addActionDropdown}
|
|
||||||
anchor={addActionButton}
|
|
||||||
align="right">
|
|
||||||
<div class="available-actions-container">
|
|
||||||
{#each actionTypes as actionType}
|
|
||||||
<div class="available-action" on:click={addAction(actionType)}>
|
|
||||||
<span>{actionType.name}</span>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenu>
|
Add Action
|
||||||
|
</TextButton>
|
||||||
</div>
|
</div>
|
||||||
|
<DropdownMenu
|
||||||
<div class="actions-container">
|
bind:this={addActionDropdown}
|
||||||
{#if actions && actions.length > 0}
|
anchor={addActionButton}
|
||||||
{#each actions as action, index}
|
align="right">
|
||||||
<div class="action-container">
|
<div class="available-actions-container">
|
||||||
<div class="action-header" on:click={selectAction(action)}>
|
{#each actionTypes as actionType}
|
||||||
<Body small lh>{index + 1}. {action[eventTypeKey]}</Body>
|
<div class="available-action" on:click={addAction(actionType)}>
|
||||||
<div class="row-expander" class:rotate={action !== selectedAction}>
|
<span>{actionType.name}</span>
|
||||||
<ArrowDownIcon />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if action === selectedAction}
|
|
||||||
<div class="selected-action-container">
|
|
||||||
<svelte:component
|
|
||||||
this={selectedActionComponent}
|
|
||||||
parameters={selectedAction.parameters} />
|
|
||||||
<div class="delete-action-button">
|
|
||||||
<TextButton text medium on:click={() => deleteAction(index)}>
|
|
||||||
Delete
|
|
||||||
</TextButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div slot="footer">
|
<div class="actions-container">
|
||||||
<a href="https://docs.budibase.com">Learn more about Actions</a>
|
{#if actions && actions.length > 0}
|
||||||
</div>
|
{#each actions as action, index}
|
||||||
</ModalContent>
|
<div class="action-container">
|
||||||
|
<div class="action-header" on:click={selectAction(action)}>
|
||||||
|
<Body small lh>{index + 1}. {action[eventTypeKey]}</Body>
|
||||||
|
<div class="row-expander" class:rotate={action !== selectedAction}>
|
||||||
|
<ArrowDownIcon />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if action === selectedAction}
|
||||||
|
<div class="selected-action-container">
|
||||||
|
<svelte:component
|
||||||
|
this={selectedActionComponent}
|
||||||
|
parameters={selectedAction.parameters} />
|
||||||
|
<div class="delete-action-button">
|
||||||
|
<TextButton text medium on:click={() => deleteAction(index)}>
|
||||||
|
Delete
|
||||||
|
</TextButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
<Button thin blue on:click={saveEventData}>Save</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="https://docs.budibase.com">Learn more about Actions</a>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.action-header {
|
.action-header {
|
||||||
|
|
|
@ -1,17 +1,26 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, Modal } from "@budibase/bbui"
|
import { Button, Modal } from "@budibase/bbui"
|
||||||
import EventEditorModal from "./EventEditorModal.svelte"
|
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
import EventEditorModal from "./EventEditorModal.svelte"
|
||||||
|
import BottomDrawer from "components/common/BottomDrawer.svelte"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
export let name
|
export let name
|
||||||
|
|
||||||
let modal
|
let drawerVisible
|
||||||
|
|
||||||
|
function showDrawer() {
|
||||||
|
drawerVisible = true
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button secondary small on:click={modal.show}>Define Actions</Button>
|
<Button secondary small on:click={showDrawer}>Define Actions</Button>
|
||||||
|
|
||||||
<Modal bind:this={modal} width="600px">
|
{#if drawerVisible}
|
||||||
<EventEditorModal event={value} eventType={name} on:change />
|
<BottomDrawer>
|
||||||
</Modal>
|
<EventEditorModal event={value} eventType={name} on:change />
|
||||||
|
</BottomDrawer>
|
||||||
|
{/if}
|
||||||
|
|
|
@ -1,17 +1,36 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Label, Spacer } from "@budibase/bbui"
|
import { Select, Label, Spacer } from "@budibase/bbui"
|
||||||
import { backendUiStore } from "builderStore"
|
import { store, backendUiStore, currentAsset } from "builderStore"
|
||||||
|
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||||
|
import ParameterBuilder from "../../../integration/QueryParameterBuilder.svelte"
|
||||||
|
|
||||||
export let parameters
|
export let parameters
|
||||||
|
|
||||||
$: datasource = $backendUiStore.datasources.find(
|
$: datasource = $backendUiStore.datasources.find(
|
||||||
ds => ds._id === parameters.datasourceId
|
ds => ds._id === parameters.datasourceId
|
||||||
)
|
)
|
||||||
|
// TODO: binding needs a significant refactor and needs to
|
||||||
|
// be centralised
|
||||||
|
$: bindableProperties = fetchBindableProperties({
|
||||||
|
componentInstanceId: $store.selectedComponentId,
|
||||||
|
components: $store.components,
|
||||||
|
screen: $currentAsset,
|
||||||
|
tables: $backendUiStore.tables,
|
||||||
|
}).map(property => ({
|
||||||
|
...property,
|
||||||
|
category: property.type === "instance" ? "Component" : "Table",
|
||||||
|
label: property.readableBinding,
|
||||||
|
path: property.runtimeBinding,
|
||||||
|
}))
|
||||||
|
|
||||||
|
$: query =
|
||||||
|
parameters.queryId &&
|
||||||
|
$backendUiStore.queries.find(query => query._id === parameters.queryId)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<Label size="m" color="dark">Datasource</Label>
|
<Label size="m" color="dark">Datasource</Label>
|
||||||
<Select secondary bind:value={parameters.datasourceId}>
|
<Select thin secondary bind:value={parameters.datasourceId}>
|
||||||
<option value="" />
|
<option value="" />
|
||||||
{#each $backendUiStore.datasources as datasource}
|
{#each $backendUiStore.datasources as datasource}
|
||||||
<option value={datasource._id}>{datasource.name}</option>
|
<option value={datasource._id}>{datasource.name}</option>
|
||||||
|
@ -22,11 +41,22 @@
|
||||||
|
|
||||||
{#if parameters.datasourceId}
|
{#if parameters.datasourceId}
|
||||||
<Label size="m" color="dark">Query</Label>
|
<Label size="m" color="dark">Query</Label>
|
||||||
<Select secondary bind:value={parameters.queryId}>
|
<Select thin secondary bind:value={parameters.queryId}>
|
||||||
<option value="" />
|
<option value="" />
|
||||||
{#each $backendUiStore.queries.filter(query => query.datasourceId === datasource._id) as query}
|
{#each $backendUiStore.queries.filter(query => query.datasourceId === datasource._id) as query}
|
||||||
<option value={query._id}>{query.name}</option>
|
<option value={query._id}>{query.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<Spacer medium />
|
||||||
|
|
||||||
|
<!-- TODO: Need to render defaults, but allow interpolation of frontend values -->
|
||||||
|
{#if query}
|
||||||
|
<ParameterBuilder
|
||||||
|
bind:customParams={parameters.queryParams}
|
||||||
|
parameters={query.parameters}
|
||||||
|
bindings={bindableProperties} />
|
||||||
|
<pre>{query.queryString}</pre>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
|
|
||||||
let getCaretPosition
|
let getCaretPosition
|
||||||
|
|
||||||
|
$: console.log(bindings)
|
||||||
|
|
||||||
$: categories = Object.entries(groupBy("category", bindings))
|
$: categories = Object.entries(groupBy("category", bindings))
|
||||||
|
|
||||||
function onClickBinding(binding) {
|
function onClickBinding(binding) {
|
||||||
|
@ -50,7 +52,9 @@
|
||||||
<span class="binding__label">{binding.label}</span>
|
<span class="binding__label">{binding.label}</span>
|
||||||
<span class="binding__type">{binding.type}</span>
|
<span class="binding__type">{binding.type}</span>
|
||||||
<br />
|
<br />
|
||||||
<div class="binding__description">{binding.description}</div>
|
<div class="binding__description">
|
||||||
|
{binding.description || ''}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -120,6 +120,7 @@
|
||||||
flex-flow: row;
|
flex-flow: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -14,6 +14,17 @@
|
||||||
dropdownRight.hide()
|
dropdownRight.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openBindingDrawer() {
|
||||||
|
backendUiStore.update(state => {
|
||||||
|
state.selectedQueryId = value._id
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
store.update(state => {
|
||||||
|
state.bottomDrawerVisible = true
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
$: tables = $backendUiStore.tables.map(m => ({
|
$: tables = $backendUiStore.tables.map(m => ({
|
||||||
label: m.name,
|
label: m.name,
|
||||||
name: `all_${m._id}`,
|
name: `all_${m._id}`,
|
||||||
|
@ -67,6 +78,9 @@
|
||||||
<span>{value.label ? value.label : 'Table / View / Query'}</span>
|
<span>{value.label ? value.label : 'Table / View / Query'}</span>
|
||||||
<Icon name="arrowdown" />
|
<Icon name="arrowdown" />
|
||||||
</div>
|
</div>
|
||||||
|
{#if value.type === "query"}
|
||||||
|
<i class="ri-settings-3-line" on:click={openBindingDrawer} />
|
||||||
|
{/if}
|
||||||
<DropdownMenu bind:this={dropdownRight} anchor={anchorRight}>
|
<DropdownMenu bind:this={dropdownRight} anchor={anchorRight}>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
|
|
|
@ -14,10 +14,11 @@
|
||||||
query = {
|
query = {
|
||||||
datasourceId: $params.selectedDatasource,
|
datasourceId: $params.selectedDatasource,
|
||||||
name: "New Query",
|
name: "New Query",
|
||||||
parameters: {},
|
parameters: [],
|
||||||
// TODO: set dynamically
|
// TODO: set dynamically
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log("The query changes", query)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
|
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
|
||||||
import CreateQueryButton from "components/backend/DataTable/buttons/CreateQueryButton.svelte"
|
|
||||||
|
|
||||||
$: datasource = $backendUiStore.datasources.find(
|
$: datasource = $backendUiStore.datasources.find(
|
||||||
ds => ds._id === $backendUiStore.selectedDatasourceId
|
ds => ds._id === $backendUiStore.selectedDatasourceId
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
import ComponentPropertiesPanel from "components/userInterface/ComponentPropertiesPanel.svelte"
|
import ComponentPropertiesPanel from "components/userInterface/ComponentPropertiesPanel.svelte"
|
||||||
import ComponentSelectionList from "components/userInterface/ComponentSelectionList.svelte"
|
import ComponentSelectionList from "components/userInterface/ComponentSelectionList.svelte"
|
||||||
import FrontendNavigatePane from "components/userInterface/FrontendNavigatePane.svelte"
|
import FrontendNavigatePane from "components/userInterface/FrontendNavigatePane.svelte"
|
||||||
|
import DataBindingDrawer from "components/userInterface/DataBindingDrawer/index.svelte"
|
||||||
|
|
||||||
$: instance = $store.appInstance
|
$: instance = $store.appInstance
|
||||||
|
|
||||||
|
@ -47,6 +48,10 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if $store.bottomDrawerVisible}
|
||||||
|
<DataBindingDrawer />
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if $selectedComponent != null}
|
{#if $selectedComponent != null}
|
||||||
<div class="components-pane">
|
<div class="components-pane">
|
||||||
<ComponentPropertiesPanel />
|
<ComponentPropertiesPanel />
|
||||||
|
@ -103,6 +108,15 @@
|
||||||
padding: var(--spacing-l) var(--spacing-xl);
|
padding: var(--spacing-l) var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.binding-drawer-container {
|
||||||
|
height: 50vh;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--background);
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
.nav-group-header > div:nth-child(1) {
|
.nav-group-header > div:nth-child(1) {
|
||||||
padding: 0rem 0.5rem 0rem 0rem;
|
padding: 0rem 0.5rem 0rem 0rem;
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
|
|
|
@ -13,10 +13,12 @@ export const fetchQueryData = async ({ _id }) => {
|
||||||
/**
|
/**
|
||||||
* Executes a query against an external data connector.
|
* Executes a query against an external data connector.
|
||||||
*/
|
*/
|
||||||
export const executeQuery = async ({ _id }) => {
|
export const executeQuery = async ({ queryId, params }) => {
|
||||||
const response = await API.post({
|
const response = await API.post({
|
||||||
url: `/api/queries/${_id}`,
|
url: `/api/queries/${queryId}`,
|
||||||
// body: params,
|
body: {
|
||||||
|
params,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
return response.rows
|
return response.rows
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,19 +26,12 @@ const navigationHandler = action => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryExecutionHandler = async (action, context) => {
|
const queryExecutionHandler = async (action, context) => {
|
||||||
const { datasourceId, queryId, params } = action.parameters
|
const { datasourceId, queryId, queryParams } = action.parameters
|
||||||
console.log(context)
|
|
||||||
// TODO: allow context based bindings for query params
|
|
||||||
// const enrichedQueryParameters = enrichDataBindings(params, context)
|
|
||||||
|
|
||||||
// console.log({
|
// TODO: allow context based bindings for query params
|
||||||
// action,
|
const enrichedQueryParameters = enrichDataBindings(queryParams, context)
|
||||||
// context,
|
|
||||||
// // enrichedQueryParameters,
|
await executeQuery({ datasourceId, queryId, params: enrichedQueryParameters })
|
||||||
// datasourceId,
|
|
||||||
// // queryId
|
|
||||||
// })
|
|
||||||
await executeQuery({ datasourceId, queryId })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlerMap = {
|
const handlerMap = {
|
||||||
|
|
|
@ -75,9 +75,13 @@ exports.preview = async function(ctx) {
|
||||||
exports.execute = async function(ctx) {
|
exports.execute = 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 query = await db.get(ctx.params.queryId)
|
||||||
|
const datasource = await db.get(query.datasourceId)
|
||||||
|
|
||||||
const query = datasource.queries[ctx.params.queryId]
|
const queryTemplate = handlebars.compile(query.queryString)
|
||||||
|
|
||||||
|
// TODO: Take the default params into account
|
||||||
|
const parsedQuery = queryTemplate(ctx.request.body.params)
|
||||||
|
|
||||||
const Integration = integrations[datasource.source]
|
const Integration = integrations[datasource.source]
|
||||||
|
|
||||||
|
@ -85,17 +89,7 @@ exports.execute = async function(ctx) {
|
||||||
ctx.throw(400, "Integration type does not exist.")
|
ctx.throw(400, "Integration type does not exist.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const response = await new Integration(datasource.config, parsedQuery).query()
|
||||||
// TODO: allow the ability to POST parameters down when executing the query
|
|
||||||
// const customParams = ctx.request.body
|
|
||||||
const queryTemplate = handlebars.compile(query.queryString)
|
|
||||||
|
|
||||||
const response = await new Integration(
|
|
||||||
datasource.config,
|
|
||||||
queryTemplate({
|
|
||||||
// pass the params here from the UI and backend contexts
|
|
||||||
})
|
|
||||||
).query()
|
|
||||||
|
|
||||||
ctx.body = response
|
ctx.body = response
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,6 @@ function replicateLocal() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
replicateLocal()
|
// replicateLocal()
|
||||||
|
|
||||||
module.exports = Pouch
|
module.exports = Pouch
|
||||||
|
|
|
@ -1,5 +1,47 @@
|
||||||
const { Client } = require("pg")
|
const { Client } = require("pg")
|
||||||
|
|
||||||
|
const SCHEMA = {
|
||||||
|
datasource: {
|
||||||
|
host: {
|
||||||
|
type: "string",
|
||||||
|
default: "localhost",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
port: {
|
||||||
|
type: "number",
|
||||||
|
required: true,
|
||||||
|
default: 5432,
|
||||||
|
},
|
||||||
|
database: {
|
||||||
|
type: "string",
|
||||||
|
default: "postgres",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
username: {
|
||||||
|
type: "string",
|
||||||
|
default: "root",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: "password",
|
||||||
|
default: "root",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
editor: {
|
||||||
|
sql: {
|
||||||
|
type: "sql",
|
||||||
|
},
|
||||||
|
gui: {
|
||||||
|
type: "config",
|
||||||
|
fields: {
|
||||||
|
something: "",
|
||||||
|
other: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
const DATASOURCE_CONFIG = {
|
const DATASOURCE_CONFIG = {
|
||||||
host: {
|
host: {
|
||||||
type: "string",
|
type: "string",
|
||||||
|
@ -34,10 +76,16 @@ const QUERY_CONFIG = {
|
||||||
},
|
},
|
||||||
gui: {
|
gui: {
|
||||||
type: "config",
|
type: "config",
|
||||||
fields: {
|
fields: [
|
||||||
something: "",
|
{
|
||||||
other: "",
|
name: "",
|
||||||
},
|
type: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue