add base wizard for datasources

This commit is contained in:
Peter Clement 2021-09-23 21:47:22 +01:00
parent c91e5ea39c
commit 0de6b55438
5 changed files with 224 additions and 67 deletions

View File

@ -1,74 +1,159 @@
<script> <script>
import { goto } from "@roxi/routify" import { ModalContent, Modal, Body, Layout, Label } from "@budibase/bbui"
import { datasources } from "stores/backend" import { onMount } from "svelte"
import { notifications } from "@budibase/bbui" import ICONS from "../icons"
import { Input, Label, ModalContent, Modal, Context } from "@budibase/bbui" import api from "builderStore/api"
import TableIntegrationMenu from "../TableIntegrationMenu/index.svelte" import { IntegrationNames } from "constants"
import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte" import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte"
import analytics from "analytics" import DatasourceDetailsModal from "components/backend/DatasourceNavigator/modals/DatasourceDetailsModal.svelte"
import { getContext } from "svelte"
const modalContext = getContext(Context.Modal) export let modal
let integrations = []
let integration = {}
let internalTableModal
let externalDatasourceModal
let tableModal const INTERNAL = "BUDIBASE"
let name
let error = ""
let integration
$: checkOpenModal(integration && integration.type === "BUDIBASE") onMount(() => {
fetchIntegrations()
function checkValid(evt) {
const datasourceName = evt.target.value
if (
$datasources?.list.some(datasource => datasource.name === datasourceName)
) {
error = `Datasource with name ${datasourceName} already exists. Please choose another name.`
return
}
error = ""
}
function checkOpenModal(isInternal) {
if (isInternal) {
tableModal.show()
}
}
async function saveDatasource() {
const { type, plus, ...config } = integration
// Create datasource
const response = await datasources.save({
name,
source: type,
config,
plus,
}) })
notifications.success(`Datasource ${name} created successfully.`)
analytics.captureEvent("Datasource Created", { name, type })
// Navigate to new datasource function selectIntegration(integrationType) {
$goto(`./datasource/${response._id}`) const selected = integrations[integrationType]
// build the schema
const config = {}
for (let key of Object.keys(selected.datasource)) {
config[key] = selected.datasource[key].default
}
integration = {
type: integrationType,
plus: selected.plus,
config,
schema: selected.datasource,
}
}
function chooseNextModal() {
if (integration.type === INTERNAL) {
externalDatasourceModal.hide()
internalTableModal.show()
} else {
externalDatasourceModal.show()
}
}
async function fetchIntegrations() {
const response = await api.get("/api/integrations")
const json = await response.json()
integrations = {
[INTERNAL]: { datasource: {}, name: "INTERNAL/CSV" },
...json,
}
return json
} }
</script> </script>
<Modal bind:this={tableModal} on:hide={modalContext.hide}> <Modal bind:this={internalTableModal}>
<CreateTableModal bind:name /> <CreateTableModal />
</Modal> </Modal>
<ModalContent
title="Create Datasource" <Modal bind:this={externalDatasourceModal}>
size="L" <DatasourceDetailsModal {integration} />
confirmText="Create" </Modal>
onConfirm={saveDatasource}
disabled={error || !name || !integration?.type} <Modal bind:this={modal}>
> <ModalContent
<Input disabled={!Object.keys(integration).length}
data-cy="datasource-name-input" title="Add Data"
label="Datasource Name" confirmText="Continue"
on:input={checkValid} cancelText="Start from scratch"
bind:value={name} size="M"
{error} onConfirm={() => {
chooseNextModal()
}}
>
<Body size="XS"
>All apps need data. You can connect to a data source below, or add data
to your app using Budibase's built-in database - it's simple!
</Body>
<Layout noPadding>
<div
class:selected={integration.type === INTERNAL}
on:click={() => selectIntegration(INTERNAL)}
class="item hoverable"
>
<div class="item-body">
<svelte:component this={ICONS.BUDIBASE} height="18" width="18" />
<span class="icon-spacing">
<Body size="S">Budibase DB (no prior data required)</Body></span
>
</div>
</div>
<Label size="S">Connect to data source</Label>
<div class="item-list">
{#each Object.entries(integrations).filter(([key]) => key !== INTERNAL) as [integrationType, schema]}
<div
class:selected={integration.type === integrationType}
on:click={() => selectIntegration(integrationType)}
class="item hoverable"
>
<div class="item-body">
<svelte:component
this={ICONS[integrationType]}
height="18"
width="18"
/> />
<Label>Datasource Type</Label>
<TableIntegrationMenu bind:integration /> <span class="icon-spacing">
</ModalContent> <Body size="S"
>{schema.name || IntegrationNames[integrationType]}</Body
></span
>
</div>
</div>
{/each}
</div>
</Layout>
</ModalContent>
</Modal>
<style>
.icon-spacing {
margin-left: var(--spacing-m);
}
.item-body {
display: flex;
margin-left: var(--spacing-m);
}
.item-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
grid-gap: var(--spectrum-alias-grid-baseline);
}
.item {
cursor: pointer;
display: grid;
grid-gap: var(--spectrum-alias-grid-margin-xsmall);
padding: var(--spectrum-alias-item-padding-s);
background: var(--spectrum-alias-background-color-secondary);
transition: 0.3s all;
border: solid var(--spectrum-alias-border-color);
border-radius: 5px;
box-sizing: border-box;
border-width: 2px;
}
.selected {
background: var(--spectrum-alias-background-color-tertiary);
}
.item:hover,
.selected {
background: var(--spectrum-alias-background-color-tertiary);
}
</style>

