Typescript conversion of linked row controller.
This commit is contained in:
parent
7eac8fafd6
commit
eeebd0fe70
|
@ -1,12 +1,32 @@
|
||||||
const { IncludeDocs, getLinkDocuments } = require("./linkUtils")
|
import { IncludeDocs, getLinkDocuments } from "./linkUtils"
|
||||||
const { InternalTables, getUserMetadataParams } = require("../utils")
|
import { InternalTables, getUserMetadataParams } from "../utils"
|
||||||
const Sentry = require("@sentry/node")
|
import Sentry from "@sentry/node"
|
||||||
const { FieldTypes, RelationshipTypes } = require("../../constants")
|
import { FieldTypes, RelationshipTypes } from "../../constants"
|
||||||
const { context } = require("@budibase/backend-core")
|
import { context } from "@budibase/backend-core"
|
||||||
const LinkDocument = require("./LinkDocument")
|
import LinkDocument from "./LinkDocument"
|
||||||
|
import {
|
||||||
|
Database,
|
||||||
|
FieldSchema,
|
||||||
|
LinkDocumentValue,
|
||||||
|
Row,
|
||||||
|
Table,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
type LinkControllerOpts = {
|
||||||
|
tableId: string
|
||||||
|
row?: Row
|
||||||
|
table?: Table
|
||||||
|
oldTable?: Table
|
||||||
|
}
|
||||||
|
|
||||||
class LinkController {
|
class LinkController {
|
||||||
constructor({ tableId, row, table, oldTable }) {
|
_db: Database
|
||||||
|
_tableId: string
|
||||||
|
_row?: Row
|
||||||
|
_table?: Table
|
||||||
|
_oldTable?: Table
|
||||||
|
|
||||||
|
constructor({ tableId, row, table, oldTable }: LinkControllerOpts) {
|
||||||
this._db = context.getAppDB()
|
this._db = context.getAppDB()
|
||||||
this._tableId = tableId
|
this._tableId = tableId
|
||||||
this._row = row
|
this._row = row
|
||||||
|
@ -24,7 +44,7 @@ class LinkController {
|
||||||
this._table =
|
this._table =
|
||||||
this._table == null ? await this._db.get(this._tableId) : this._table
|
this._table == null ? await this._db.get(this._tableId) : this._table
|
||||||
}
|
}
|
||||||
return this._table
|
return this._table!
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,7 +54,7 @@ class LinkController {
|
||||||
* @returns {Promise<boolean>} True if there are any linked fields, otherwise it will return
|
* @returns {Promise<boolean>} True if there are any linked fields, otherwise it will return
|
||||||
* false.
|
* false.
|
||||||
*/
|
*/
|
||||||
async doesTableHaveLinkedFields(table = null) {
|
async doesTableHaveLinkedFields(table?: Table) {
|
||||||
if (table == null) {
|
if (table == null) {
|
||||||
table = await this.table()
|
table = await this.table()
|
||||||
}
|
}
|
||||||
|
@ -50,7 +70,7 @@ class LinkController {
|
||||||
/**
|
/**
|
||||||
* Utility function for main getLinkDocuments function - refer to it for functionality.
|
* Utility function for main getLinkDocuments function - refer to it for functionality.
|
||||||
*/
|
*/
|
||||||
getRowLinkDocs(rowId) {
|
getRowLinkDocs(rowId: string) {
|
||||||
return getLinkDocuments({
|
return getLinkDocuments({
|
||||||
tableId: this._tableId,
|
tableId: this._tableId,
|
||||||
rowId,
|
rowId,
|
||||||
|
@ -61,23 +81,23 @@ class LinkController {
|
||||||
/**
|
/**
|
||||||
* Utility function for main getLinkDocuments function - refer to it for functionality.
|
* Utility function for main getLinkDocuments function - refer to it for functionality.
|
||||||
*/
|
*/
|
||||||
getTableLinkDocs() {
|
async getTableLinkDocs() {
|
||||||
return getLinkDocuments({
|
return (await getLinkDocuments({
|
||||||
tableId: this._tableId,
|
tableId: this._tableId,
|
||||||
includeDocs: IncludeDocs.INCLUDE,
|
includeDocs: IncludeDocs.INCLUDE,
|
||||||
})
|
})) as LinkDocument[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes sure the passed in table schema contains valid relationship structures.
|
* Makes sure the passed in table schema contains valid relationship structures.
|
||||||
*/
|
*/
|
||||||
validateTable(table) {
|
validateTable(table: Table) {
|
||||||
const usedAlready = []
|
const usedAlready = []
|
||||||
for (let schema of Object.values(table.schema)) {
|
for (let schema of Object.values(table.schema)) {
|
||||||
if (schema.type !== FieldTypes.LINK) {
|
if (schema.type !== FieldTypes.LINK) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const unique = schema.tableId + schema.fieldName
|
const unique = schema.tableId! + schema?.fieldName
|
||||||
if (usedAlready.indexOf(unique) !== -1) {
|
if (usedAlready.indexOf(unique) !== -1) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Cannot re-use the linked column name for a linked table."
|
"Cannot re-use the linked column name for a linked table."
|
||||||
|
@ -90,7 +110,7 @@ class LinkController {
|
||||||
/**
|
/**
|
||||||
* Returns whether the two link schemas are equal (in the important parts, not a pure equality check)
|
* Returns whether the two link schemas are equal (in the important parts, not a pure equality check)
|
||||||
*/
|
*/
|
||||||
areLinkSchemasEqual(linkSchema1, linkSchema2) {
|
areLinkSchemasEqual(linkSchema1: FieldSchema, linkSchema2: FieldSchema) {
|
||||||
const compareFields = [
|
const compareFields = [
|
||||||
"name",
|
"name",
|
||||||
"type",
|
"type",
|
||||||
|
@ -100,6 +120,7 @@ class LinkController {
|
||||||
"relationshipType",
|
"relationshipType",
|
||||||
]
|
]
|
||||||
for (let field of compareFields) {
|
for (let field of compareFields) {
|
||||||
|
// @ts-ignore
|
||||||
if (linkSchema1[field] !== linkSchema2[field]) {
|
if (linkSchema1[field] !== linkSchema2[field]) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -111,7 +132,7 @@ class LinkController {
|
||||||
* Given the link field of this table, and the link field of the linked table, this makes sure
|
* Given the link field of this table, and the link field of the linked table, this makes sure
|
||||||
* the state of relationship type is accurate on both.
|
* the state of relationship type is accurate on both.
|
||||||
*/
|
*/
|
||||||
handleRelationshipType(linkerField, linkedField) {
|
handleRelationshipType(linkerField: FieldSchema, linkedField: FieldSchema) {
|
||||||
if (
|
if (
|
||||||
!linkerField.relationshipType ||
|
!linkerField.relationshipType ||
|
||||||
linkerField.relationshipType === RelationshipTypes.MANY_TO_MANY
|
linkerField.relationshipType === RelationshipTypes.MANY_TO_MANY
|
||||||
|
@ -138,10 +159,10 @@ class LinkController {
|
||||||
*/
|
*/
|
||||||
async rowSaved() {
|
async rowSaved() {
|
||||||
const table = await this.table()
|
const table = await this.table()
|
||||||
const row = this._row
|
const row = this._row!
|
||||||
const operations = []
|
const operations = []
|
||||||
// get link docs to compare against
|
// get link docs to compare against
|
||||||
const linkDocs = await this.getRowLinkDocs(row._id)
|
const linkDocs = (await this.getRowLinkDocs(row._id!)) as LinkDocument[]
|
||||||
for (let fieldName of Object.keys(table.schema)) {
|
for (let fieldName of Object.keys(table.schema)) {
|
||||||
// get the links this row wants to make
|
// get the links this row wants to make
|
||||||
const rowField = row[fieldName]
|
const rowField = row[fieldName]
|
||||||
|
@ -161,30 +182,32 @@ class LinkController {
|
||||||
|
|
||||||
// if 1:N, ensure that this ID is not already attached to another record
|
// if 1:N, ensure that this ID is not already attached to another record
|
||||||
const linkedTable = await this._db.get(field.tableId)
|
const linkedTable = await this._db.get(field.tableId)
|
||||||
const linkedSchema = linkedTable.schema[field.fieldName]
|
const linkedSchema = linkedTable.schema[field.fieldName!]
|
||||||
|
|
||||||
// We need to map the global users to metadata in each app for relationships
|
// We need to map the global users to metadata in each app for relationships
|
||||||
if (field.tableId === InternalTables.USER_METADATA) {
|
if (field.tableId === InternalTables.USER_METADATA) {
|
||||||
const users = await this._db.allDocs(getUserMetadataParams(null, {}))
|
const users = await this._db.allDocs(getUserMetadataParams(null, {}))
|
||||||
const metadataRequired = rowField.filter(
|
const metadataRequired = rowField.filter(
|
||||||
userId => !users.rows.some(user => user.id === userId)
|
(userId: string) => !users.rows.some(user => user.id === userId)
|
||||||
)
|
)
|
||||||
|
|
||||||
// ensure non-existing user metadata is created in the app DB
|
// ensure non-existing user metadata is created in the app DB
|
||||||
await this._db.bulkDocs(
|
await this._db.bulkDocs(
|
||||||
metadataRequired.map(userId => ({ _id: userId }))
|
metadataRequired.map((userId: string) => ({ _id: userId }))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// iterate through the link IDs in the row field, see if any don't exist already
|
// iterate through the link IDs in the row field, see if any don't exist already
|
||||||
for (let linkId of rowField) {
|
for (let linkId of rowField) {
|
||||||
if (linkedSchema.relationshipType === RelationshipTypes.ONE_TO_MANY) {
|
if (
|
||||||
|
linkedSchema?.relationshipType === RelationshipTypes.ONE_TO_MANY
|
||||||
|
) {
|
||||||
let links = (
|
let links = (
|
||||||
await getLinkDocuments({
|
(await getLinkDocuments({
|
||||||
tableId: field.tableId,
|
tableId: field.tableId,
|
||||||
rowId: linkId,
|
rowId: linkId,
|
||||||
includeDocs: IncludeDocs.EXCLUDE,
|
includeDocs: IncludeDocs.EXCLUDE,
|
||||||
})
|
})) as LinkDocumentValue[]
|
||||||
).filter(
|
).filter(
|
||||||
link =>
|
link =>
|
||||||
link.id !== row._id && link.fieldName === linkedSchema.name
|
link.id !== row._id && link.fieldName === linkedSchema.name
|
||||||
|
@ -209,11 +232,11 @@ class LinkController {
|
||||||
}
|
}
|
||||||
operations.push(
|
operations.push(
|
||||||
new LinkDocument(
|
new LinkDocument(
|
||||||
table._id,
|
table._id!,
|
||||||
fieldName,
|
fieldName,
|
||||||
row._id,
|
row._id!,
|
||||||
field.tableId,
|
field.tableId!,
|
||||||
field.fieldName,
|
field.fieldName!,
|
||||||
linkId
|
linkId
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -246,9 +269,9 @@ class LinkController {
|
||||||
* be accurate. This also returns the row that was deleted.
|
* be accurate. This also returns the row that was deleted.
|
||||||
*/
|
*/
|
||||||
async rowDeleted() {
|
async rowDeleted() {
|
||||||
const row = this._row
|
const row = this._row!
|
||||||
// need to get the full link docs to be be able to delete it
|
// need to get the full link docs to be be able to delete it
|
||||||
const linkDocs = await this.getRowLinkDocs(row._id)
|
const linkDocs = await this.getRowLinkDocs(row._id!)
|
||||||
if (linkDocs.length === 0) {
|
if (linkDocs.length === 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -267,13 +290,13 @@ class LinkController {
|
||||||
* @param {string} fieldName The field to be removed from the table.
|
* @param {string} fieldName The field to be removed from the table.
|
||||||
* @returns {Promise<void>} The table has now been updated.
|
* @returns {Promise<void>} The table has now been updated.
|
||||||
*/
|
*/
|
||||||
async removeFieldFromTable(fieldName) {
|
async removeFieldFromTable(fieldName: string) {
|
||||||
let oldTable = this._oldTable
|
let oldTable = this._oldTable
|
||||||
let field = oldTable.schema[fieldName]
|
let field = oldTable?.schema[fieldName] as FieldSchema
|
||||||
const linkDocs = await this.getTableLinkDocs()
|
const linkDocs = await this.getTableLinkDocs()
|
||||||
let toDelete = linkDocs.filter(linkDoc => {
|
let toDelete = linkDocs.filter(linkDoc => {
|
||||||
let correctFieldName =
|
let correctFieldName =
|
||||||
linkDoc.doc1.tableId === oldTable._id
|
linkDoc.doc1.tableId === oldTable?._id
|
||||||
? linkDoc.doc1.fieldName
|
? linkDoc.doc1.fieldName
|
||||||
: linkDoc.doc2.fieldName
|
: linkDoc.doc2.fieldName
|
||||||
return correctFieldName === fieldName
|
return correctFieldName === fieldName
|
||||||
|
@ -288,7 +311,9 @@ class LinkController {
|
||||||
)
|
)
|
||||||
// remove schema from other table
|
// remove schema from other table
|
||||||
let linkedTable = await this._db.get(field.tableId)
|
let linkedTable = await this._db.get(field.tableId)
|
||||||
delete linkedTable.schema[field.fieldName]
|
if (field.fieldName) {
|
||||||
|
delete linkedTable.schema[field.fieldName]
|
||||||
|
}
|
||||||
await this._db.put(linkedTable)
|
await this._db.put(linkedTable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,7 +330,7 @@ class LinkController {
|
||||||
const schema = table.schema
|
const schema = table.schema
|
||||||
for (let fieldName of Object.keys(schema)) {
|
for (let fieldName of Object.keys(schema)) {
|
||||||
const field = schema[fieldName]
|
const field = schema[fieldName]
|
||||||
if (field.type === FieldTypes.LINK) {
|
if (field.type === FieldTypes.LINK && field.fieldName) {
|
||||||
// handle this in a separate try catch, want
|
// handle this in a separate try catch, want
|
||||||
// the put to bubble up as an error, if can't update
|
// the put to bubble up as an error, if can't update
|
||||||
// table for some reason
|
// table for some reason
|
||||||
|
@ -362,8 +387,8 @@ class LinkController {
|
||||||
const oldTable = this._oldTable
|
const oldTable = this._oldTable
|
||||||
// first start by checking if any link columns have been deleted
|
// first start by checking if any link columns have been deleted
|
||||||
const newTable = await this.table()
|
const newTable = await this.table()
|
||||||
for (let fieldName of Object.keys(oldTable.schema)) {
|
for (let fieldName of Object.keys(oldTable?.schema || {})) {
|
||||||
const field = oldTable.schema[fieldName]
|
const field = oldTable?.schema[fieldName] as FieldSchema
|
||||||
// this field has been removed from the table schema
|
// this field has been removed from the table schema
|
||||||
if (
|
if (
|
||||||
field.type === FieldTypes.LINK &&
|
field.type === FieldTypes.LINK &&
|
||||||
|
@ -389,7 +414,7 @@ class LinkController {
|
||||||
for (let fieldName of Object.keys(schema)) {
|
for (let fieldName of Object.keys(schema)) {
|
||||||
const field = schema[fieldName]
|
const field = schema[fieldName]
|
||||||
try {
|
try {
|
||||||
if (field.type === FieldTypes.LINK) {
|
if (field.type === FieldTypes.LINK && field.fieldName) {
|
||||||
const linkedTable = await this._db.get(field.tableId)
|
const linkedTable = await this._db.get(field.tableId)
|
||||||
delete linkedTable.schema[field.fieldName]
|
delete linkedTable.schema[field.fieldName]
|
||||||
await this._db.put(linkedTable)
|
await this._db.put(linkedTable)
|
||||||
|
@ -416,4 +441,4 @@ class LinkController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = LinkController
|
export = LinkController
|
|
@ -1,47 +0,0 @@
|
||||||
const { generateLinkID } = require("../utils")
|
|
||||||
const { FieldTypes } = require("../../constants")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new link document structure which can be put to the database. It is important to
|
|
||||||
* note that while this talks about linker/linked the link is bi-directional and for all intent
|
|
||||||
* and purposes it does not matter from which direction the link was initiated.
|
|
||||||
* @param {string} tableId1 The ID of the first table (the linker).
|
|
||||||
* @param {string} tableId2 The ID of the second table (the linked).
|
|
||||||
* @param {string} fieldName1 The name of the field in the linker table.
|
|
||||||
* @param {string} fieldName2 The name of the field in the linked table.
|
|
||||||
* @param {string} rowId1 The ID of the row which is acting as the linker.
|
|
||||||
* @param {string} rowId2 The ID of the row which is acting as the linked.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function LinkDocument(
|
|
||||||
tableId1,
|
|
||||||
fieldName1,
|
|
||||||
rowId1,
|
|
||||||
tableId2,
|
|
||||||
fieldName2,
|
|
||||||
rowId2
|
|
||||||
) {
|
|
||||||
// build the ID out of unique references to this link document
|
|
||||||
this._id = generateLinkID(
|
|
||||||
tableId1,
|
|
||||||
tableId2,
|
|
||||||
rowId1,
|
|
||||||
rowId2,
|
|
||||||
fieldName1,
|
|
||||||
fieldName2
|
|
||||||
)
|
|
||||||
// required for referencing in view
|
|
||||||
this.type = FieldTypes.LINK
|
|
||||||
this.doc1 = {
|
|
||||||
tableId: tableId1,
|
|
||||||
fieldName: fieldName1,
|
|
||||||
rowId: rowId1,
|
|
||||||
}
|
|
||||||
this.doc2 = {
|
|
||||||
tableId: tableId2,
|
|
||||||
fieldName: fieldName2,
|
|
||||||
rowId: rowId2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = LinkDocument
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { generateLinkID } from "../utils"
|
||||||
|
import { FieldTypes } from "../../constants"
|
||||||
|
import { LinkDocument } from "@budibase/types"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new link document structure which can be put to the database. It is important to
|
||||||
|
* note that while this talks about linker/linked the link is bi-directional and for all intent
|
||||||
|
* and purposes it does not matter from which direction the link was initiated.
|
||||||
|
* @param {string} tableId1 The ID of the first table (the linker).
|
||||||
|
* @param {string} tableId2 The ID of the second table (the linked).
|
||||||
|
* @param {string} fieldName1 The name of the field in the linker table.
|
||||||
|
* @param {string} fieldName2 The name of the field in the linked table.
|
||||||
|
* @param {string} rowId1 The ID of the row which is acting as the linker.
|
||||||
|
* @param {string} rowId2 The ID of the row which is acting as the linked.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
class LinkDocumentImpl implements LinkDocument {
|
||||||
|
_id: string
|
||||||
|
type: string
|
||||||
|
doc1: {
|
||||||
|
rowId: string
|
||||||
|
fieldName: string
|
||||||
|
tableId: string
|
||||||
|
}
|
||||||
|
doc2: {
|
||||||
|
rowId: string
|
||||||
|
fieldName: string
|
||||||
|
tableId: string
|
||||||
|
}
|
||||||
|
constructor(
|
||||||
|
tableId1: string,
|
||||||
|
fieldName1: string,
|
||||||
|
rowId1: string,
|
||||||
|
tableId2: string,
|
||||||
|
fieldName2: string,
|
||||||
|
rowId2: string
|
||||||
|
) {
|
||||||
|
this._id = generateLinkID(
|
||||||
|
tableId1,
|
||||||
|
tableId2,
|
||||||
|
rowId1,
|
||||||
|
rowId2,
|
||||||
|
fieldName1,
|
||||||
|
fieldName2
|
||||||
|
)
|
||||||
|
this.type = FieldTypes.LINK
|
||||||
|
this.doc1 = {
|
||||||
|
tableId: tableId1,
|
||||||
|
fieldName: fieldName1,
|
||||||
|
rowId: rowId1,
|
||||||
|
}
|
||||||
|
this.doc2 = {
|
||||||
|
tableId: tableId2,
|
||||||
|
fieldName: fieldName2,
|
||||||
|
rowId: rowId2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export = LinkDocumentImpl
|
|
@ -1,5 +1,5 @@
|
||||||
const LinkController = require("./LinkController")
|
import LinkController from "./LinkController"
|
||||||
const {
|
import {
|
||||||
IncludeDocs,
|
IncludeDocs,
|
||||||
getLinkDocuments,
|
getLinkDocuments,
|
||||||
createLinkView,
|
createLinkView,
|
||||||
|
@ -7,21 +7,24 @@ const {
|
||||||
getRelatedTableForField,
|
getRelatedTableForField,
|
||||||
getLinkedTableIDs,
|
getLinkedTableIDs,
|
||||||
getLinkedTable,
|
getLinkedTable,
|
||||||
} = require("./linkUtils")
|
} from "./linkUtils"
|
||||||
const { flatten } = require("lodash")
|
import { flatten } from "lodash"
|
||||||
const { FieldTypes } = require("../../constants")
|
import { FieldTypes } from "../../constants"
|
||||||
const { getMultiIDParams, USER_METDATA_PREFIX } = require("../../db/utils")
|
import { getMultiIDParams, USER_METDATA_PREFIX } from "../utils"
|
||||||
const { partition } = require("lodash")
|
import { partition } from "lodash"
|
||||||
const { getGlobalUsersFromMetadata } = require("../../utilities/global")
|
import { getGlobalUsersFromMetadata } from "../../utilities/global"
|
||||||
const { processFormulas } = require("../../utilities/rowProcessor/utils")
|
import { processFormulas } from "../../utilities/rowProcessor"
|
||||||
const { context } = require("@budibase/backend-core")
|
import { context } from "@budibase/backend-core"
|
||||||
|
import { Table, Row, LinkDocumentValue } from "@budibase/types"
|
||||||
|
|
||||||
|
export { IncludeDocs, getLinkDocuments, createLinkView } from "./linkUtils"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This functionality makes sure that when rows with links are created, updated or deleted they are processed
|
* This functionality makes sure that when rows with links are created, updated or deleted they are processed
|
||||||
* correctly - making sure that no stale links are left around and that all links have been made successfully.
|
* correctly - making sure that no stale links are left around and that all links have been made successfully.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const EventType = {
|
export const EventType = {
|
||||||
ROW_SAVE: "row:save",
|
ROW_SAVE: "row:save",
|
||||||
ROW_UPDATE: "row:update",
|
ROW_UPDATE: "row:update",
|
||||||
ROW_DELETE: "row:delete",
|
ROW_DELETE: "row:delete",
|
||||||
|
@ -30,13 +33,7 @@ const EventType = {
|
||||||
TABLE_DELETE: "table:delete",
|
TABLE_DELETE: "table:delete",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.EventType = EventType
|
function clearRelationshipFields(table: Table, rows: Row[]) {
|
||||||
// re-export search here for ease of use
|
|
||||||
exports.IncludeDocs = IncludeDocs
|
|
||||||
exports.getLinkDocuments = getLinkDocuments
|
|
||||||
exports.createLinkView = createLinkView
|
|
||||||
|
|
||||||
function clearRelationshipFields(table, rows) {
|
|
||||||
for (let [key, field] of Object.entries(table.schema)) {
|
for (let [key, field] of Object.entries(table.schema)) {
|
||||||
if (field.type === FieldTypes.LINK) {
|
if (field.type === FieldTypes.LINK) {
|
||||||
rows = rows.map(row => {
|
rows = rows.map(row => {
|
||||||
|
@ -48,18 +45,17 @@ function clearRelationshipFields(table, rows) {
|
||||||
return rows
|
return rows
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLinksForRows(rows) {
|
async function getLinksForRows(rows: Row[]) {
|
||||||
const tableIds = [...new Set(rows.map(el => el.tableId))]
|
const tableIds = [...new Set(rows.map(el => el.tableId))]
|
||||||
// start by getting all the link values for performance reasons
|
// start by getting all the link values for performance reasons
|
||||||
|
const promises = tableIds.map(tableId =>
|
||||||
|
getLinkDocuments({
|
||||||
|
tableId: tableId,
|
||||||
|
includeDocs: IncludeDocs.EXCLUDE,
|
||||||
|
})
|
||||||
|
)
|
||||||
const responses = flatten(
|
const responses = flatten(
|
||||||
await Promise.all(
|
(await Promise.all(promises)) as LinkDocumentValue[][]
|
||||||
tableIds.map(tableId =>
|
|
||||||
getLinkDocuments({
|
|
||||||
tableId: tableId,
|
|
||||||
includeDocs: IncludeDocs.EXCLUDE,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
// have to get unique as the previous table query can
|
// have to get unique as the previous table query can
|
||||||
// return duplicates, could be querying for both tables in a relation
|
// return duplicates, could be querying for both tables in a relation
|
||||||
|
@ -72,7 +68,7 @@ async function getLinksForRows(rows) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getFullLinkedDocs(links) {
|
async function getFullLinkedDocs(links: LinkDocumentValue[]) {
|
||||||
// create DBs
|
// create DBs
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const linkedRowIds = links.map(link => link.id)
|
const linkedRowIds = links.map(link => link.id)
|
||||||
|
@ -103,12 +99,18 @@ async function getFullLinkedDocs(links) {
|
||||||
* @returns {Promise<object>} When the update is complete this will respond successfully. Returns the row for
|
* @returns {Promise<object>} When the update is complete this will respond successfully. Returns the row for
|
||||||
* row operations and the table for table operations.
|
* row operations and the table for table operations.
|
||||||
*/
|
*/
|
||||||
exports.updateLinks = async function (args) {
|
export async function updateLinks(args: {
|
||||||
|
tableId: string
|
||||||
|
eventType: string
|
||||||
|
row?: Row
|
||||||
|
table?: Table
|
||||||
|
oldTable?: Table
|
||||||
|
}) {
|
||||||
const { eventType, row, tableId, table, oldTable } = args
|
const { eventType, row, tableId, table, oldTable } = args
|
||||||
const baseReturnObj = row == null ? table : row
|
const baseReturnObj = row == null ? table : row
|
||||||
// make sure table ID is set
|
// make sure table ID is set
|
||||||
if (tableId == null && table != null) {
|
if (tableId == null && table != null) {
|
||||||
args.tableId = table._id
|
args.tableId = table._id!
|
||||||
}
|
}
|
||||||
let linkController = new LinkController(args)
|
let linkController = new LinkController(args)
|
||||||
try {
|
try {
|
||||||
|
@ -146,7 +148,7 @@ exports.updateLinks = async function (args) {
|
||||||
* @param {array<object>} rows The rows which are to be enriched.
|
* @param {array<object>} rows The rows which are to be enriched.
|
||||||
* @return {Promise<*>} returns the rows with all of the enriched relationships on it.
|
* @return {Promise<*>} returns the rows with all of the enriched relationships on it.
|
||||||
*/
|
*/
|
||||||
exports.attachFullLinkedDocs = async (table, rows) => {
|
export async function attachFullLinkedDocs(table: Table, rows: Row[]) {
|
||||||
const linkedTableIds = getLinkedTableIDs(table)
|
const linkedTableIds = getLinkedTableIDs(table)
|
||||||
if (linkedTableIds.length === 0) {
|
if (linkedTableIds.length === 0) {
|
||||||
return rows
|
return rows
|
||||||
|
@ -159,7 +161,7 @@ exports.attachFullLinkedDocs = async (table, rows) => {
|
||||||
rows = clearRelationshipFields(table, rows)
|
rows = clearRelationshipFields(table, rows)
|
||||||
// now get the docs and combine into the rows
|
// now get the docs and combine into the rows
|
||||||
let linked = await getFullLinkedDocs(links)
|
let linked = await getFullLinkedDocs(links)
|
||||||
const linkedTables = []
|
const linkedTables: Table[] = []
|
||||||
for (let row of rows) {
|
for (let row of rows) {
|
||||||
for (let link of links.filter(link => link.thisId === row._id)) {
|
for (let link of links.filter(link => link.thisId === row._id)) {
|
||||||
if (row[link.fieldName] == null) {
|
if (row[link.fieldName] == null) {
|
||||||
|
@ -185,13 +187,16 @@ exports.attachFullLinkedDocs = async (table, rows) => {
|
||||||
* @param {array<object>} enriched The pre-enriched rows (full docs) which are to be squashed.
|
* @param {array<object>} enriched The pre-enriched rows (full docs) which are to be squashed.
|
||||||
* @returns {Promise<Array>} The rows after having their links squashed to only contain the ID and primary display.
|
* @returns {Promise<Array>} The rows after having their links squashed to only contain the ID and primary display.
|
||||||
*/
|
*/
|
||||||
exports.squashLinksToPrimaryDisplay = async (table, enriched) => {
|
export async function squashLinksToPrimaryDisplay(
|
||||||
|
table: Table,
|
||||||
|
enriched: Row[]
|
||||||
|
) {
|
||||||
// will populate this as we find them
|
// will populate this as we find them
|
||||||
const linkedTables = [table]
|
const linkedTables = [table]
|
||||||
for (let row of enriched) {
|
for (let row of enriched) {
|
||||||
// this only fetches the table if its not already in array
|
// this only fetches the table if its not already in array
|
||||||
const rowTable = await getLinkedTable(row.tableId, linkedTables)
|
const rowTable = await getLinkedTable(row.tableId!, linkedTables)
|
||||||
for (let [column, schema] of Object.entries(rowTable.schema)) {
|
for (let [column, schema] of Object.entries(rowTable?.schema || {})) {
|
||||||
if (schema.type !== FieldTypes.LINK || !Array.isArray(row[column])) {
|
if (schema.type !== FieldTypes.LINK || !Array.isArray(row[column])) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -199,8 +204,8 @@ exports.squashLinksToPrimaryDisplay = async (table, enriched) => {
|
||||||
for (let link of row[column]) {
|
for (let link of row[column]) {
|
||||||
const linkTblId = link.tableId || getRelatedTableForField(table, column)
|
const linkTblId = link.tableId || getRelatedTableForField(table, column)
|
||||||
const linkedTable = await getLinkedTable(linkTblId, linkedTables)
|
const linkedTable = await getLinkedTable(linkTblId, linkedTables)
|
||||||
const obj = { _id: link._id }
|
const obj: any = { _id: link._id }
|
||||||
if (link[linkedTable.primaryDisplay]) {
|
if (linkedTable?.primaryDisplay && link[linkedTable.primaryDisplay]) {
|
||||||
obj.primaryDisplay = link[linkedTable.primaryDisplay]
|
obj.primaryDisplay = link[linkedTable.primaryDisplay]
|
||||||
}
|
}
|
||||||
newLinks.push(obj)
|
newLinks.push(obj)
|
|
@ -1,20 +1,24 @@
|
||||||
const Sentry = require("@sentry/node")
|
import { ViewName, getQueryIndex } from "../utils"
|
||||||
const { ViewName, getQueryIndex } = require("../utils")
|
import { FieldTypes } from "../../constants"
|
||||||
const { FieldTypes } = require("../../constants")
|
import { createLinkView } from "../views/staticViews"
|
||||||
const { createLinkView } = require("../views/staticViews")
|
import { context, logging } from "@budibase/backend-core"
|
||||||
const { context } = require("@budibase/backend-core")
|
import {
|
||||||
|
FieldSchema,
|
||||||
|
LinkDocument,
|
||||||
|
LinkDocumentValue,
|
||||||
|
Table,
|
||||||
|
} from "@budibase/types"
|
||||||
|
export { createLinkView } from "../views/staticViews"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only needed so that boolean parameters are being used for includeDocs
|
* Only needed so that boolean parameters are being used for includeDocs
|
||||||
* @type {{EXCLUDE: boolean, INCLUDE: boolean}}
|
* @type {{EXCLUDE: boolean, INCLUDE: boolean}}
|
||||||
*/
|
*/
|
||||||
exports.IncludeDocs = {
|
export const IncludeDocs = {
|
||||||
INCLUDE: true,
|
INCLUDE: true,
|
||||||
EXCLUDE: false,
|
EXCLUDE: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.createLinkView = createLinkView
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the linking documents, not the linked documents themselves.
|
* Gets the linking documents, not the linked documents themselves.
|
||||||
* @param {string} args.tableId The table which we are searching for linked rows against.
|
* @param {string} args.tableId The table which we are searching for linked rows against.
|
||||||
|
@ -28,10 +32,14 @@ exports.createLinkView = createLinkView
|
||||||
* @returns {Promise<object[]>} This will return an array of the linking documents that were found
|
* @returns {Promise<object[]>} This will return an array of the linking documents that were found
|
||||||
* (if any).
|
* (if any).
|
||||||
*/
|
*/
|
||||||
exports.getLinkDocuments = async function (args) {
|
export async function getLinkDocuments(args: {
|
||||||
|
tableId?: string
|
||||||
|
rowId?: string
|
||||||
|
includeDocs?: any
|
||||||
|
}): Promise<LinkDocumentValue[] | LinkDocument[]> {
|
||||||
const { tableId, rowId, includeDocs } = args
|
const { tableId, rowId, includeDocs } = args
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
let params
|
let params: any
|
||||||
if (rowId != null) {
|
if (rowId != null) {
|
||||||
params = { key: [tableId, rowId] }
|
params = { key: [tableId, rowId] }
|
||||||
}
|
}
|
||||||
|
@ -43,7 +51,7 @@ exports.getLinkDocuments = async function (args) {
|
||||||
try {
|
try {
|
||||||
let linkRows = (await db.query(getQueryIndex(ViewName.LINK), params)).rows
|
let linkRows = (await db.query(getQueryIndex(ViewName.LINK), params)).rows
|
||||||
// filter to get unique entries
|
// filter to get unique entries
|
||||||
const foundIds = []
|
const foundIds: string[] = []
|
||||||
linkRows = linkRows.filter(link => {
|
linkRows = linkRows.filter(link => {
|
||||||
// make sure anything unique is the correct key
|
// make sure anything unique is the correct key
|
||||||
if (
|
if (
|
||||||
|
@ -60,35 +68,36 @@ exports.getLinkDocuments = async function (args) {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (includeDocs) {
|
if (includeDocs) {
|
||||||
return linkRows.map(row => row.doc)
|
return linkRows.map(row => row.doc) as LinkDocument[]
|
||||||
} else {
|
} else {
|
||||||
return linkRows.map(row => row.value)
|
return linkRows.map(row => row.value) as LinkDocumentValue[]
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
// check if the view doesn't exist, it should for all new instances
|
// check if the view doesn't exist, it should for all new instances
|
||||||
if (err != null && err.name === "not_found") {
|
if (err != null && err.name === "not_found") {
|
||||||
await exports.createLinkView()
|
await createLinkView()
|
||||||
return exports.getLinkDocuments(arguments[0])
|
return getLinkDocuments(arguments[0])
|
||||||
} else {
|
} else {
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
Sentry.captureException(err)
|
logging.logAlert("Failed to get link documents", err)
|
||||||
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getUniqueByProp = (array, prop) => {
|
export function getUniqueByProp(array: any[], prop: string) {
|
||||||
return array.filter((obj, pos, arr) => {
|
return array.filter((obj, pos, arr) => {
|
||||||
return arr.map(mapObj => mapObj[prop]).indexOf(obj[prop]) === pos
|
return arr.map(mapObj => mapObj[prop]).indexOf(obj[prop]) === pos
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getLinkedTableIDs = table => {
|
export function getLinkedTableIDs(table: Table) {
|
||||||
return Object.values(table.schema)
|
return Object.values(table.schema)
|
||||||
.filter(column => column.type === FieldTypes.LINK)
|
.filter((column: FieldSchema) => column.type === FieldTypes.LINK)
|
||||||
.map(column => column.tableId)
|
.map(column => column.tableId)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getLinkedTable = async (id, tables) => {
|
export async function getLinkedTable(id: string, tables: Table[]) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
let linkedTable = tables.find(table => table._id === id)
|
let linkedTable = tables.find(table => table._id === id)
|
||||||
if (linkedTable) {
|
if (linkedTable) {
|
||||||
|
@ -101,7 +110,7 @@ exports.getLinkedTable = async (id, tables) => {
|
||||||
return linkedTable
|
return linkedTable
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getRelatedTableForField = (table, fieldName) => {
|
export function getRelatedTableForField(table: Table, fieldName: string) {
|
||||||
// look to see if its on the table, straight in the schema
|
// look to see if its on the table, straight in the schema
|
||||||
const field = table.schema[fieldName]
|
const field = table.schema[fieldName]
|
||||||
if (field != null) {
|
if (field != null) {
|
|
@ -1,4 +1,6 @@
|
||||||
export interface LinkDocument {
|
import { Document } from "../document"
|
||||||
|
|
||||||
|
export interface LinkDocument extends Document {
|
||||||
type: string
|
type: string
|
||||||
doc1: {
|
doc1: {
|
||||||
rowId: string
|
rowId: string
|
||||||
|
@ -11,3 +13,9 @@ export interface LinkDocument {
|
||||||
tableId: string
|
tableId: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LinkDocumentValue {
|
||||||
|
id: string
|
||||||
|
thisId: string
|
||||||
|
fieldName: string
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue