Implementing UI to support the backend put in place.

This commit is contained in:
mike12345567 2021-07-02 14:33:05 +01:00
parent 3eb8f3c445
commit 10730d8053
7 changed files with 274 additions and 118 deletions

View File

@ -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)

View File

@ -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,
}
]
function onChangeRelationshipType(evt) {
if (evt.detail === RelationshipTypes.ONE_TO_MANY) {
relationship.through = null
$: updateRelationshipType(fromRelationship?.relationshipType)
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}
/>
<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}
{:else if toTable}
<Select
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>

View File

@ -12,10 +12,10 @@
{#each tables as table}
<MenuItem noClose icon="Table" on:click={() => select(table)}>
{table.name}
{#if selected}
<Icon size="S" name="Checkmark" />
{/if}
{#if selected}
<Icon size="S" name="Checkmark" />
{/if}
</MenuItem>
{/each}
</MenuSection>
</Menu>
</Menu>

View File

@ -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,9 +175,16 @@
<Divider />
<div class="query-header">
<Heading size="S">Tables</Heading>
<Button primary on:click={updateDatasourceSchema}
>Fetch Tables From Database</Button
>
<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
@ -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>
@ -141,30 +201,35 @@
{/each}
</div>
<Divider />
<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.
</Body>
<div class="query-list">
{#each Object.values(relationships) as relationship}
<div
class="query-list-item"
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>
{/each}
</div>
<Body>
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}
<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>
<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>

View File

@ -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>

View File

@ -37,6 +37,6 @@ export interface BudibaseAppMetadata {
name: string
url: string
instance: { _id: string }
updatedAt: Date,
updatedAt: Date
createdAt: Date
}
}

View File

@ -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(
@ -173,7 +170,7 @@ module PostgresModule {
// add the existing relationships from the entities if they exist, to prevent them from being overridden
if (entities) {
const existingTableSchema = entities[tableName].schema
const existingTableSchema = entities[tableName].schema
for (let key in existingTableSchema) {
if (existingTableSchema[key].type === "link") {
tables[tableName].schema[key] = existingTableSchema[key]