Updating REST creation, removing the modal step, updating the config page to move things around as per designs.
This commit is contained in:
parent
40aa9656e1
commit
3e5f9b9505
|
@ -0,0 +1,44 @@
|
|||
import { datasources, tables } from "../stores/backend"
|
||||
import { IntegrationNames } from "../constants"
|
||||
import analytics, { Events } from "../analytics"
|
||||
import { get } from "svelte/store"
|
||||
import cloneDeep from "lodash/cloneDeepWith"
|
||||
|
||||
function prepareData(config) {
|
||||
let datasource = {}
|
||||
let existingTypeCount = get(datasources).list.filter(
|
||||
ds => ds.source === config.type
|
||||
).length
|
||||
|
||||
let baseName = IntegrationNames[config.type]
|
||||
let name =
|
||||
existingTypeCount === 0 ? baseName : `${baseName}-${existingTypeCount + 1}`
|
||||
|
||||
datasource.type = "datasource"
|
||||
datasource.source = config.type
|
||||
datasource.config = config.config
|
||||
datasource.name = name
|
||||
datasource.plus = config.plus
|
||||
|
||||
return datasource
|
||||
}
|
||||
|
||||
export async function saveDatasource(config) {
|
||||
const datasource = prepareData(config)
|
||||
// Create datasource
|
||||
const resp = await datasources.save(datasource, datasource.plus)
|
||||
|
||||
// update the tables incase data source plus
|
||||
await tables.fetch()
|
||||
await datasources.select(resp._id)
|
||||
analytics.captureEvent(Events.DATASOURCE.CREATED, {
|
||||
name: resp.name,
|
||||
source: resp.source,
|
||||
})
|
||||
return resp
|
||||
}
|
||||
|
||||
export async function createRestDatasource(integration) {
|
||||
const config = cloneDeep(integration)
|
||||
return saveDatasource(config)
|
||||
}
|
|
@ -9,16 +9,40 @@
|
|||
} from "@budibase/bbui"
|
||||
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
|
||||
import { capitalise } from "helpers"
|
||||
import { IntegrationTypes } from "constants"
|
||||
|
||||
export let integration
|
||||
export let schema
|
||||
export let creating
|
||||
|
||||
function filter([key, value]) {
|
||||
if (!value) {
|
||||
return false
|
||||
}
|
||||
return !(
|
||||
(integration.source === IntegrationTypes.REST &&
|
||||
key === "defaultHeaders") ||
|
||||
value.deprecated
|
||||
)
|
||||
}
|
||||
|
||||
$: config = integration.config
|
||||
$: configKeys = Object.entries(schema || {})
|
||||
.filter(el => filter(el))
|
||||
.map(([key]) => key)
|
||||
|
||||
let addButton
|
||||
</script>
|
||||
|
||||
<form>
|
||||
<Layout gap="S">
|
||||
{#each Object.keys(schema) as configKey}
|
||||
{#if !creating}
|
||||
<div class="form-row">
|
||||
<Label>{capitalise("Name")}</Label>
|
||||
<Input on:change bind:value={integration.name} />
|
||||
</div>
|
||||
{/if}
|
||||
{#each configKeys as configKey}
|
||||
{#if schema[configKey].type === "object"}
|
||||
<div class="form-row ssl">
|
||||
<Label>{capitalise(configKey)}</Label>
|
||||
|
@ -29,13 +53,13 @@
|
|||
<KeyValueBuilder
|
||||
bind:this={addButton}
|
||||
defaults={schema[configKey].default}
|
||||
bind:object={integration[configKey]}
|
||||
bind:object={config[configKey]}
|
||||
noAddButton={true}
|
||||
/>
|
||||
{:else if schema[configKey].type === "boolean"}
|
||||
<div class="form-row">
|
||||
<Label>{capitalise(configKey)}</Label>
|
||||
<Toggle text="" bind:value={integration[configKey]} />
|
||||
<Toggle text="" bind:value={config[configKey]} />
|
||||
</div>
|
||||
{:else if schema[configKey].type === "longForm"}
|
||||
<div class="form-row">
|
||||
|
@ -43,7 +67,7 @@
|
|||
<TextArea
|
||||
type={schema[configKey].type}
|
||||
on:change
|
||||
bind:value={integration[configKey]}
|
||||
bind:value={config[configKey]}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
|
@ -52,7 +76,7 @@
|
|||
<Input
|
||||
type={schema[configKey].type}
|
||||
on:change
|
||||
bind:value={integration[configKey]}
|
||||
bind:value={config[configKey]}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -0,0 +1,249 @@
|
|||
<script>
|
||||
import {
|
||||
Button,
|
||||
Heading,
|
||||
Body,
|
||||
Divider,
|
||||
InlineAlert,
|
||||
ActionButton,
|
||||
notifications,
|
||||
Modal,
|
||||
} from "@budibase/bbui"
|
||||
import { datasources, integrations, tables } from "stores/backend"
|
||||
import CreateEditRelationship from "components/backend/Datasources/CreateEditRelationship.svelte"
|
||||
import CreateExternalTableModal from "./CreateExternalTableModal.svelte"
|
||||
import { goto } from "@roxi/routify"
|
||||
|
||||
export let datasource
|
||||
export let save
|
||||
|
||||
let relationshipModal
|
||||
let createExternalTableModal
|
||||
let selectedFromRelationship, selectedToRelationship
|
||||
|
||||
$: integration = datasource && $integrations[datasource.source]
|
||||
$: plusTables = datasource?.plus
|
||||
? Object.values(datasource?.entities || {})
|
||||
: []
|
||||
$: relationships = getRelationships(plusTables)
|
||||
$: schemaError = $datasources.schemaError
|
||||
|
||||
function getRelationships(tables) {
|
||||
if (!tables || !Array.isArray(tables)) {
|
||||
return {}
|
||||
}
|
||||
let pairs = {}
|
||||
for (let table of tables) {
|
||||
for (let column of Object.values(table.schema)) {
|
||||
if (column.type !== "link") {
|
||||
continue
|
||||
}
|
||||
// these relationships have an id to pair them to each other
|
||||
// one has a main for the from side
|
||||
const key = column.main ? "from" : "to"
|
||||
pairs[column._id] = {
|
||||
...pairs[column._id],
|
||||
[key]: column,
|
||||
}
|
||||
}
|
||||
}
|
||||
return pairs
|
||||
}
|
||||
|
||||
function buildRelationshipDisplayString(fromCol, toCol) {
|
||||
function getTableName(tableId) {
|
||||
if (!tableId || typeof tableId !== "string") {
|
||||
return null
|
||||
}
|
||||
return plusTables.find(table => table._id === tableId)?.name || "Unknown"
|
||||
}
|
||||
if (!toCol || !fromCol) {
|
||||
return "Cannot build name"
|
||||
}
|
||||
const fromTableName = getTableName(toCol.tableId)
|
||||
const toTableName = getTableName(fromCol.tableId)
|
||||
const throughTableName = getTableName(fromCol.through)
|
||||
|
||||
let displayString
|
||||
if (throughTableName) {
|
||||
displayString = `${fromTableName} through ${throughTableName} → ${toTableName}`
|
||||
} else {
|
||||
displayString = `${fromTableName} → ${toTableName}`
|
||||
}
|
||||
return displayString
|
||||
}
|
||||
|
||||
async function updateDatasourceSchema() {
|
||||
try {
|
||||
await datasources.updateSchema(datasource)
|
||||
notifications.success(`Datasource ${name} tables updated successfully.`)
|
||||
await tables.fetch()
|
||||
} catch (err) {
|
||||
notifications.error(`Error updating datasource schema: ${err}`)
|
||||
}
|
||||
}
|
||||
|
||||
function onClickTable(table) {
|
||||
tables.select(table)
|
||||
$goto(`../../table/${table._id}`)
|
||||
}
|
||||
|
||||
function openRelationshipModal(fromRelationship, toRelationship) {
|
||||
selectedFromRelationship = fromRelationship || {}
|
||||
selectedToRelationship = toRelationship || {}
|
||||
relationshipModal.show()
|
||||
}
|
||||
|
||||
function createNewTable() {
|
||||
createExternalTableModal.show()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:this={relationshipModal}>
|
||||
<CreateEditRelationship
|
||||
{datasource}
|
||||
{save}
|
||||
close={relationshipModal.hide}
|
||||
{plusTables}
|
||||
fromRelationship={selectedFromRelationship}
|
||||
toRelationship={selectedToRelationship}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<Modal bind:this={createExternalTableModal}>
|
||||
<CreateExternalTableModal {datasource} />
|
||||
</Modal>
|
||||
|
||||
<Divider />
|
||||
<div class="query-header">
|
||||
<Heading size="S">Tables</Heading>
|
||||
<div class="table-buttons">
|
||||
<div>
|
||||
<ActionButton
|
||||
size="S"
|
||||
quiet
|
||||
icon="DataRefresh"
|
||||
on:click={updateDatasourceSchema}
|
||||
>
|
||||
Fetch tables from database
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Body>
|
||||
This datasource can determine tables automatically. Budibase can fetch your
|
||||
tables directly from the database and you can use them without having to write
|
||||
any queries at all.
|
||||
</Body>
|
||||
{#if schemaError}
|
||||
<InlineAlert
|
||||
type="error"
|
||||
header="Error fetching tables"
|
||||
message={schemaError}
|
||||
onConfirm={datasources.removeSchemaError}
|
||||
/>
|
||||
{/if}
|
||||
<div class="query-list">
|
||||
{#each plusTables as table}
|
||||
<div class="query-list-item" on:click={() => onClickTable(table)}>
|
||||
<p class="query-name">{table.name}</p>
|
||||
<p>Primary Key: {table.primary}</p>
|
||||
<p>→</p>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="add-table">
|
||||
<Button cta on:click={createNewTable}>Create new table</Button>
|
||||
</div>
|
||||
</div>
|
||||
{#if plusTables?.length !== 0}
|
||||
<Divider />
|
||||
<div class="query-header">
|
||||
<Heading size="S">Relationships</Heading>
|
||||
<ActionButton
|
||||
icon="DataCorrelated"
|
||||
primary
|
||||
size="S"
|
||||
quiet
|
||||
on:click={openRelationshipModal}
|
||||
>
|
||||
Define existing relationship
|
||||
</ActionButton>
|
||||
</div>
|
||||
<Body>
|
||||
Tell budibase how your tables are related to get even more smart features.
|
||||
</Body>
|
||||
{/if}
|
||||
<div class="query-list">
|
||||
{#each Object.values(relationships) as relationship}
|
||||
<div
|
||||
class="query-list-item"
|
||||
on:click={() => openRelationshipModal(relationship.from, relationship.to)}
|
||||
>
|
||||
<p class="query-name">
|
||||
{buildRelationshipDisplayString(relationship.from, relationship.to)}
|
||||
</p>
|
||||
<p>{relationship.from?.name} to {relationship.to?.name}</p>
|
||||
<p>→</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.query-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 0 0 var(--spacing-s) 0;
|
||||
}
|
||||
|
||||
.query-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.query-list-item {
|
||||
border-radius: var(--border-radius-m);
|
||||
background: var(--background);
|
||||
border: var(--border-dark);
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 0.75fr 20px;
|
||||
align-items: center;
|
||||
padding-left: var(--spacing-m);
|
||||
padding-right: var(--spacing-m);
|
||||
gap: var(--layout-xs);
|
||||
transition: 200ms background ease;
|
||||
}
|
||||
|
||||
.query-list-item:hover {
|
||||
background: var(--grey-1);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--grey-8);
|
||||
}
|
||||
|
||||
.query-name {
|
||||
color: var(--ink);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
|
||||
.table-buttons {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.table-buttons div {
|
||||
grid-column-end: -1;
|
||||
}
|
||||
|
||||
.add-table {
|
||||
margin-top: var(--spacing-m);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,29 @@
|
|||
<script>
|
||||
import { Divider, Heading, Button } from "@budibase/bbui"
|
||||
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
|
||||
|
||||
export let datasource
|
||||
|
||||
let addHeader
|
||||
</script>
|
||||
|
||||
<Divider />
|
||||
<div class="query-header">
|
||||
<Heading size="S">Headers</Heading>
|
||||
<Button secondary on:click={() => addHeader.addEntry()}>Add Header</Button>
|
||||
</div>
|
||||
<KeyValueBuilder
|
||||
bind:this={addHeader}
|
||||
bind:object={datasource.config.defaultHeaders}
|
||||
noAddButton={true}
|
||||
/>
|
||||
|
||||
<style>
|
||||
.query-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 0 0 var(--spacing-s) 0;
|
||||
}
|
||||
</style>
|
|
@ -3,9 +3,11 @@
|
|||
import { onMount } from "svelte"
|
||||
import ICONS from "../icons"
|
||||
import api from "builderStore/api"
|
||||
import { IntegrationNames } from "constants"
|
||||
import { IntegrationNames, IntegrationTypes } from "constants"
|
||||
import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte"
|
||||
import DatasourceConfigModal from "components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte"
|
||||
import { createRestDatasource } from "builderStore/datasource"
|
||||
import { goto } from "@roxi/routify"
|
||||
|
||||
export let modal
|
||||
let integrations = []
|
||||
|
@ -13,8 +15,6 @@
|
|||
let internalTableModal
|
||||
let externalDatasourceModal
|
||||
|
||||
const INTERNAL = "BUDIBASE"
|
||||
|
||||
onMount(() => {
|
||||
fetchIntegrations()
|
||||
})
|
||||
|
@ -35,10 +35,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
function chooseNextModal() {
|
||||
if (integration.type === INTERNAL) {
|
||||
async function chooseNextModal() {
|
||||
if (integration.type === IntegrationTypes.INTERNAL) {
|
||||
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}`)
|
||||
} else {
|
||||
externalDatasourceModal.show()
|
||||
}
|
||||
|
@ -48,7 +52,7 @@
|
|||
const response = await api.get("/api/integrations")
|
||||
const json = await response.json()
|
||||
integrations = {
|
||||
[INTERNAL]: { datasource: {}, name: "INTERNAL/CSV" },
|
||||
[IntegrationTypes.INTERNAL]: { datasource: {}, name: "INTERNAL/CSV" },
|
||||
...json,
|
||||
}
|
||||
return json
|
||||
|
@ -80,8 +84,8 @@
|
|||
to your app using Budibase's built-in database.
|
||||
</Body>
|
||||
<div
|
||||
class:selected={integration.type === INTERNAL}
|
||||
on:click={() => selectIntegration(INTERNAL)}
|
||||
class:selected={integration.type === IntegrationTypes.INTERNAL}
|
||||
on:click={() => selectIntegration(IntegrationTypes.INTERNAL)}
|
||||
class="item hoverable"
|
||||
>
|
||||
<div class="item-body">
|
||||
|
@ -96,7 +100,7 @@
|
|||
<Detail size="S">Connect to data source</Detail>
|
||||
</div>
|
||||
<div class="item-list">
|
||||
{#each Object.entries(integrations).filter(([key]) => key !== INTERNAL) as [integrationType, schema]}
|
||||
{#each Object.entries(integrations).filter(([key]) => key !== IntegrationTypes.INTERNAL) as [integrationType, schema]}
|
||||
<div
|
||||
class:selected={integration.type === integrationType}
|
||||
on:click={() => selectIntegration(integrationType)}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<script>
|
||||
import { goto } from "@roxi/routify"
|
||||
import { ModalContent, notifications, Body, Layout } from "@budibase/bbui"
|
||||
import analytics, { Events } from "analytics"
|
||||
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
|
||||
import { datasources, tables } from "stores/backend"
|
||||
import { IntegrationNames } from "constants"
|
||||
import cloneDeep from "lodash/cloneDeepWith"
|
||||
import { saveDatasource as save } from "builderStore/datasource"
|
||||
|
||||
export let integration
|
||||
export let modal
|
||||
|
@ -13,45 +12,13 @@
|
|||
// kill the reference so the input isn't saved
|
||||
let config = cloneDeep(integration)
|
||||
|
||||
function prepareData() {
|
||||
let datasource = {}
|
||||
let existingTypeCount = $datasources.list.filter(
|
||||
ds => ds.source == config.type
|
||||
).length
|
||||
|
||||
let baseName = IntegrationNames[config.type]
|
||||
let name =
|
||||
existingTypeCount === 0
|
||||
? baseName
|
||||
: `${baseName}-${existingTypeCount + 1}`
|
||||
|
||||
datasource.type = "datasource"
|
||||
datasource.source = config.type
|
||||
datasource.config = config.config
|
||||
datasource.name = name
|
||||
datasource.plus = config.plus
|
||||
|
||||
return datasource
|
||||
}
|
||||
async function saveDatasource() {
|
||||
const datasource = prepareData()
|
||||
try {
|
||||
// Create datasource
|
||||
const resp = await datasources.save(datasource, datasource.plus)
|
||||
|
||||
// update the tables incase data source plus
|
||||
await tables.fetch()
|
||||
await datasources.select(resp._id)
|
||||
const resp = await save(config)
|
||||
$goto(`./datasource/${resp._id}`)
|
||||
notifications.success(`Datasource updated successfully.`)
|
||||
analytics.captureEvent(Events.DATASOURCE.CREATED, {
|
||||
name: resp.name,
|
||||
source: resp.source,
|
||||
})
|
||||
return true
|
||||
} catch (err) {
|
||||
notifications.error(`Error saving datasource: ${err}`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -72,7 +39,11 @@
|
|||
</Body>
|
||||
</Layout>
|
||||
|
||||
<IntegrationConfigForm schema={config.schema} integration={config.config} />
|
||||
<IntegrationConfigForm
|
||||
schema={config.schema}
|
||||
integration={config}
|
||||
creating={true}
|
||||
/>
|
||||
</ModalContent>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
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)
|
||||
const entities = Object.values(datasource?.entities || {})
|
||||
if (
|
||||
wasSelectedSource === datasource._id ||
|
||||
(entities &&
|
||||
|
|
|
@ -15,19 +15,36 @@ export const AppStatus = {
|
|||
DEPLOYED: "published",
|
||||
}
|
||||
|
||||
export const IntegrationNames = {
|
||||
POSTGRES: "PostgreSQL",
|
||||
MONGODB: "MongoDB",
|
||||
COUCHDB: "CouchDB",
|
||||
export const IntegrationTypes = {
|
||||
POSTGRES: "POSTGRES",
|
||||
MONGODB: "MONGODB",
|
||||
COUCHDB: "COUCHDB",
|
||||
S3: "S3",
|
||||
MYSQL: "MySQL",
|
||||
MYSQL: "MYSQL",
|
||||
REST: "REST",
|
||||
DYNAMODB: "DynamoDB",
|
||||
ELASTICSEARCH: "ElasticSearch",
|
||||
SQL_SERVER: "SQL Server",
|
||||
AIRTABLE: "Airtable",
|
||||
ARANGODB: "ArangoDB",
|
||||
ORACLE: "Oracle",
|
||||
DYNAMODB: "DYNAMODB",
|
||||
ELASTICSEARCH: "ELASTICSEARCH",
|
||||
SQL_SERVER: "SQL_SERVER",
|
||||
AIRTABLE: "AIRTABLE",
|
||||
ARANGODB: "ARANGODB",
|
||||
ORACLE: "ORACLE",
|
||||
INTERNAL: "INTERNAL",
|
||||
}
|
||||
|
||||
export const IntegrationNames = {
|
||||
[IntegrationTypes.POSTGRES]: "PostgreSQL",
|
||||
[IntegrationTypes.MONGODB]: "MongoDB",
|
||||
[IntegrationTypes.COUCHDB]: "CouchDB",
|
||||
[IntegrationTypes.S3]: "S3",
|
||||
[IntegrationTypes.MYSQL]: "MySQL",
|
||||
[IntegrationTypes.REST]: "REST",
|
||||
[IntegrationTypes.DYNAMODB]: "DynamoDB",
|
||||
[IntegrationTypes.ELASTICSEARCH]: "ElasticSearch",
|
||||
[IntegrationTypes.SQL_SERVER]: "SQL Server",
|
||||
[IntegrationTypes.AIRTABLE]: "Airtable",
|
||||
[IntegrationTypes.ARANGODB]: "ArangoDB",
|
||||
[IntegrationTypes.ORACLE]: "Oracle",
|
||||
[IntegrationTypes.INTERNAL]: "Internal",
|
||||
}
|
||||
|
||||
// fields on the user table that cannot be edited
|
||||
|
|
|
@ -6,74 +6,18 @@
|
|||
Body,
|
||||
Divider,
|
||||
Layout,
|
||||
Modal,
|
||||
InlineAlert,
|
||||
ActionButton,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { datasources, integrations, queries, tables } from "stores/backend"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
|
||||
import CreateEditRelationship from "components/backend/Datasources/CreateEditRelationship.svelte"
|
||||
import CreateExternalTableModal from "./modals/CreateExternalTableModal.svelte"
|
||||
import RestExtraConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/RestExtraConfigForm.svelte"
|
||||
import PlusConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte"
|
||||
import ICONS from "components/backend/DatasourceNavigator/icons"
|
||||
import { IntegrationTypes } from "constants"
|
||||
import { capitalise } from "helpers"
|
||||
|
||||
let relationshipModal
|
||||
let createExternalTableModal
|
||||
let selectedFromRelationship, selectedToRelationship
|
||||
|
||||
$: datasource = $datasources.list.find(ds => ds._id === $datasources.selected)
|
||||
$: integration = datasource && $integrations[datasource.source]
|
||||
$: plusTables = datasource?.plus
|
||||
? Object.values(datasource.entities || {})
|
||||
: []
|
||||
$: relationships = getRelationships(plusTables)
|
||||
$: schemaError = $datasources.schemaError
|
||||
|
||||
function getRelationships(tables) {
|
||||
if (!tables || !Array.isArray(tables)) {
|
||||
return {}
|
||||
}
|
||||
let pairs = {}
|
||||
for (let table of tables) {
|
||||
for (let column of Object.values(table.schema)) {
|
||||
if (column.type !== "link") {
|
||||
continue
|
||||
}
|
||||
// these relationships have an id to pair them to each other
|
||||
// one has a main for the from side
|
||||
const key = column.main ? "from" : "to"
|
||||
pairs[column._id] = {
|
||||
...pairs[column._id],
|
||||
[key]: column,
|
||||
}
|
||||
}
|
||||
}
|
||||
return pairs
|
||||
}
|
||||
|
||||
function buildRelationshipDisplayString(fromCol, toCol) {
|
||||
function getTableName(tableId) {
|
||||
if (!tableId || typeof tableId !== "string") {
|
||||
return null
|
||||
}
|
||||
return plusTables.find(table => table._id === tableId)?.name || "Unknown"
|
||||
}
|
||||
if (!toCol || !fromCol) {
|
||||
return "Cannot build name"
|
||||
}
|
||||
const fromTableName = getTableName(toCol.tableId)
|
||||
const toTableName = getTableName(fromCol.tableId)
|
||||
const throughTableName = getTableName(fromCol.through)
|
||||
|
||||
let displayString
|
||||
if (throughTableName) {
|
||||
displayString = `${fromTableName} through ${throughTableName} → ${toTableName}`
|
||||
} else {
|
||||
displayString = `${fromTableName} → ${toTableName}`
|
||||
}
|
||||
return displayString
|
||||
}
|
||||
|
||||
async function saveDatasource() {
|
||||
try {
|
||||
|
@ -89,52 +33,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function updateDatasourceSchema() {
|
||||
try {
|
||||
await datasources.updateSchema(datasource)
|
||||
notifications.success(`Datasource ${name} tables updated successfully.`)
|
||||
await tables.fetch()
|
||||
} catch (err) {
|
||||
notifications.error(`Error updating datasource schema: ${err}`)
|
||||
}
|
||||
}
|
||||
|
||||
function onClickQuery(query) {
|
||||
queries.select(query)
|
||||
$goto(`./${query._id}`)
|
||||
}
|
||||
|
||||
function onClickTable(table) {
|
||||
tables.select(table)
|
||||
$goto(`../../table/${table._id}`)
|
||||
}
|
||||
|
||||
function openRelationshipModal(fromRelationship, toRelationship) {
|
||||
selectedFromRelationship = fromRelationship || {}
|
||||
selectedToRelationship = toRelationship || {}
|
||||
relationshipModal.show()
|
||||
}
|
||||
|
||||
function createNewTable() {
|
||||
createExternalTableModal.show()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:this={relationshipModal}>
|
||||
<CreateEditRelationship
|
||||
{datasource}
|
||||
save={saveDatasource}
|
||||
close={relationshipModal.hide}
|
||||
{plusTables}
|
||||
fromRelationship={selectedFromRelationship}
|
||||
toRelationship={selectedToRelationship}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<Modal bind:this={createExternalTableModal}>
|
||||
<CreateExternalTableModal {datasource} />
|
||||
</Modal>
|
||||
|
||||
{#if datasource && integration}
|
||||
<section>
|
||||
<Layout>
|
||||
|
@ -156,92 +60,15 @@
|
|||
<Button secondary on:click={saveDatasource}>Save</Button>
|
||||
</div>
|
||||
<Body size="S">
|
||||
Connect your database to Budibase using the config below.
|
||||
Connect your data source to Budibase using the config below.
|
||||
</Body>
|
||||
<IntegrationConfigForm
|
||||
schema={integration.datasource}
|
||||
integration={datasource.config}
|
||||
integration={datasource}
|
||||
/>
|
||||
</div>
|
||||
{#if datasource.plus}
|
||||
<Divider />
|
||||
<div class="query-header">
|
||||
<Heading size="S">Tables</Heading>
|
||||
<div class="table-buttons">
|
||||
<div>
|
||||
<ActionButton
|
||||
size="S"
|
||||
quiet
|
||||
icon="DataRefresh"
|
||||
on:click={updateDatasourceSchema}
|
||||
>
|
||||
Fetch tables from database
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Body>
|
||||
This datasource can determine tables automatically. Budibase can fetch
|
||||
your tables directly from the database and you can use them without
|
||||
having to write any queries at all.
|
||||
</Body>
|
||||
{#if schemaError}
|
||||
<InlineAlert
|
||||
type="error"
|
||||
header="Error fetching tables"
|
||||
message={schemaError}
|
||||
onConfirm={datasources.removeSchemaError}
|
||||
/>
|
||||
{/if}
|
||||
<div class="query-list">
|
||||
{#each plusTables as table}
|
||||
<div class="query-list-item" on:click={() => onClickTable(table)}>
|
||||
<p class="query-name">{table.name}</p>
|
||||
<p>Primary Key: {table.primary}</p>
|
||||
<p>→</p>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="add-table">
|
||||
<Button cta on:click={createNewTable}>Create new table</Button>
|
||||
</div>
|
||||
</div>
|
||||
{#if plusTables?.length !== 0}
|
||||
<Divider />
|
||||
<div class="query-header">
|
||||
<Heading size="S">Relationships</Heading>
|
||||
<ActionButton
|
||||
icon="DataCorrelated"
|
||||
primary
|
||||
size="S"
|
||||
quiet
|
||||
on:click={openRelationshipModal}
|
||||
>
|
||||
Define existing relationship
|
||||
</ActionButton>
|
||||
</div>
|
||||
<Body>
|
||||
Tell budibase how your tables are related to get even more smart
|
||||
features.
|
||||
</Body>
|
||||
{/if}
|
||||
<div class="query-list">
|
||||
{#each Object.values(relationships) as relationship}
|
||||
<div
|
||||
class="query-list-item"
|
||||
on:click={() =>
|
||||
openRelationshipModal(relationship.from, relationship.to)}
|
||||
>
|
||||
<p class="query-name">
|
||||
{buildRelationshipDisplayString(
|
||||
relationship.from,
|
||||
relationship.to
|
||||
)}
|
||||
</p>
|
||||
<p>{relationship.from?.name} to {relationship.to?.name}</p>
|
||||
<p>→</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<PlusConfigForm bind:datasource save={saveDatasource} />
|
||||
{/if}
|
||||
<Divider />
|
||||
<div class="query-header">
|
||||
|
@ -257,6 +84,9 @@
|
|||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if datasource?.source === IntegrationTypes.REST}
|
||||
<RestExtraConfigForm bind:datasource />
|
||||
{/if}
|
||||
</Layout>
|
||||
</section>
|
||||
{/if}
|
||||
|
@ -329,17 +159,4 @@
|
|||
text-overflow: ellipsis;
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
|
||||
.table-buttons {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.table-buttons div {
|
||||
grid-column-end: -1;
|
||||
}
|
||||
|
||||
.add-table {
|
||||
margin-top: var(--spacing-m);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -23,8 +23,9 @@ module RestModule {
|
|||
datasource: {
|
||||
url: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
default: "localhost",
|
||||
required: true,
|
||||
default: "",
|
||||
required: false,
|
||||
deprecated: true,
|
||||
},
|
||||
defaultHeaders: {
|
||||
type: DatasourceFieldTypes.OBJECT,
|
||||
|
|
Loading…
Reference in New Issue