Merge branch 'test-oracle' of github.com:Budibase/budibase into test-oracle
This commit is contained in:
commit
8ee2e6d0de
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||
"version": "2.29.25",
|
||||
"version": "2.29.26",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*",
|
||||
|
|
|
@ -308,8 +308,12 @@ export class DatabaseImpl implements Database {
|
|||
}
|
||||
|
||||
async bulkDocs(documents: AnyDocument[]) {
|
||||
const now = new Date().toISOString()
|
||||
return this.performCall(db => {
|
||||
return () => db.bulk({ docs: documents })
|
||||
return () =>
|
||||
db.bulk({
|
||||
docs: documents.map(d => ({ createdAt: now, ...d, updatedAt: now })),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
import tk from "timekeeper"
|
||||
|
||||
import { DatabaseImpl } from ".."
|
||||
|
||||
import { generator, structures } from "../../../../tests"
|
||||
|
||||
const initialTime = new Date()
|
||||
tk.freeze(initialTime)
|
||||
|
||||
describe("DatabaseImpl", () => {
|
||||
const db = new DatabaseImpl(structures.db.id())
|
||||
|
||||
beforeEach(() => {
|
||||
tk.freeze(initialTime)
|
||||
})
|
||||
|
||||
describe("put", () => {
|
||||
it("persists createdAt and updatedAt fields", async () => {
|
||||
const id = generator.guid()
|
||||
await db.put({ _id: id })
|
||||
|
||||
expect(await db.get(id)).toEqual({
|
||||
_id: id,
|
||||
_rev: expect.any(String),
|
||||
createdAt: initialTime.toISOString(),
|
||||
updatedAt: initialTime.toISOString(),
|
||||
})
|
||||
})
|
||||
|
||||
it("updates updated at fields", async () => {
|
||||
const id = generator.guid()
|
||||
|
||||
await db.put({ _id: id })
|
||||
tk.travel(100)
|
||||
|
||||
await db.put({ ...(await db.get(id)), newValue: 123 })
|
||||
|
||||
expect(await db.get(id)).toEqual({
|
||||
_id: id,
|
||||
_rev: expect.any(String),
|
||||
newValue: 123,
|
||||
createdAt: initialTime.toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("bulkDocs", () => {
|
||||
it("persists createdAt and updatedAt fields", async () => {
|
||||
const ids = generator.unique(() => generator.guid(), 5)
|
||||
await db.bulkDocs(ids.map(id => ({ _id: id })))
|
||||
|
||||
for (const id of ids) {
|
||||
expect(await db.get(id)).toEqual({
|
||||
_id: id,
|
||||
_rev: expect.any(String),
|
||||
createdAt: initialTime.toISOString(),
|
||||
updatedAt: initialTime.toISOString(),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it("updates updated at fields", async () => {
|
||||
const ids = generator.unique(() => generator.guid(), 5)
|
||||
|
||||
await db.bulkDocs(ids.map(id => ({ _id: id })))
|
||||
tk.travel(100)
|
||||
|
||||
const docsToUpdate = await Promise.all(
|
||||
ids.map(async id => ({ ...(await db.get(id)), newValue: 123 }))
|
||||
)
|
||||
await db.bulkDocs(docsToUpdate)
|
||||
|
||||
for (const id of ids) {
|
||||
expect(await db.get(id)).toEqual({
|
||||
_id: id,
|
||||
_rev: expect.any(String),
|
||||
newValue: 123,
|
||||
createdAt: initialTime.toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it("keeps existing createdAt", async () => {
|
||||
const ids = generator.unique(() => generator.guid(), 2)
|
||||
|
||||
await db.bulkDocs(ids.map(id => ({ _id: id })))
|
||||
tk.travel(100)
|
||||
|
||||
const newDocs = generator
|
||||
.unique(() => generator.guid(), 3)
|
||||
.map(id => ({ _id: id }))
|
||||
const docsToUpdate = await Promise.all(
|
||||
ids.map(async id => ({ ...(await db.get(id)), newValue: 123 }))
|
||||
)
|
||||
await db.bulkDocs([...newDocs, ...docsToUpdate])
|
||||
|
||||
for (const { _id } of docsToUpdate) {
|
||||
expect(await db.get(_id)).toEqual({
|
||||
_id,
|
||||
_rev: expect.any(String),
|
||||
newValue: 123,
|
||||
createdAt: initialTime.toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
for (const { _id } of newDocs) {
|
||||
expect(await db.get(_id)).toEqual({
|
||||
_id,
|
||||
_rev: expect.any(String),
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
|
@ -54,6 +54,7 @@
|
|||
</div>
|
||||
<div class="controls">
|
||||
<div
|
||||
class:disabled={!$selectedAutomation?.definition?.trigger}
|
||||
on:click={() => {
|
||||
testDataModal.show()
|
||||
}}
|
||||
|
@ -80,6 +81,7 @@
|
|||
automation._id,
|
||||
automation.disabled
|
||||
)}
|
||||
disabled={!$selectedAutomation?.definition?.trigger}
|
||||
value={!automation.disabled}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
name: "Edit",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
disabled: !automation.definition.trigger,
|
||||
callback: updateAutomationDialog.show,
|
||||
},
|
||||
{
|
||||
|
@ -62,7 +62,9 @@
|
|||
name: "Duplicate",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: automation.definition.trigger.name === "Webhook",
|
||||
disabled:
|
||||
!automation.definition.trigger ||
|
||||
automation.definition.trigger?.name === "Webhook",
|
||||
callback: duplicateAutomation,
|
||||
},
|
||||
]
|
||||
|
@ -74,7 +76,7 @@
|
|||
name: automation.disabled ? "Activate" : "Pause",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
disabled: !automation.definition.trigger,
|
||||
callback: () => {
|
||||
automationStore.actions.toggleDisabled(
|
||||
automation._id,
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
.map(automation => ({
|
||||
...automation,
|
||||
displayName:
|
||||
$automationStore.automationDisplayData[automation._id].displayName ||
|
||||
$automationStore.automationDisplayData[automation._id]?.displayName ||
|
||||
automation.name,
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
|
@ -30,12 +30,13 @@
|
|||
})
|
||||
|
||||
$: groupedAutomations = filteredAutomations.reduce((acc, auto) => {
|
||||
acc[auto.definition.trigger.event] ??= {
|
||||
icon: auto.definition.trigger.icon,
|
||||
name: (auto.definition.trigger?.name || "").toUpperCase(),
|
||||
const catName = auto.definition?.trigger?.event || "No Trigger"
|
||||
acc[catName] ??= {
|
||||
icon: auto.definition?.trigger?.icon || "AlertCircle",
|
||||
name: (auto.definition?.trigger?.name || "No Trigger").toUpperCase(),
|
||||
entries: [],
|
||||
}
|
||||
acc[auto.definition.trigger.event].entries.push(auto)
|
||||
acc[catName].entries.push(auto)
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
|
|
|
@ -21,7 +21,9 @@
|
|||
|
||||
$: nameError =
|
||||
nameTouched && !name ? "Please specify a name for the automation." : null
|
||||
$: triggers = Object.entries($automationStore.blockDefinitions.TRIGGER)
|
||||
$: triggers = Object.entries(
|
||||
$automationStore.blockDefinitions.CREATABLE_TRIGGER
|
||||
)
|
||||
|
||||
async function createAutomation() {
|
||||
try {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
const { datasource } = getContext("grid")
|
||||
|
||||
$: triggers = $automationStore.blockDefinitions.TRIGGER
|
||||
$: triggers = $automationStore.blockDefinitions.CREATABLE_TRIGGER
|
||||
|
||||
$: table = $tables.list.find(table => table._id === $datasource.tableId)
|
||||
|
||||
|
|
|
@ -5,14 +5,16 @@ import { generate } from "shortid"
|
|||
import { createHistoryStore } from "stores/builder/history"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import { updateReferencesInObject } from "dataBinding"
|
||||
import { AutomationTriggerStepId } from "@budibase/types"
|
||||
|
||||
const initialAutomationState = {
|
||||
automations: [],
|
||||
testResults: null,
|
||||
showTestPanel: false,
|
||||
blockDefinitions: {
|
||||
TRIGGER: [],
|
||||
ACTION: [],
|
||||
TRIGGER: {},
|
||||
CREATABLE_TRIGGER: {},
|
||||
ACTION: {},
|
||||
},
|
||||
selectedAutomationId: null,
|
||||
automationDisplayData: {},
|
||||
|
@ -46,14 +48,29 @@ const updateStepReferences = (steps, modifiedIndex, action) => {
|
|||
})
|
||||
}
|
||||
|
||||
const getFinalDefinitions = (triggers, actions) => {
|
||||
const creatable = {}
|
||||
Object.entries(triggers).forEach(entry => {
|
||||
if (entry[0] === AutomationTriggerStepId.ROW_ACTION) {
|
||||
return
|
||||
}
|
||||
creatable[entry[0]] = entry[1]
|
||||
})
|
||||
return {
|
||||
TRIGGER: triggers,
|
||||
CREATABLE_TRIGGER: creatable,
|
||||
ACTION: actions,
|
||||
}
|
||||
}
|
||||
|
||||
const automationActions = store => ({
|
||||
definitions: async () => {
|
||||
const response = await API.getAutomationDefinitions()
|
||||
store.update(state => {
|
||||
state.blockDefinitions = {
|
||||
TRIGGER: response.trigger,
|
||||
ACTION: response.action,
|
||||
}
|
||||
state.blockDefinitions = getFinalDefinitions(
|
||||
response.trigger,
|
||||
response.action
|
||||
)
|
||||
return state
|
||||
})
|
||||
return response
|
||||
|
@ -69,10 +86,10 @@ const automationActions = store => ({
|
|||
return a.name < b.name ? -1 : 1
|
||||
})
|
||||
state.automationDisplayData = automationResponse.builderData
|
||||
state.blockDefinitions = {
|
||||
TRIGGER: definitions.trigger,
|
||||
ACTION: definitions.action,
|
||||
}
|
||||
state.blockDefinitions = getFinalDefinitions(
|
||||
definitions.trigger,
|
||||
definitions.action
|
||||
)
|
||||
return state
|
||||
})
|
||||
},
|
||||
|
@ -87,8 +104,6 @@ const automationActions = store => ({
|
|||
disabled: false,
|
||||
}
|
||||
const response = await store.actions.save(automation)
|
||||
await store.actions.fetch()
|
||||
store.actions.select(response._id)
|
||||
return response
|
||||
},
|
||||
duplicate: async automation => {
|
||||
|
@ -98,14 +113,13 @@ const automationActions = store => ({
|
|||
_id: undefined,
|
||||
_ref: undefined,
|
||||
})
|
||||
await store.actions.fetch()
|
||||
store.actions.select(response._id)
|
||||
return response
|
||||
},
|
||||
save: async automation => {
|
||||
const response = await API.updateAutomation(automation)
|
||||
|
||||
await store.actions.fetch()
|
||||
store.actions.select(response._id)
|
||||
return response.automation
|
||||
},
|
||||
delete: async automation => {
|
||||
|
@ -113,18 +127,22 @@ const automationActions = store => ({
|
|||
automationId: automation?._id,
|
||||
automationRev: automation?._rev,
|
||||
})
|
||||
|
||||
store.update(state => {
|
||||
// Remove the automation
|
||||
state.automations = state.automations.filter(
|
||||
x => x._id !== automation._id
|
||||
)
|
||||
|
||||
// Select a new automation if required
|
||||
if (automation._id === state.selectedAutomationId) {
|
||||
store.actions.select(state.automations[0]?._id)
|
||||
state.selectedAutomationId = state.automations[0]?._id || null
|
||||
}
|
||||
|
||||
// Clear out automationDisplayData for the automation
|
||||
delete state.automationDisplayData[automation._id]
|
||||
return state
|
||||
})
|
||||
await store.actions.fetch()
|
||||
},
|
||||
toggleDisabled: async automationId => {
|
||||
let automation
|
||||
|
@ -381,7 +399,7 @@ export const selectedAutomation = derived(automationStore, $automationStore => {
|
|||
export const selectedAutomationDisplayData = derived(
|
||||
[automationStore, selectedAutomation],
|
||||
([$automationStore, $selectedAutomation]) => {
|
||||
if (!$selectedAutomation._id) {
|
||||
if (!$selectedAutomation?._id) {
|
||||
return null
|
||||
}
|
||||
return $automationStore.automationDisplayData[$selectedAutomation._id]
|
||||
|
|
|
@ -14,6 +14,7 @@ import sdk from "../../../sdk"
|
|||
import { Automation, FieldType, Table } from "@budibase/types"
|
||||
import { mocks } from "@budibase/backend-core/tests"
|
||||
import { FilterConditions } from "../../../automations/steps/filter"
|
||||
import { removeDeprecated } from "../../../automations/utils"
|
||||
|
||||
const MAX_RETRIES = 4
|
||||
let {
|
||||
|
@ -69,14 +70,15 @@ describe("/automations", () => {
|
|||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
|
||||
let definitionsLength = Object.keys(BUILTIN_ACTION_DEFINITIONS).length
|
||||
definitionsLength-- // OUTGOING_WEBHOOK is deprecated
|
||||
let definitionsLength = Object.keys(
|
||||
removeDeprecated(BUILTIN_ACTION_DEFINITIONS)
|
||||
).length
|
||||
|
||||
expect(Object.keys(res.body.action).length).toBeGreaterThanOrEqual(
|
||||
definitionsLength
|
||||
)
|
||||
expect(Object.keys(res.body.trigger).length).toEqual(
|
||||
Object.keys(TRIGGER_DEFINITIONS).length
|
||||
Object.keys(removeDeprecated(TRIGGER_DEFINITIONS)).length
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
RelationshipType,
|
||||
Row,
|
||||
SaveTableRequest,
|
||||
SourceName,
|
||||
Table,
|
||||
TableSourceType,
|
||||
User,
|
||||
|
@ -33,7 +34,8 @@ describe.each([
|
|||
[DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)],
|
||||
[DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)],
|
||||
[DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)],
|
||||
])("/tables (%s)", (_, dsProvider) => {
|
||||
[DatabaseName.ORACLE, getDatasource(DatabaseName.ORACLE)],
|
||||
])("/tables (%s)", (name, dsProvider) => {
|
||||
const isInternal: boolean = !dsProvider
|
||||
let datasource: Datasource | undefined
|
||||
let config = setup.getConfig()
|
||||
|
@ -52,15 +54,20 @@ describe.each([
|
|||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it.each([
|
||||
let names = [
|
||||
"alphanum",
|
||||
"with spaces",
|
||||
"with-dashes",
|
||||
"with_underscores",
|
||||
'with "double quotes"',
|
||||
"with 'single quotes'",
|
||||
"with `backticks`",
|
||||
])("creates a table with name: %s", async name => {
|
||||
]
|
||||
|
||||
if (name !== DatabaseName.ORACLE) {
|
||||
names.push(`with "double quotes"`)
|
||||
names.push(`with 'single quotes'`)
|
||||
}
|
||||
|
||||
it.each(names)("creates a table with name: %s", async name => {
|
||||
const table = await config.api.table.save(
|
||||
tableForDatasource(datasource, { name })
|
||||
)
|
||||
|
|
|
@ -3,11 +3,15 @@ import { definitions } from "./triggerInfo"
|
|||
import { automationQueue } from "./bullboard"
|
||||
import { updateEntityMetadata } from "../utilities"
|
||||
import { MetadataTypes } from "../constants"
|
||||
import { db as dbCore, context, utils } from "@budibase/backend-core"
|
||||
import { context, db as dbCore, utils } from "@budibase/backend-core"
|
||||
import { getAutomationMetadataParams } from "../db/utils"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import { quotas } from "@budibase/pro"
|
||||
import { Automation, AutomationJob } from "@budibase/types"
|
||||
import {
|
||||
Automation,
|
||||
AutomationJob,
|
||||
AutomationStepSchema,
|
||||
} from "@budibase/types"
|
||||
import { automationsEnabled } from "../features"
|
||||
import { helpers, REBOOT_CRON } from "@budibase/shared-core"
|
||||
import tracer from "dd-trace"
|
||||
|
@ -111,7 +115,9 @@ export async function updateTestHistory(
|
|||
)
|
||||
}
|
||||
|
||||
export function removeDeprecated(definitions: any) {
|
||||
export function removeDeprecated(
|
||||
definitions: Record<string, AutomationStepSchema>
|
||||
) {
|
||||
const base = cloneDeep(definitions)
|
||||
for (let key of Object.keys(base)) {
|
||||
if (base[key].deprecated) {
|
||||
|
|
|
@ -83,7 +83,7 @@ export async function startContainer(container: GenericContainer) {
|
|||
container = container
|
||||
.withReuse()
|
||||
.withLabels({ "com.budibase": "true" })
|
||||
.withName(key)
|
||||
.withName(`${key}_testcontainer`)
|
||||
|
||||
let startedContainer: StartedTestContainer | undefined = undefined
|
||||
let lastError = undefined
|
||||
|
|
|
@ -87,10 +87,10 @@ export async function fetch() {
|
|||
include_docs: true,
|
||||
})
|
||||
)
|
||||
return response.rows
|
||||
.map(row => row.doc)
|
||||
.filter(doc => !!doc)
|
||||
.map(trimUnexpectedObjectFields)
|
||||
const automations: PersistedAutomation[] = response.rows
|
||||
.filter(row => !!row.doc)
|
||||
.map(row => row.doc!)
|
||||
return automations.map(trimUnexpectedObjectFields)
|
||||
}
|
||||
|
||||
export async function get(automationId: string) {
|
||||
|
|
|
@ -29,8 +29,7 @@ export async function getBuilderData(
|
|||
const rowActionNameCache: Record<string, TableRowActions> = {}
|
||||
async function getRowActionName(tableId: string, rowActionId: string) {
|
||||
if (!rowActionNameCache[tableId]) {
|
||||
const rowActions = await sdk.rowActions.get(tableId)
|
||||
rowActionNameCache[tableId] = rowActions
|
||||
rowActionNameCache[tableId] = await sdk.rowActions.get(tableId)
|
||||
}
|
||||
|
||||
return rowActionNameCache[tableId].actions[rowActionId]?.name
|
||||
|
@ -45,9 +44,11 @@ export async function getBuilderData(
|
|||
}
|
||||
|
||||
const { tableId, rowActionId } = automation.definition.trigger.inputs
|
||||
if (!tableId || !rowActionId) {
|
||||
continue
|
||||
}
|
||||
|
||||
const tableName = await getTableName(tableId)
|
||||
|
||||
const rowActionName = await getRowActionName(tableId, rowActionId)
|
||||
|
||||
result[automation._id!] = {
|
||||
|
|
|
@ -174,9 +174,7 @@ export interface AutomationStepSchema {
|
|||
deprecated?: boolean
|
||||
stepId: AutomationTriggerStepId | AutomationActionStepId
|
||||
blockToLoop?: string
|
||||
inputs: {
|
||||
[key: string]: any
|
||||
}
|
||||
inputs: Record<string, any>
|
||||
schema: {
|
||||
inputs: InputOutputBlock
|
||||
outputs: InputOutputBlock
|
||||
|
|
Loading…
Reference in New Issue