Merge branch 'master' of github.com:Budibase/budibase into views-openapi
This commit is contained in:
commit
6fe4f16845
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||||
"version": "2.33.2",
|
"version": "2.33.3",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|
|
@ -11,11 +11,13 @@ import {
|
||||||
IncludeRelationship,
|
IncludeRelationship,
|
||||||
InternalSearchFilterOperator,
|
InternalSearchFilterOperator,
|
||||||
isManyToOne,
|
isManyToOne,
|
||||||
|
isOneToMany,
|
||||||
OneToManyRelationshipFieldMetadata,
|
OneToManyRelationshipFieldMetadata,
|
||||||
Operation,
|
Operation,
|
||||||
PaginationJson,
|
PaginationJson,
|
||||||
QueryJson,
|
QueryJson,
|
||||||
RelationshipFieldMetadata,
|
RelationshipFieldMetadata,
|
||||||
|
RelationshipType,
|
||||||
Row,
|
Row,
|
||||||
SearchFilters,
|
SearchFilters,
|
||||||
SortJson,
|
SortJson,
|
||||||
|
@ -50,13 +52,15 @@ import sdk from "../../../sdk"
|
||||||
import env from "../../../environment"
|
import env from "../../../environment"
|
||||||
import { makeExternalQuery } from "../../../integrations/base/query"
|
import { makeExternalQuery } from "../../../integrations/base/query"
|
||||||
import { dataFilters, helpers } from "@budibase/shared-core"
|
import { dataFilters, helpers } from "@budibase/shared-core"
|
||||||
|
import { isRelationshipColumn } from "../../../db/utils"
|
||||||
|
|
||||||
export interface ManyRelationship {
|
interface ManyRelationship {
|
||||||
tableId?: string
|
tableId?: string
|
||||||
id?: string
|
id?: string
|
||||||
isUpdate?: boolean
|
isUpdate?: boolean
|
||||||
key: string
|
key: string
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
|
relationshipType: RelationshipType
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RunConfig {
|
export interface RunConfig {
|
||||||
|
@ -384,6 +388,7 @@ export class ExternalRequest<T extends Operation> {
|
||||||
[otherKey]: breakRowIdField(relationship)[0],
|
[otherKey]: breakRowIdField(relationship)[0],
|
||||||
// leave the ID for enrichment later
|
// leave the ID for enrichment later
|
||||||
[thisKey]: `{{ literal ${tablePrimary} }}`,
|
[thisKey]: `{{ literal ${tablePrimary} }}`,
|
||||||
|
relationshipType: RelationshipType.MANY_TO_MANY,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -400,6 +405,7 @@ export class ExternalRequest<T extends Operation> {
|
||||||
[thisKey]: breakRowIdField(relationship)[0],
|
[thisKey]: breakRowIdField(relationship)[0],
|
||||||
// leave the ID for enrichment later
|
// leave the ID for enrichment later
|
||||||
[otherKey]: `{{ literal ${tablePrimary} }}`,
|
[otherKey]: `{{ literal ${tablePrimary} }}`,
|
||||||
|
relationshipType: RelationshipType.MANY_TO_ONE,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -420,14 +426,30 @@ export class ExternalRequest<T extends Operation> {
|
||||||
return { row: newRow as T, manyRelationships }
|
return { row: newRow as T, manyRelationships }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getLookupRelationsKey(relationship: {
|
||||||
|
relationshipType: RelationshipType
|
||||||
|
fieldName: string
|
||||||
|
through?: string
|
||||||
|
}) {
|
||||||
|
if (relationship.relationshipType === RelationshipType.MANY_TO_MANY) {
|
||||||
|
return `${relationship.through}_${relationship.fieldName}`
|
||||||
|
}
|
||||||
|
return relationship.fieldName
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This is a cached lookup, of relationship records, this is mainly for creating/deleting junction
|
* This is a cached lookup, of relationship records, this is mainly for creating/deleting junction
|
||||||
* information.
|
* information.
|
||||||
*/
|
*/
|
||||||
async lookupRelations(tableId: string, row: Row) {
|
private async lookupRelations(tableId: string, row: Row) {
|
||||||
const related: {
|
const related: Record<
|
||||||
[key: string]: { rows: Row[]; isMany: boolean; tableId: string }
|
string,
|
||||||
} = {}
|
{
|
||||||
|
rows: Row[]
|
||||||
|
isMany: boolean
|
||||||
|
tableId: string
|
||||||
|
}
|
||||||
|
> = {}
|
||||||
|
|
||||||
const { tableName } = breakExternalTableId(tableId)
|
const { tableName } = breakExternalTableId(tableId)
|
||||||
const table = this.tables[tableName]
|
const table = this.tables[tableName]
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -459,11 +481,8 @@ export class ExternalRequest<T extends Operation> {
|
||||||
"Unable to lookup relationships - undefined column properties."
|
"Unable to lookup relationships - undefined column properties."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const { tableName: relatedTableName } =
|
|
||||||
breakExternalTableId(relatedTableId)
|
if (!lookupField || !row?.[lookupField]) {
|
||||||
// @ts-ignore
|
|
||||||
const linkPrimaryKey = this.tables[relatedTableName].primary[0]
|
|
||||||
if (!lookupField || !row?.[lookupField] == null) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const endpoint = getEndpoint(relatedTableId, Operation.READ)
|
const endpoint = getEndpoint(relatedTableId, Operation.READ)
|
||||||
|
@ -487,10 +506,8 @@ export class ExternalRequest<T extends Operation> {
|
||||||
!Array.isArray(response) || isKnexEmptyReadResponse(response)
|
!Array.isArray(response) || isKnexEmptyReadResponse(response)
|
||||||
? []
|
? []
|
||||||
: response
|
: response
|
||||||
const storeTo = isManyToMany(field)
|
|
||||||
? field.throughFrom || linkPrimaryKey
|
related[this.getLookupRelationsKey(field)] = {
|
||||||
: fieldName
|
|
||||||
related[storeTo] = {
|
|
||||||
rows,
|
rows,
|
||||||
isMany: isManyToMany(field),
|
isMany: isManyToMany(field),
|
||||||
tableId: relatedTableId,
|
tableId: relatedTableId,
|
||||||
|
@ -518,7 +535,8 @@ export class ExternalRequest<T extends Operation> {
|
||||||
const promises = []
|
const promises = []
|
||||||
const related = await this.lookupRelations(mainTableId, row)
|
const related = await this.lookupRelations(mainTableId, row)
|
||||||
for (let relationship of relationships) {
|
for (let relationship of relationships) {
|
||||||
const { key, tableId, isUpdate, id, ...rest } = relationship
|
const { key, tableId, isUpdate, id, relationshipType, ...rest } =
|
||||||
|
relationship
|
||||||
const body: { [key: string]: any } = processObjectSync(rest, row, {})
|
const body: { [key: string]: any } = processObjectSync(rest, row, {})
|
||||||
const linkTable = this.getTable(tableId)
|
const linkTable = this.getTable(tableId)
|
||||||
const relationshipPrimary = linkTable?.primary || []
|
const relationshipPrimary = linkTable?.primary || []
|
||||||
|
@ -529,7 +547,14 @@ export class ExternalRequest<T extends Operation> {
|
||||||
|
|
||||||
const linkSecondary = relationshipPrimary[1]
|
const linkSecondary = relationshipPrimary[1]
|
||||||
|
|
||||||
const rows = related[key]?.rows || []
|
const rows =
|
||||||
|
related[
|
||||||
|
this.getLookupRelationsKey({
|
||||||
|
relationshipType,
|
||||||
|
fieldName: key,
|
||||||
|
through: relationship.tableId,
|
||||||
|
})
|
||||||
|
]?.rows || []
|
||||||
|
|
||||||
const relationshipMatchPredicate = ({
|
const relationshipMatchPredicate = ({
|
||||||
row,
|
row,
|
||||||
|
@ -574,12 +599,12 @@ export class ExternalRequest<T extends Operation> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// finally cleanup anything that needs to be removed
|
// finally cleanup anything that needs to be removed
|
||||||
for (let [colName, { isMany, rows, tableId }] of Object.entries(related)) {
|
for (const [field, { isMany, rows, tableId }] of Object.entries(related)) {
|
||||||
const table: Table | undefined = this.getTable(tableId)
|
const table: Table | undefined = this.getTable(tableId)
|
||||||
// if it's not the foreign key skip it, nothing to do
|
// if it's not the foreign key skip it, nothing to do
|
||||||
if (
|
if (
|
||||||
!table ||
|
!table ||
|
||||||
(!isMany && table.primary && table.primary.indexOf(colName) !== -1)
|
(!isMany && table.primary && table.primary.indexOf(field) !== -1)
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -587,7 +612,7 @@ export class ExternalRequest<T extends Operation> {
|
||||||
const rowId = generateIdForRow(row, table)
|
const rowId = generateIdForRow(row, table)
|
||||||
const promise: Promise<any> = isMany
|
const promise: Promise<any> = isMany
|
||||||
? this.removeManyToManyRelationships(rowId, table)
|
? this.removeManyToManyRelationships(rowId, table)
|
||||||
: this.removeOneToManyRelationships(rowId, table, colName)
|
: this.removeOneToManyRelationships(rowId, table, field)
|
||||||
if (promise) {
|
if (promise) {
|
||||||
promises.push(promise)
|
promises.push(promise)
|
||||||
}
|
}
|
||||||
|
@ -599,23 +624,24 @@ export class ExternalRequest<T extends Operation> {
|
||||||
async removeRelationshipsToRow(table: Table, rowId: string) {
|
async removeRelationshipsToRow(table: Table, rowId: string) {
|
||||||
const row = await this.getRow(table, rowId)
|
const row = await this.getRow(table, rowId)
|
||||||
const related = await this.lookupRelations(table._id!, row)
|
const related = await this.lookupRelations(table._id!, row)
|
||||||
for (let column of Object.values(table.schema)) {
|
for (const column of Object.values(table.schema)) {
|
||||||
const relationshipColumn = column as RelationshipFieldMetadata
|
if (!isRelationshipColumn(column) || isOneToMany(column)) {
|
||||||
if (!isManyToOne(relationshipColumn)) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const { rows, isMany, tableId } = related[relationshipColumn.fieldName]
|
|
||||||
|
const relatedByTable = related[this.getLookupRelationsKey(column)]
|
||||||
|
if (!relatedByTable) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const { rows, isMany, tableId } = relatedByTable
|
||||||
const table = this.getTable(tableId)!
|
const table = this.getTable(tableId)!
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
rows.map(row => {
|
rows.map(row => {
|
||||||
const rowId = generateIdForRow(row, table)
|
const rowId = generateIdForRow(row, table)
|
||||||
return isMany
|
return isMany
|
||||||
? this.removeManyToManyRelationships(rowId, table)
|
? this.removeManyToManyRelationships(rowId, table)
|
||||||
: this.removeOneToManyRelationships(
|
: this.removeOneToManyRelationships(rowId, table, column.fieldName)
|
||||||
rowId,
|
|
||||||
table,
|
|
||||||
relationshipColumn.fieldName
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,15 @@ import {
|
||||||
TRIGGER_DEFINITIONS,
|
TRIGGER_DEFINITIONS,
|
||||||
BUILTIN_ACTION_DEFINITIONS,
|
BUILTIN_ACTION_DEFINITIONS,
|
||||||
} from "../../../automations"
|
} from "../../../automations"
|
||||||
import { events } from "@budibase/backend-core"
|
import { configs, context, events } from "@budibase/backend-core"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import { Automation, FieldType, Table } from "@budibase/types"
|
import {
|
||||||
|
Automation,
|
||||||
|
ConfigType,
|
||||||
|
FieldType,
|
||||||
|
SettingsConfig,
|
||||||
|
Table,
|
||||||
|
} from "@budibase/types"
|
||||||
import { mocks } from "@budibase/backend-core/tests"
|
import { mocks } from "@budibase/backend-core/tests"
|
||||||
import { FilterConditions } from "../../../automations/steps/filter"
|
import { FilterConditions } from "../../../automations/steps/filter"
|
||||||
import { removeDeprecated } from "../../../automations/utils"
|
import { removeDeprecated } from "../../../automations/utils"
|
||||||
|
@ -39,8 +45,7 @@ describe("/automations", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// @ts-ignore
|
jest.clearAllMocks()
|
||||||
events.automation.deleted.mockClear()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("get definitions", () => {
|
describe("get definitions", () => {
|
||||||
|
@ -244,6 +249,59 @@ describe("/automations", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("run", () => {
|
||||||
|
let oldConfig: SettingsConfig
|
||||||
|
beforeAll(async () => {
|
||||||
|
await context.doInTenant(config.getTenantId(), async () => {
|
||||||
|
oldConfig = await configs.getSettingsConfigDoc()
|
||||||
|
|
||||||
|
const settings: SettingsConfig = {
|
||||||
|
_id: oldConfig._id,
|
||||||
|
_rev: oldConfig._rev,
|
||||||
|
type: ConfigType.SETTINGS,
|
||||||
|
config: {
|
||||||
|
platformUrl: "https://example.com",
|
||||||
|
logoUrl: "https://example.com/logo.png",
|
||||||
|
company: "Test Company",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const saved = await configs.save(settings)
|
||||||
|
oldConfig._rev = saved.rev
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await context.doInTenant(config.getTenantId(), async () => {
|
||||||
|
await configs.save(oldConfig)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to access platformUrl, logoUrl and company in the automation", async () => {
|
||||||
|
const result = await createAutomationBuilder({
|
||||||
|
name: "Test Automation",
|
||||||
|
appId: config.getAppId(),
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
.appAction({ fields: {} })
|
||||||
|
.serverLog({
|
||||||
|
text: "{{ settings.url }}",
|
||||||
|
})
|
||||||
|
.serverLog({
|
||||||
|
text: "{{ settings.logo }}",
|
||||||
|
})
|
||||||
|
.serverLog({
|
||||||
|
text: "{{ settings.company }}",
|
||||||
|
})
|
||||||
|
.run()
|
||||||
|
|
||||||
|
expect(result.steps[0].outputs.message).toEndWith("https://example.com")
|
||||||
|
expect(result.steps[1].outputs.message).toEndWith(
|
||||||
|
"https://example.com/logo.png"
|
||||||
|
)
|
||||||
|
expect(result.steps[2].outputs.message).toEndWith("Test Company")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("test", () => {
|
describe("test", () => {
|
||||||
it("tests the automation successfully", async () => {
|
it("tests the automation successfully", async () => {
|
||||||
let table = await config.createTable()
|
let table = await config.createTable()
|
||||||
|
|
|
@ -952,6 +952,105 @@ describe.each([
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
!isLucene &&
|
||||||
|
describe("relations to same table", () => {
|
||||||
|
let relatedRows: Row[]
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const relatedTable = await config.api.table.save(
|
||||||
|
defaultTable({
|
||||||
|
schema: {
|
||||||
|
name: { name: "name", type: FieldType.STRING },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const relatedTableId = relatedTable._id!
|
||||||
|
table = await config.api.table.save(
|
||||||
|
defaultTable({
|
||||||
|
schema: {
|
||||||
|
name: { name: "name", type: FieldType.STRING },
|
||||||
|
related1: {
|
||||||
|
type: FieldType.LINK,
|
||||||
|
name: "related1",
|
||||||
|
fieldName: "main1",
|
||||||
|
tableId: relatedTableId,
|
||||||
|
relationshipType: RelationshipType.MANY_TO_MANY,
|
||||||
|
},
|
||||||
|
related2: {
|
||||||
|
type: FieldType.LINK,
|
||||||
|
name: "related2",
|
||||||
|
fieldName: "main2",
|
||||||
|
tableId: relatedTableId,
|
||||||
|
relationshipType: RelationshipType.MANY_TO_MANY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
relatedRows = await Promise.all([
|
||||||
|
config.api.row.save(relatedTableId, { name: "foo" }),
|
||||||
|
config.api.row.save(relatedTableId, { name: "bar" }),
|
||||||
|
config.api.row.save(relatedTableId, { name: "baz" }),
|
||||||
|
config.api.row.save(relatedTableId, { name: "boo" }),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can create rows with both relationships", async () => {
|
||||||
|
const row = await config.api.row.save(table._id!, {
|
||||||
|
name: "test",
|
||||||
|
related1: [relatedRows[0]._id!],
|
||||||
|
related2: [relatedRows[1]._id!],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(row).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "test",
|
||||||
|
related1: [
|
||||||
|
{
|
||||||
|
_id: relatedRows[0]._id,
|
||||||
|
primaryDisplay: relatedRows[0].name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
related2: [
|
||||||
|
{
|
||||||
|
_id: relatedRows[1]._id,
|
||||||
|
primaryDisplay: relatedRows[1].name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can create rows with no relationships", async () => {
|
||||||
|
const row = await config.api.row.save(table._id!, {
|
||||||
|
name: "test",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(row.related1).toBeUndefined()
|
||||||
|
expect(row.related2).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can create rows with only one relationships field", async () => {
|
||||||
|
const row = await config.api.row.save(table._id!, {
|
||||||
|
name: "test",
|
||||||
|
related1: [],
|
||||||
|
related2: [relatedRows[1]._id!],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(row).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "test",
|
||||||
|
related2: [
|
||||||
|
{
|
||||||
|
_id: relatedRows[1]._id,
|
||||||
|
primaryDisplay: relatedRows[1].name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect(row.related1).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("get", () => {
|
describe("get", () => {
|
||||||
|
@ -1054,6 +1153,134 @@ describe.each([
|
||||||
const rows = await config.api.row.fetch(table._id!)
|
const rows = await config.api.row.fetch(table._id!)
|
||||||
expect(rows).toHaveLength(1)
|
expect(rows).toHaveLength(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
!isLucene &&
|
||||||
|
describe("relations to same table", () => {
|
||||||
|
let relatedRows: Row[]
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const relatedTable = await config.api.table.save(
|
||||||
|
defaultTable({
|
||||||
|
schema: {
|
||||||
|
name: { name: "name", type: FieldType.STRING },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const relatedTableId = relatedTable._id!
|
||||||
|
table = await config.api.table.save(
|
||||||
|
defaultTable({
|
||||||
|
schema: {
|
||||||
|
name: { name: "name", type: FieldType.STRING },
|
||||||
|
related1: {
|
||||||
|
type: FieldType.LINK,
|
||||||
|
name: "related1",
|
||||||
|
fieldName: "main1",
|
||||||
|
tableId: relatedTableId,
|
||||||
|
relationshipType: RelationshipType.MANY_TO_MANY,
|
||||||
|
},
|
||||||
|
related2: {
|
||||||
|
type: FieldType.LINK,
|
||||||
|
name: "related2",
|
||||||
|
fieldName: "main2",
|
||||||
|
tableId: relatedTableId,
|
||||||
|
relationshipType: RelationshipType.MANY_TO_MANY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
relatedRows = await Promise.all([
|
||||||
|
config.api.row.save(relatedTableId, { name: "foo" }),
|
||||||
|
config.api.row.save(relatedTableId, { name: "bar" }),
|
||||||
|
config.api.row.save(relatedTableId, { name: "baz" }),
|
||||||
|
config.api.row.save(relatedTableId, { name: "boo" }),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can edit rows with both relationships", async () => {
|
||||||
|
let row = await config.api.row.save(table._id!, {
|
||||||
|
name: "test",
|
||||||
|
related1: [relatedRows[0]._id!],
|
||||||
|
related2: [relatedRows[1]._id!],
|
||||||
|
})
|
||||||
|
|
||||||
|
row = await config.api.row.save(table._id!, {
|
||||||
|
...row,
|
||||||
|
related1: [relatedRows[0]._id!, relatedRows[1]._id!],
|
||||||
|
related2: [relatedRows[2]._id!],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(row).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "test",
|
||||||
|
related1: expect.arrayContaining([
|
||||||
|
{
|
||||||
|
_id: relatedRows[0]._id,
|
||||||
|
primaryDisplay: relatedRows[0].name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: relatedRows[1]._id,
|
||||||
|
primaryDisplay: relatedRows[1].name,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
related2: [
|
||||||
|
{
|
||||||
|
_id: relatedRows[2]._id,
|
||||||
|
primaryDisplay: relatedRows[2].name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can drop existing relationship", async () => {
|
||||||
|
let row = await config.api.row.save(table._id!, {
|
||||||
|
name: "test",
|
||||||
|
related1: [relatedRows[0]._id!],
|
||||||
|
related2: [relatedRows[1]._id!],
|
||||||
|
})
|
||||||
|
|
||||||
|
row = await config.api.row.save(table._id!, {
|
||||||
|
...row,
|
||||||
|
related1: [],
|
||||||
|
related2: [relatedRows[2]._id!],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(row).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "test",
|
||||||
|
related2: [
|
||||||
|
{
|
||||||
|
_id: relatedRows[2]._id,
|
||||||
|
primaryDisplay: relatedRows[2].name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect(row.related1).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can drop both relationships", async () => {
|
||||||
|
let row = await config.api.row.save(table._id!, {
|
||||||
|
name: "test",
|
||||||
|
related1: [relatedRows[0]._id!],
|
||||||
|
related2: [relatedRows[1]._id!],
|
||||||
|
})
|
||||||
|
|
||||||
|
row = await config.api.row.save(table._id!, {
|
||||||
|
...row,
|
||||||
|
related1: [],
|
||||||
|
related2: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(row).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "test",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect(row.related1).toBeUndefined()
|
||||||
|
expect(row.related2).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("patch", () => {
|
describe("patch", () => {
|
||||||
|
@ -1330,6 +1557,73 @@ describe.each([
|
||||||
)
|
)
|
||||||
expect(res.length).toEqual(2)
|
expect(res.length).toEqual(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
!isLucene &&
|
||||||
|
describe("relations to same table", () => {
|
||||||
|
let relatedRows: Row[]
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const relatedTable = await config.api.table.save(
|
||||||
|
defaultTable({
|
||||||
|
schema: {
|
||||||
|
name: { name: "name", type: FieldType.STRING },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const relatedTableId = relatedTable._id!
|
||||||
|
table = await config.api.table.save(
|
||||||
|
defaultTable({
|
||||||
|
schema: {
|
||||||
|
name: { name: "name", type: FieldType.STRING },
|
||||||
|
related1: {
|
||||||
|
type: FieldType.LINK,
|
||||||
|
name: "related1",
|
||||||
|
fieldName: "main1",
|
||||||
|
tableId: relatedTableId,
|
||||||
|
relationshipType: RelationshipType.MANY_TO_MANY,
|
||||||
|
},
|
||||||
|
related2: {
|
||||||
|
type: FieldType.LINK,
|
||||||
|
name: "related2",
|
||||||
|
fieldName: "main2",
|
||||||
|
tableId: relatedTableId,
|
||||||
|
relationshipType: RelationshipType.MANY_TO_MANY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
relatedRows = await Promise.all([
|
||||||
|
config.api.row.save(relatedTableId, { name: "foo" }),
|
||||||
|
config.api.row.save(relatedTableId, { name: "bar" }),
|
||||||
|
config.api.row.save(relatedTableId, { name: "baz" }),
|
||||||
|
config.api.row.save(relatedTableId, { name: "boo" }),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can delete rows with both relationships", async () => {
|
||||||
|
const row = await config.api.row.save(table._id!, {
|
||||||
|
name: "test",
|
||||||
|
related1: [relatedRows[0]._id!],
|
||||||
|
related2: [relatedRows[1]._id!],
|
||||||
|
})
|
||||||
|
|
||||||
|
await config.api.row.delete(table._id!, { _id: row._id! })
|
||||||
|
|
||||||
|
await config.api.row.get(table._id!, row._id!, { status: 404 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can delete rows with empty relationships", async () => {
|
||||||
|
const row = await config.api.row.save(table._id!, {
|
||||||
|
name: "test",
|
||||||
|
related1: [],
|
||||||
|
related2: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
await config.api.row.delete(table._id!, { _id: row._id! })
|
||||||
|
|
||||||
|
await config.api.row.get(table._id!, row._id!, { status: 404 })
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("validate", () => {
|
describe("validate", () => {
|
||||||
|
@ -2796,7 +3090,7 @@ describe.each([
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
["from original saved row", (row: Row) => row],
|
["from original saved row", (row: Row) => row],
|
||||||
["from updated row", (row: Row) => config.api.row.save(viewId, row)],
|
["from updated row", (row: Row) => config.api.row.save(viewId, row)],
|
||||||
]
|
]
|
||||||
|
|
||||||
it.each(testScenarios)(
|
it.each(testScenarios)(
|
||||||
|
|
|
@ -225,7 +225,9 @@ class AutomationBuilder extends BaseStepBuilder {
|
||||||
private triggerOutputs: any
|
private triggerOutputs: any
|
||||||
private triggerSet: boolean = false
|
private triggerSet: boolean = false
|
||||||
|
|
||||||
constructor(options: { name?: string; appId?: string } = {}) {
|
constructor(
|
||||||
|
options: { name?: string; appId?: string; config?: TestConfiguration } = {}
|
||||||
|
) {
|
||||||
super()
|
super()
|
||||||
this.automationConfig = {
|
this.automationConfig = {
|
||||||
name: options.name || `Test Automation ${uuidv4()}`,
|
name: options.name || `Test Automation ${uuidv4()}`,
|
||||||
|
@ -237,7 +239,7 @@ class AutomationBuilder extends BaseStepBuilder {
|
||||||
type: "automation",
|
type: "automation",
|
||||||
appId: options.appId ?? setup.getConfig().getAppId(),
|
appId: options.appId ?? setup.getConfig().getAppId(),
|
||||||
}
|
}
|
||||||
this.config = setup.getConfig()
|
this.config = options.config || setup.getConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TRIGGERS
|
// TRIGGERS
|
||||||
|
@ -347,6 +349,7 @@ class AutomationBuilder extends BaseStepBuilder {
|
||||||
export function createAutomationBuilder(options?: {
|
export function createAutomationBuilder(options?: {
|
||||||
name?: string
|
name?: string
|
||||||
appId?: string
|
appId?: string
|
||||||
|
config?: TestConfiguration
|
||||||
}) {
|
}) {
|
||||||
return new AutomationBuilder(options)
|
return new AutomationBuilder(options)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,4 +20,9 @@ export interface AutomationContext extends AutomationResults {
|
||||||
env?: Record<string, string>
|
env?: Record<string, string>
|
||||||
user?: UserBindings
|
user?: UserBindings
|
||||||
trigger: any
|
trigger: any
|
||||||
|
settings?: {
|
||||||
|
url?: string
|
||||||
|
logo?: string
|
||||||
|
company?: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ import {
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { AutomationContext, TriggerOutput } from "../definitions/automations"
|
import { AutomationContext, TriggerOutput } from "../definitions/automations"
|
||||||
import { WorkerCallback } from "./definitions"
|
import { WorkerCallback } from "./definitions"
|
||||||
import { context, logging } from "@budibase/backend-core"
|
import { context, logging, configs } from "@budibase/backend-core"
|
||||||
import { processObject, processStringSync } from "@budibase/string-templates"
|
import { processObject, processStringSync } from "@budibase/string-templates"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { performance } from "perf_hooks"
|
import { performance } from "perf_hooks"
|
||||||
|
@ -263,6 +263,18 @@ class Orchestrator {
|
||||||
this.context.env = await sdkUtils.getEnvironmentVariables()
|
this.context.env = await sdkUtils.getEnvironmentVariables()
|
||||||
this.context.user = this.currentUser
|
this.context.user = this.currentUser
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { config } = await configs.getSettingsConfigDoc()
|
||||||
|
this.context.settings = {
|
||||||
|
url: config.platformUrl,
|
||||||
|
logo: config.logoUrl,
|
||||||
|
company: config.company,
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// if settings doc doesn't exist, make the settings blank
|
||||||
|
this.context.settings = {}
|
||||||
|
}
|
||||||
|
|
||||||
let metadata
|
let metadata
|
||||||
|
|
||||||
// check if this is a recurring automation,
|
// check if this is a recurring automation,
|
||||||
|
|
22
yarn.lock
22
yarn.lock
|
@ -2051,7 +2051,7 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@budibase/backend-core@2.32.11":
|
"@budibase/backend-core@2.33.2":
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/nano" "10.1.5"
|
"@budibase/nano" "10.1.5"
|
||||||
|
@ -2132,15 +2132,15 @@
|
||||||
through2 "^2.0.0"
|
through2 "^2.0.0"
|
||||||
|
|
||||||
"@budibase/pro@npm:@budibase/pro@latest":
|
"@budibase/pro@npm:@budibase/pro@latest":
|
||||||
version "2.32.11"
|
version "2.33.2"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.32.11.tgz#c94d534f829ca0ef252677757e157a7e58b87b4d"
|
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.33.2.tgz#5c2012f7b2bf0fd871cda1ad37ad7a0442c84658"
|
||||||
integrity sha512-mOkqJpqHKWsfTWZwWcvBCYFUIluSUHltQNinc1ZRsg9rC3OKoHSDop6gzm744++H/GzGRN8V86kLhCgtNIlkpA==
|
integrity sha512-lBB6Wfp6OIOHRlGq82WS9KxvEXRs/P2QlwJT0Aj9PhmkQFsnXm2r8d18f0xTGvcflD+iR7XGP/k56JlCanmhQg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@anthropic-ai/sdk" "^0.27.3"
|
"@anthropic-ai/sdk" "^0.27.3"
|
||||||
"@budibase/backend-core" "2.32.11"
|
"@budibase/backend-core" "2.33.2"
|
||||||
"@budibase/shared-core" "2.32.11"
|
"@budibase/shared-core" "2.33.2"
|
||||||
"@budibase/string-templates" "2.32.11"
|
"@budibase/string-templates" "2.33.2"
|
||||||
"@budibase/types" "2.32.11"
|
"@budibase/types" "2.33.2"
|
||||||
"@koa/router" "8.0.8"
|
"@koa/router" "8.0.8"
|
||||||
bull "4.10.1"
|
bull "4.10.1"
|
||||||
dd-trace "5.2.0"
|
dd-trace "5.2.0"
|
||||||
|
@ -2153,13 +2153,13 @@
|
||||||
scim-patch "^0.8.1"
|
scim-patch "^0.8.1"
|
||||||
scim2-parse-filter "^0.2.8"
|
scim2-parse-filter "^0.2.8"
|
||||||
|
|
||||||
"@budibase/shared-core@2.32.11":
|
"@budibase/shared-core@2.33.2":
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/types" "0.0.0"
|
"@budibase/types" "0.0.0"
|
||||||
cron-validate "1.4.5"
|
cron-validate "1.4.5"
|
||||||
|
|
||||||
"@budibase/string-templates@2.32.11":
|
"@budibase/string-templates@2.33.2":
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/handlebars-helpers" "^0.13.2"
|
"@budibase/handlebars-helpers" "^0.13.2"
|
||||||
|
@ -2167,7 +2167,7 @@
|
||||||
handlebars "^4.7.8"
|
handlebars "^4.7.8"
|
||||||
lodash.clonedeep "^4.5.0"
|
lodash.clonedeep "^4.5.0"
|
||||||
|
|
||||||
"@budibase/types@2.32.11":
|
"@budibase/types@2.33.2":
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
scim-patch "^0.8.1"
|
scim-patch "^0.8.1"
|
||||||
|
|
Loading…
Reference in New Issue