From 430ca37826aa652067a196ab7dcc4921f3a1bc81 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Mon, 22 Jun 2020 21:30:23 +0100 Subject: [PATCH] bidirectional linked records --- .../common/LinkedRecordSelector.svelte | 26 ++-- .../ModelDataTable/LinkedRecord.svelte | 92 ++++++++++++ .../ModelDataTable/ModelDataTable.svelte | 14 +- .../components/database/ModelDataTable/api.js | 2 +- .../modals/CreateEditRecord.svelte | 2 +- .../ModelDataTable/modals/DeleteRecord.svelte | 2 + .../nav/ModelNavigator/BlockNavigator.svelte | 8 +- .../nav/ModelNavigator/EmptyModel.svelte | 14 +- .../builder/src/constants/backend/index.js | 138 +++++++++--------- packages/server/src/api/controllers/model.js | 1 + packages/server/src/api/controllers/record.js | 23 +++ .../server/src/utilities/linkedRecords.js | 0 12 files changed, 235 insertions(+), 87 deletions(-) create mode 100644 packages/builder/src/components/database/ModelDataTable/LinkedRecord.svelte create mode 100644 packages/server/src/utilities/linkedRecords.js diff --git a/packages/builder/src/components/common/LinkedRecordSelector.svelte b/packages/builder/src/components/common/LinkedRecordSelector.svelte index b4787773b3..22fa383ec6 100644 --- a/packages/builder/src/components/common/LinkedRecordSelector.svelte +++ b/packages/builder/src/components/common/LinkedRecordSelector.svelte @@ -4,7 +4,8 @@ import api from "builderStore/api" export let modelId - export let linkedRecords + export let linkName + export let linked = [] let records = [] @@ -19,16 +20,16 @@ }) function linkRecord(record) { - linkedRecords.push(record) + linked.push(record._id) }
+

{linkName}

