forked from tsai/budibase
13 changed files with 413 additions and 257 deletions
@ -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) |
|||
} |
|||
@ -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> |
|||
Loading…
Reference in new issue