Typescript conversion of linked row controller.

This commit is contained in:
mike12345567 2022-11-25 18:54:57 +00:00
parent 7eac8fafd6
commit eeebd0fe70
6 changed files with 207 additions and 147 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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