Implementing UI to support the backend put in place.
This commit is contained in:
parent
3eb8f3c445
commit
10730d8053
|
@ -10,9 +10,10 @@
|
|||
|
||||
export let sourceId
|
||||
|
||||
|
||||
$: selectedView = $views.selected && $views.selected.name
|
||||
$: sortedTables = $tables.list.filter(table => table.sourceId === sourceId).sort(alphabetical)
|
||||
$: sortedTables = $tables.list
|
||||
.filter(table => table.sourceId === sourceId)
|
||||
.sort(alphabetical)
|
||||
|
||||
function selectTable(table) {
|
||||
tables.select(table)
|
||||
|
|
|
@ -1,25 +1,31 @@
|
|||
<script>
|
||||
import { RelationshipTypes } from "constants/backend"
|
||||
import { Menu, MenuItem, MenuSection, Button, Input, Icon, ModalContent, RadioGroup, Heading, Select } from "@budibase/bbui"
|
||||
import { Button, Input, ModalContent, Select } from "@budibase/bbui"
|
||||
import { tables } from "stores/backend"
|
||||
import { uuid } from "builderStore/uuid"
|
||||
|
||||
export let save
|
||||
export let datasource
|
||||
export let from
|
||||
export let plusTables
|
||||
export let relationship = {}
|
||||
export let plusTables = []
|
||||
export let fromRelationship = {}
|
||||
export let toRelationship = {}
|
||||
export let close
|
||||
|
||||
let originalName = relationship.name
|
||||
let originalFromName = fromRelationship.name, originalToName = toRelationship.name
|
||||
|
||||
function isValid(relationship) {
|
||||
if (relationship.relationshipType === RelationshipTypes.MANY_TO_MANY && !relationship.through) {
|
||||
return false
|
||||
}
|
||||
return relationship.name && relationship.tableId && relationship.relationshipType
|
||||
}
|
||||
|
||||
$: tableOptions = plusTables.map(table => ({ label: table.name, value: table._id }))
|
||||
$: valid = relationship.name && relationship.tableId && relationship.relationshipType
|
||||
$: from = plusTables.find(table => table._id === relationship.source)
|
||||
$: to = plusTables.find(table => table._id === relationship.tableId)
|
||||
$: through = plusTables.find(table => table._id === relationship.through)
|
||||
$: linkTable = through || to
|
||||
|
||||
|
||||
$: fromTable = plusTables.find(table => table._id === toRelationship?.tableId)
|
||||
$: toTable = plusTables.find(table => table._id === fromRelationship?.tableId)
|
||||
$: through = plusTables.find(table => table._id === fromRelationship?.through)
|
||||
$: valid = toTable && fromTable && isValid(fromRelationship)
|
||||
$: linkTable = through || toTable
|
||||
$: relationshipTypes = [
|
||||
{
|
||||
label: "Many",
|
||||
|
@ -27,53 +33,89 @@
|
|||
},
|
||||
{
|
||||
label: "One",
|
||||
value: RelationshipTypes.ONE_TO_MANY,
|
||||
value: RelationshipTypes.MANY_TO_ONE,
|
||||
}
|
||||
]
|
||||
$: updateRelationshipType(fromRelationship?.relationshipType)
|
||||
|
||||
function onChangeRelationshipType(evt) {
|
||||
if (evt.detail === RelationshipTypes.ONE_TO_MANY) {
|
||||
relationship.through = null
|
||||
function updateRelationshipType(fromType) {
|
||||
if (fromType === RelationshipTypes.MANY_TO_MANY) {
|
||||
toRelationship.relationshipType = RelationshipTypes.MANY_TO_MANY
|
||||
} else {
|
||||
toRelationship.relationshipType = RelationshipTypes.MANY_TO_ONE
|
||||
}
|
||||
}
|
||||
|
||||
function buildRelationships() {
|
||||
// if any to many only need to check from
|
||||
const manyToMany = fromRelationship.relationshipType === RelationshipTypes.MANY_TO_MANY
|
||||
// main is simply used to know this is the side the user configured it from
|
||||
const id = uuid()
|
||||
let relateFrom = {
|
||||
...fromRelationship,
|
||||
type: "link",
|
||||
main: true,
|
||||
_id: id,
|
||||
}
|
||||
let relateTo = {
|
||||
...toRelationship,
|
||||
type: "link",
|
||||
_id: id,
|
||||
}
|
||||
|
||||
// [0] is because we don't support composite keys for relationships right now
|
||||
if (manyToMany) {
|
||||
relateFrom = {
|
||||
...relateFrom,
|
||||
through: through._id,
|
||||
fieldName: toTable.primary[0],
|
||||
}
|
||||
relateTo = {
|
||||
...relateTo,
|
||||
through: through._id,
|
||||
fieldName: fromTable.primary[0],
|
||||
}
|
||||
} else {
|
||||
relateFrom = {
|
||||
...relateFrom,
|
||||
foreignKey: relateFrom.fieldName,
|
||||
fieldName: fromTable.primary[0],
|
||||
}
|
||||
relateTo = {
|
||||
...relateTo,
|
||||
relationshipType: RelationshipTypes.ONE_TO_MANY,
|
||||
foreignKey: relateFrom.fieldName,
|
||||
fieldName: fromTable.primary[0],
|
||||
}
|
||||
}
|
||||
|
||||
fromRelationship = relateFrom
|
||||
toRelationship = relateTo
|
||||
}
|
||||
|
||||
// save the relationship on to the datasource
|
||||
async function saveRelationship() {
|
||||
const manyToMany = relationship.relationshipType === RelationshipTypes.MANY_TO_MANY
|
||||
buildRelationships()
|
||||
// source of relationship
|
||||
datasource.entities[from.name].schema[relationship.name] = {
|
||||
type: "link",
|
||||
foreignKey: relationship.fieldName,
|
||||
...relationship
|
||||
}
|
||||
datasource.entities[fromTable.name].schema[fromRelationship.name] = fromRelationship
|
||||
// save other side of relationship in the other schema
|
||||
datasource.entities[to.name].schema[relationship.name] = {
|
||||
name: relationship.name,
|
||||
type: "link",
|
||||
relationshipType: manyToMany ? RelationshipTypes.MANY_TO_MANY : RelationshipTypes.MANY_TO_ONE,
|
||||
tableId: from._id,
|
||||
fieldName: relationship.fieldName,
|
||||
foreignKey: relationship.fieldName
|
||||
}
|
||||
datasource.entities[toTable.name].schema[toRelationship.name] = toRelationship
|
||||
|
||||
// If relationship has been renamed
|
||||
if (originalName !== relationship.name) {
|
||||
delete datasource.entities[from.name].schema[originalName]
|
||||
delete datasource.entities[to.name].schema[originalName]
|
||||
if (originalFromName !== fromRelationship.name) {
|
||||
delete datasource.entities[fromTable.name].schema[originalFromName]
|
||||
}
|
||||
if (originalToName !== toRelationship.name) {
|
||||
delete datasource.entities[toTable.name].schema[originalToName]
|
||||
}
|
||||
|
||||
console.log({
|
||||
from: datasource.entities[from.name].schema[relationship.name],
|
||||
to: datasource.entities[to.name].schema[relationship.name],
|
||||
})
|
||||
|
||||
await save()
|
||||
await tables.fetch()
|
||||
}
|
||||
|
||||
async function deleteRelationship() {
|
||||
delete datasource.entities[from.name].schema[relationship.name]
|
||||
delete datasource.entities[to.name].schema[relationship.name]
|
||||
delete datasource.entities[fromTable.name].schema[fromRelationship.name]
|
||||
delete datasource.entities[toTable.name].schema[toRelationship.name]
|
||||
await save()
|
||||
await tables.fetch()
|
||||
close()
|
||||
|
@ -87,57 +129,54 @@
|
|||
onConfirm={saveRelationship}
|
||||
disabled={!valid}
|
||||
>
|
||||
<Input label="Relationship Name" bind:value={relationship.name} />
|
||||
<div class="relationship-names">
|
||||
<div class="left-name">
|
||||
<Input label="From name" bind:value={fromRelationship.name} />
|
||||
</div>
|
||||
<div class="right-name">
|
||||
<Input label="To name" bind:value={toRelationship.name} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-selector">
|
||||
<Select
|
||||
label="Relationship"
|
||||
options={relationshipTypes}
|
||||
bind:value={relationship.relationshipType}
|
||||
bind:value={fromRelationship.relationshipType}
|
||||
/>
|
||||
|
||||
<Select
|
||||
label="From"
|
||||
options={tableOptions}
|
||||
bind:value={relationship.source}
|
||||
bind:value={toRelationship.tableId}
|
||||
/>
|
||||
|
||||
<Select
|
||||
label={"Has many"}
|
||||
options={tableOptions}
|
||||
bind:value={relationship.tableId}
|
||||
bind:value={fromRelationship.tableId}
|
||||
/>
|
||||
|
||||
{#if relationship?.relationshipType === RelationshipTypes.MANY_TO_MANY}
|
||||
{#if fromRelationship?.relationshipType === RelationshipTypes.MANY_TO_MANY}
|
||||
<Select
|
||||
label={"Through"}
|
||||
options={tableOptions}
|
||||
bind:value={relationship.through}
|
||||
bind:value={fromRelationship.through}
|
||||
/>
|
||||
|
||||
{:else if toTable}
|
||||
<Select
|
||||
label={"Key"}
|
||||
options={Object.keys(through.schema || {})}
|
||||
bind:value={relationship.fieldName}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if relationship?.relationshipType === RelationshipTypes.ONE_TO_MANY && to}
|
||||
<Select
|
||||
label={`Foreign Key (${to.name})`}
|
||||
options={Object.keys(to.schema)}
|
||||
bind:value={relationship.fieldName}
|
||||
label={`Foreign Key (${toTable?.name})`}
|
||||
options={Object.keys(toTable?.schema)}
|
||||
bind:value={fromRelationship.fieldName}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div slot="footer">
|
||||
{#if originalName !== null}
|
||||
{#if originalFromName !== null}
|
||||
<Button warning text on:click={deleteRelationship}>Delete</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
</ModalContent>
|
||||
|
||||
<style>
|
||||
|
@ -146,4 +185,14 @@
|
|||
grid-template-columns: repeat(5, 1fr);
|
||||
grid-gap: var(--spacing-xl);
|
||||
}
|
||||
.relationship-names {
|
||||
display: grid;
|
||||
grid-gap: var(--spacing-xl);
|
||||
}
|
||||
.left-name {
|
||||
grid-column: 1;
|
||||
}
|
||||
.right-name {
|
||||
grid-column: 2;
|
||||
}
|
||||
</style>
|
|
@ -2,31 +2,71 @@
|
|||
import { goto, beforeUrlChange } from "@roxi/routify"
|
||||
import { Button, Heading, Body, Divider, Layout, Modal } from "@budibase/bbui"
|
||||
import { datasources, integrations, queries, tables } from "stores/backend"
|
||||
import { RelationshipTypes } from "constants/backend"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
|
||||
import CreateEditRelationship from "./CreateEditRelationship/CreateEditRelationship.svelte"
|
||||
import DisplayColumnModal from "./modals/EditDisplayColumnsModal.svelte"
|
||||
import ICONS from "components/backend/DatasourceNavigator/icons"
|
||||
import { capitalise } from "helpers"
|
||||
|
||||
let unsaved = false
|
||||
let relationshipModal
|
||||
let selectedRelationship
|
||||
let displayColumnModal
|
||||
let selectedFromRelationship, selectedToRelationship
|
||||
|
||||
$: datasource = $datasources.list.find(ds => ds._id === $datasources.selected)
|
||||
$: integration = datasource && $integrations[datasource.source]
|
||||
$: plusTables = datasource?.plus ? Object.values(datasource.entities || {}) : []
|
||||
$: plusTables = datasource?.plus
|
||||
? Object.values(datasource.entities || {})
|
||||
: []
|
||||
$: relationships = getRelationships(plusTables)
|
||||
|
||||
function buildRelationshipDisplayString(fromTable, toTable) {
|
||||
let displayString = fromTable.name
|
||||
const toTableName = toTable.tableId?.split("_").pop()
|
||||
|
||||
displayString += `→ ${toTableName} (${toTable.relationshipType})`
|
||||
|
||||
if (toTable.through) {
|
||||
// TODO: Through stuff
|
||||
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
|
||||
}
|
||||
console.log(`table - ${table.name} - ${column.name} - id: ${column._id} - ${column.main}`)
|
||||
// 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)
|
||||
console.log(throughTableName)
|
||||
|
||||
let displayFrom = `${fromTableName} (${fromCol.name})`
|
||||
let displayTo = `${toTableName} (${toCol.name})`
|
||||
let displayString
|
||||
if (throughTableName) {
|
||||
displayString = `${displayFrom} through ${throughTableName} → ${displayTo}`
|
||||
} else {
|
||||
displayString = `${displayFrom} → ${displayTo}`
|
||||
}
|
||||
return displayString
|
||||
}
|
||||
|
||||
|
@ -66,11 +106,16 @@
|
|||
unsaved = true
|
||||
}
|
||||
|
||||
function openRelationshipModal(relationship) {
|
||||
selectedRelationship = relationship || {}
|
||||
function openRelationshipModal(fromRelationship, toRelationship) {
|
||||
selectedFromRelationship = fromRelationship || {}
|
||||
selectedToRelationship = toRelationship || {}
|
||||
relationshipModal.show()
|
||||
}
|
||||
|
||||
function openDisplayColumnModal() {
|
||||
displayColumnModal.show()
|
||||
}
|
||||
|
||||
$beforeUrlChange(() => {
|
||||
if (unsaved) {
|
||||
notifications.error(
|
||||
|
@ -83,7 +128,18 @@
|
|||
</script>
|
||||
|
||||
<Modal bind:this={relationshipModal}>
|
||||
<CreateEditRelationship {datasource} save={saveDatasource} close={relationshipModal.hide} {plusTables} relationship={selectedRelationship} />
|
||||
<CreateEditRelationship
|
||||
{datasource}
|
||||
save={saveDatasource}
|
||||
close={relationshipModal.hide}
|
||||
{plusTables}
|
||||
fromRelationship={selectedFromRelationship}
|
||||
toRelationship={selectedToRelationship}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<Modal bind:this={displayColumnModal}>
|
||||
<DisplayColumnModal {datasource} {plusTables} save={saveDatasource} />
|
||||
</Modal>
|
||||
|
||||
{#if datasource && integration}
|
||||
|
@ -119,10 +175,17 @@
|
|||
<Divider />
|
||||
<div class="query-header">
|
||||
<Heading size="S">Tables</Heading>
|
||||
<div class="table-buttons">
|
||||
{#if plusTables && plusTables.length !== 0}
|
||||
<Button primary on:click={openDisplayColumnModal}>
|
||||
Update display columns
|
||||
</Button>
|
||||
{/if}
|
||||
<Button primary on:click={updateDatasourceSchema}
|
||||
>Fetch Tables From Database</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<Body>
|
||||
This datasource can determine tables automatically. Budibase can fetch
|
||||
your tables directly from the database and you can use them without
|
||||
|
@ -130,10 +193,7 @@
|
|||
</Body>
|
||||
<div class="query-list">
|
||||
{#each plusTables as table}
|
||||
<div
|
||||
class="query-list-item"
|
||||
on:click={() => onClickTable(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>
|
||||
|
@ -144,27 +204,32 @@
|
|||
<Divider />
|
||||
<div class="query-header">
|
||||
<Heading size="S">Relationships</Heading>
|
||||
<Button primary on:click={() => openRelationshipModal()}>Create Relationship</Button>
|
||||
<Button primary on:click={() => openRelationshipModal()}
|
||||
>Create Relationship</Button
|
||||
>
|
||||
</div>
|
||||
<Body>
|
||||
Tell budibase how your tables are related to get even more smart features.
|
||||
Tell budibase how your tables are related to get even more smart
|
||||
features.
|
||||
</Body>
|
||||
<div class="query-list">
|
||||
{#each plusTables as table}
|
||||
{#each Object.keys(table.schema) as column}
|
||||
{#if table.schema[column].type === "link" && table.schema[column].relationshipType !== RelationshipTypes.MANY_TO_ONE}
|
||||
{#each Object.values(relationships) as relationship}
|
||||
<div
|
||||
class="query-list-item"
|
||||
on:click={() => openRelationshipModal(table.schema[column])}>
|
||||
<p class="query-name">{table.schema[column].name}</p>
|
||||
<p>{buildRelationshipDisplayString(table, table.schema[column])}</p>
|
||||
on:click={() =>
|
||||
openRelationshipModal(relationship.from, relationship.to)}
|
||||
>
|
||||
<p>
|
||||
{buildRelationshipDisplayString(
|
||||
relationship.from,
|
||||
relationship.to
|
||||
)}
|
||||
</p>
|
||||
<p class="query-name">{relationship.from?.name} to {relationship.to?.name}</p>
|
||||
<p>→</p>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
<Divider />
|
||||
<div class="query-header">
|
||||
|
@ -252,4 +317,10 @@
|
|||
text-overflow: ellipsis;
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
|
||||
.table-buttons {
|
||||
display: grid;
|
||||
grid-gap: var(--spacing-l);
|
||||
grid-template-columns:1fr 1fr;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<script>
|
||||
import { ModalContent, Select, Body } from "@budibase/bbui"
|
||||
import { tables } from "stores/backend"
|
||||
|
||||
export let datasource
|
||||
export let plusTables
|
||||
export let save
|
||||
|
||||
async function saveDisplayColumns() {
|
||||
// be explicit about copying over
|
||||
for (let table of plusTables) {
|
||||
datasource.entities[table.name].primaryDisplay = table.primaryDisplay
|
||||
}
|
||||
save()
|
||||
await tables.fetch()
|
||||
}
|
||||
|
||||
function getColumnOptions(table) {
|
||||
if (!table || !table.schema) {
|
||||
return []
|
||||
}
|
||||
return Object.entries(table.schema).filter(field => field[1].type !== "link").map(([fieldName]) => fieldName)
|
||||
}
|
||||
</script>
|
||||
<ModalContent
|
||||
title="Edit display columns"
|
||||
confirmText="Save"
|
||||
onConfirm={saveDisplayColumns}
|
||||
>
|
||||
<Body>Select the columns that will be shown when displaying relationships.</Body>
|
||||
{#each plusTables as table}
|
||||
<Select
|
||||
label={table.name}
|
||||
options={getColumnOptions(table)}
|
||||
bind:value={table.primaryDisplay}
|
||||
/>
|
||||
{/each}
|
||||
</ModalContent>
|
|
@ -37,6 +37,6 @@ export interface BudibaseAppMetadata {
|
|||
name: string
|
||||
url: string
|
||||
instance: { _id: string }
|
||||
updatedAt: Date,
|
||||
updatedAt: Date
|
||||
createdAt: Date
|
||||
}
|
|
@ -135,10 +135,7 @@ module PostgresModule {
|
|||
* Fetches the tables from the postgres table and assigns them to the datasource.
|
||||
* @param {*} datasourceId - datasourceId to fetch
|
||||
*/
|
||||
async buildSchema(
|
||||
datasourceId: string,
|
||||
entities: Record<string, Table>
|
||||
) {
|
||||
async buildSchema(datasourceId: string, entities: Record<string, Table>) {
|
||||
let tableKeys: { [key: string]: string[] } = {}
|
||||
try {
|
||||
const primaryKeysResponse = await this.client.query(
|
||||
|
|
Loading…
Reference in New Issue