{#each records as record} -
-

{record.name}

+
linkRecord(record)}>
- {#each Object.keys(record) as key} + {#each Object.keys(record).slice(0, 2) as key}
{key}

{record[key]}

@@ -40,21 +41,24 @@
diff --git a/packages/builder/src/components/database/ModelDataTable/LinkedRecord.svelte b/packages/builder/src/components/database/ModelDataTable/LinkedRecord.svelte new file mode 100644 index 0000000000..c00fb53c3e --- /dev/null +++ b/packages/builder/src/components/database/ModelDataTable/LinkedRecord.svelte @@ -0,0 +1,92 @@ + + +
+ (open = !open)}>{records.length} + {#if open} +
+

{header}

+ {#each records as record} +
+
+ {#each Object.keys(record).slice(0, 2) as key} +
+ {key} +

{record[key]}

+
+ {/each} +
+
+ {/each} +
+ {/if} +
+ + diff --git a/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte b/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte index 008c4eeed0..0b13b3a7d4 100644 --- a/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte +++ b/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte @@ -4,6 +4,7 @@ import { Button } from "@budibase/bbui" import Select from "components/common/Select.svelte" import ActionButton from "components/common/ActionButton.svelte" + import LinkedRecord from "./LinkedRecord.svelte"; import TablePagination from "./TablePagination.svelte" import { DeleteRecordModal, CreateEditRecordModal } from "./modals" import * as api from "./api" @@ -41,6 +42,7 @@ let headers = [] let views = [] let currentPage = 0 + let search $: instanceId = $backendUiStore.selectedDatabase._id @@ -91,6 +93,10 @@ + @@ -130,7 +136,13 @@ {#each headers as header} - + {/each} {/each} diff --git a/packages/builder/src/components/database/ModelDataTable/api.js b/packages/builder/src/components/database/ModelDataTable/api.js index cb98879567..3bd8a0b3e6 100644 --- a/packages/builder/src/components/database/ModelDataTable/api.js +++ b/packages/builder/src/components/database/ModelDataTable/api.js @@ -15,7 +15,7 @@ export async function createDatabase(appname, instanceName) { } export async function deleteRecord(record, instanceId) { - const DELETE_RECORDS_URL = `/api/${instanceId}/${record._modelId}/records/${record._id}/${record._rev}` + const DELETE_RECORDS_URL = `/api/${instanceId}/${record.modelId}/records/${record._id}/${record._rev}` const response = await api.delete(DELETE_RECORDS_URL) return response } diff --git a/packages/builder/src/components/database/ModelDataTable/modals/CreateEditRecord.svelte b/packages/builder/src/components/database/ModelDataTable/modals/CreateEditRecord.svelte index f96e2530a6..7c94aa3a03 100644 --- a/packages/builder/src/components/database/ModelDataTable/modals/CreateEditRecord.svelte +++ b/packages/builder/src/components/database/ModelDataTable/modals/CreateEditRecord.svelte @@ -77,7 +77,7 @@ {#each modelSchema as [key, meta]}
{#if meta.type === 'link'} - + {:else} import ActionButton from "components/common/ActionButton.svelte" + import { notifier } from "@beyonk/svelte-notifications" import { store, backendUiStore } from "builderStore" import * as api from "../api" @@ -26,6 +27,7 @@ alert on:click={async () => { await api.deleteRecord(record, instanceId) + notifier.danger("Record deleted") backendUiStore.actions.records.delete(record) onClosed() }}> diff --git a/packages/builder/src/components/nav/ModelNavigator/BlockNavigator.svelte b/packages/builder/src/components/nav/ModelNavigator/BlockNavigator.svelte index ed6e58ee54..3be5ebc715 100644 --- a/packages/builder/src/components/nav/ModelNavigator/BlockNavigator.svelte +++ b/packages/builder/src/components/nav/ModelNavigator/BlockNavigator.svelte @@ -1,6 +1,6 @@
@@ -48,7 +60,7 @@

Blocks are pre-made fields and help you build your model quicker.

{#each Object.values(MODELS) as model} - + createModel(model)}/> {/each}
diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.js index 16749b3d9d..620cd4ad48 100644 --- a/packages/builder/src/constants/backend/index.js +++ b/packages/builder/src/constants/backend/index.js @@ -28,15 +28,15 @@ export const FIELDS = { presence: false, }, }, - OPTIONS: { - name: "Options", - icon: "ri-list-check-2", - type: "options", - constraints: { - type: "string", - presence: false, - }, - }, + // OPTIONS: { + // name: "Options", + // icon: "ri-list-check-2", + // type: "options", + // constraints: { + // type: "string", + // presence: false, + // }, + // }, DATETIME: { name: "Date/Time", icon: "ri-calendar-event-fill", @@ -47,24 +47,24 @@ export const FIELDS = { presence: false, }, }, - IMAGE: { - name: "File", - icon: "ri-image-line", - type: "file", - constraints: { - type: "string", - presence: false, - }, - }, - FILE: { - name: "Image", - icon: "ri-file-line", - type: "file", - constraints: { - type: "string", - presence: false, - }, - }, + // IMAGE: { + // name: "File", + // icon: "ri-image-line", + // type: "file", + // constraints: { + // type: "string", + // presence: false, + // }, + // }, + // FILE: { + // name: "Image", + // icon: "ri-file-line", + // type: "file", + // constraints: { + // type: "string", + // presence: false, + // }, + // }, DATA_LINK: { name: "Data Links", icon: "ri-link", @@ -106,16 +106,16 @@ export const BLOCKS = { presence: false, }, }, - PRIORITY: { - name: "Options", - icon: "ri-list-check-2", - type: "options", - constraints: { - type: "string", - presence: false, - inclusion: ["low", "medium", "high"], - }, - }, + // PRIORITY: { + // name: "Options", + // icon: "ri-list-check-2", + // type: "options", + // constraints: { + // type: "string", + // presence: false, + // inclusion: ["low", "medium", "high"], + // }, + // }, END_DATE: { name: "End Date", icon: "ri-calendar-event-fill", @@ -126,39 +126,29 @@ export const BLOCKS = { presence: false, }, }, - AVATAR: { - name: "Avatar", - icon: "ri-image-line", - type: "image", - constraints: { - type: "string", - presence: false, - }, - }, - PDF: { - name: "PDF", - icon: "ri-file-line", - type: "file", - constraints: { - type: "string", - presence: false, - }, - }, - DATA_LINK: { - name: "Data Links", - icon: "ri-link", - type: "link", - modelId: null, - constraints: { - type: "array", - }, - }, + // AVATAR: { + // name: "Avatar", + // icon: "ri-image-line", + // type: "image", + // constraints: { + // type: "string", + // presence: false, + // }, + // }, + // PDF: { + // name: "PDF", + // icon: "ri-file-line", + // type: "file", + // constraints: { + // type: "string", + // presence: false, + // }, + // }, } -// TODO: Needs more thought, need to come up with the constraints etc for each one export const MODELS = { CONTACTS: { - icon: "ri-link", + icon: "ri-contacts-book-line", name: "Contacts", schema: { Name: BLOCKS.NAME, @@ -170,7 +160,21 @@ export const MODELS = { name: "Recipes", schema: { Name: BLOCKS.NAME, - "Phone Number": BLOCKS.PHONE_NUMBER, + Cuisine: { + ...FIELDS.PLAIN_TEXT, + name: "Cuisine" + }, + }, + }, + SPORTS_TEAM: { + icon: "ri-basketball-line", + name: "Sports Team", + schema: { + Name: BLOCKS.NAME, + Championships: { + ...FIELDS.NUMBER, + name: "Championships" + } }, }, } diff --git a/packages/server/src/api/controllers/model.js b/packages/server/src/api/controllers/model.js index effd179e9d..0040ea6207 100644 --- a/packages/server/src/api/controllers/model.js +++ b/packages/server/src/api/controllers/model.js @@ -34,6 +34,7 @@ exports.save = async function(ctx) { // create the link field in the other model const linkedModel = await db.get(schema[key].modelId) linkedModel.schema[modelToSave.name] = { + name: modelToSave.name, type: "link", modelId: modelToSave._id, constraints: { diff --git a/packages/server/src/api/controllers/record.js b/packages/server/src/api/controllers/record.js index cf8eb27606..37b490a29d 100644 --- a/packages/server/src/api/controllers/record.js +++ b/packages/server/src/api/controllers/record.js @@ -1,6 +1,7 @@ const CouchDB = require("../../db") const validateJs = require("validate.js") const newid = require("../../db/newid") +const { link } = require("pouchdb-adapter-memory") exports.save = async function(ctx) { const db = new CouchDB(ctx.params.instanceId) @@ -43,6 +44,28 @@ exports.save = async function(ctx) { const response = await db.post(record) record._rev = response.rev + // create links in other tables + for (let key in record) { + // link + if (Array.isArray(record[key])) { + const linked = await db.allDocs({ + include_docs: true, + keys: record[key], + }) + + // add this record to the linked records in attached models + const linkedDocs = linked.rows.map(row => { + const doc = row.doc + return { + ...doc, + [model.name]: doc[model.name] ? [...doc[model.name], record._id] : [record._id] + } + }) + + await db.bulkDocs(linkedDocs) + } + } + ctx.eventEmitter && ctx.eventEmitter.emit(`record:save`, { record, diff --git a/packages/server/src/utilities/linkedRecords.js b/packages/server/src/utilities/linkedRecords.js new file mode 100644 index 0000000000..e69de29bb2
{row[header]} + {#if Array.isArray(row[header])} + + {:else} + {row[header] || 0} + {/if} +