View File

@ -0,0 +1,51 @@
<script>
import { ModalContent, notifications } from "@budibase/bbui"
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
import { datasources } from "stores/backend"
import { IntegrationNames } from "constants"
export let integration
function prepareData() {
let datasource = {}
let existingTypeCount = $datasources.list.filter(
ds => ds.type == integration.type
).length
let baseName = IntegrationNames[integration.type]
let name =
existingTypeCount == 0 ? baseName : `${baseName}-${existingTypeCount + 1}`
datasource.type = "datasource"
datasource.source = integration.type
datasource.config = integration.config
datasource.name = name
return datasource
}
async function saveDatasource() {
try {
// Create datasource
await datasources.save(prepareData())
notifications.success(`Datasource updated successfully.`)
} catch (err) {
notifications.error(`Error saving datasource: ${err}`)
}
}
</script>
<ModalContent
title="Add Data"
onConfirm={() => saveDatasource()}
confirmText="Continue"
cancelText="Start from scratch"
size="M"
>
<IntegrationConfigForm
schema={integration.schema}
bind:integration={integration.config}
/>
</ModalContent>
<style>
</style>

View File

@ -15,6 +15,20 @@ export const AppStatus = {
DEPLOYED: "published", DEPLOYED: "published",
} }
export const IntegrationNames = {
POSTGRES: "PostgreSQL",
MONGODB: "MongoDB",
COUCHDB: "CouchDB",
S3: "S3",
MYSQL: "MySQL",
REST: "REST",
DYNAMODB: "DynamoDB",
ELASTICSEARCH: "ElasticSearch",
SQL_SERVER: "SQL Server",
AIRTABLE: "Airtable",
ARANGODB: "ArangoDB",
}
// fields on the user table that cannot be edited // fields on the user table that cannot be edited
export const UNEDITABLE_USER_FIELDS = [ export const UNEDITABLE_USER_FIELDS = [
"email", "email",

View File

@ -1,12 +1,14 @@
<script> <script>
import { goto, params } from "@roxi/routify" import { goto, params } from "@roxi/routify"
import { Icon, Modal, Tabs, Tab } from "@budibase/bbui" import { Icon, Tabs, Tab } from "@budibase/bbui"
import { BUDIBASE_INTERNAL_DB } from "constants" import { BUDIBASE_INTERNAL_DB } from "constants"
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 CreateDatasourceModal from "components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte"
let selected = "Sources" let selected = "Sources"
let modal let modal
$: isExternal = $: isExternal =
$params.selectedDatasource && $params.selectedDatasource &&
$params.selectedDatasource !== BUDIBASE_INTERNAL_DB $params.selectedDatasource !== BUDIBASE_INTERNAL_DB
@ -23,9 +25,7 @@
<Tab title="Sources"> <Tab title="Sources">
<div class="tab-content-padding"> <div class="tab-content-padding">
<DatasourceNavigator /> <DatasourceNavigator />
<Modal bind:this={modal}> <CreateDatasourceModal bind:modal />
<CreateDatasourceModal />
</Modal>
</div> </div>
</Tab> </Tab>
</Tabs> </Tabs>

View File

@ -1,6 +1,13 @@
<script> <script>
import { goto } from "@roxi/routify" import CreateDatasourceModal from "components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte"
$goto("./table") import { datasources } from "stores/backend"
$: setupComplete =
$datasources.list.find(x => (x._id = "bb_internal")).entities.length > 1 ||
$datasources.list > 1
</script> </script>
{#if !setupComplete}
<CreateDatasourceModal />
{/if}
<!-- routify:options index=false --> <!-- routify:options index=false -->