Merge branch 'master' into fix/BUDI-7444
This commit is contained in:
commit
5240c2c2ca
|
@ -107,3 +107,4 @@ budibase-component
|
|||
budibase-datasource
|
||||
|
||||
*.iml
|
||||
.nx
|
|
@ -32,10 +32,14 @@
|
|||
import TourWrap from "components/portal/onboarding/TourWrap.svelte"
|
||||
import { TOUR_STEP_KEYS } from "components/portal/onboarding/tours.js"
|
||||
import { goto } from "@roxi/routify"
|
||||
import { onMount } from "svelte"
|
||||
import PosthogClient from "../../analytics/PosthogClient"
|
||||
|
||||
export let application
|
||||
export let loaded
|
||||
|
||||
const posthog = new PosthogClient(process.env.POSTHOG_TOKEN)
|
||||
|
||||
let unpublishModal
|
||||
let updateAppModal
|
||||
let revertModal
|
||||
|
@ -44,6 +48,7 @@
|
|||
let appActionPopoverOpen = false
|
||||
let appActionPopoverAnchor
|
||||
let publishing = false
|
||||
let showNpsSurvey = false
|
||||
let lastOpened
|
||||
|
||||
$: filteredApps = $appsStore.apps.filter(app => app.devId === application)
|
||||
|
@ -98,6 +103,7 @@
|
|||
type: "success",
|
||||
icon: "GlobeCheck",
|
||||
})
|
||||
showNpsSurvey = true
|
||||
await completePublish()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
@ -148,6 +154,10 @@
|
|||
notifications.error("Error refreshing app")
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
posthog.init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
|
@ -345,6 +355,10 @@
|
|||
<RevertModal bind:this={revertModal} />
|
||||
<VersionModal hideIcon bind:this={versionModal} />
|
||||
|
||||
{#if showNpsSurvey}
|
||||
<div class="nps-survey" />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.app-action-popover-content {
|
||||
padding: var(--spacing-xl);
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
module MongoMock {
|
||||
const mongodb: any = {}
|
||||
|
||||
mongodb.MongoClient = function () {
|
||||
this.connect = jest.fn()
|
||||
this.close = jest.fn()
|
||||
this.insertOne = jest.fn()
|
||||
this.insertMany = jest.fn(() => ({ toArray: () => [] }))
|
||||
this.find = jest.fn(() => ({ toArray: () => [] }))
|
||||
this.findOne = jest.fn()
|
||||
this.findOneAndUpdate = jest.fn()
|
||||
this.count = jest.fn()
|
||||
this.deleteOne = jest.fn()
|
||||
this.deleteMany = jest.fn(() => ({ toArray: () => [] }))
|
||||
this.updateOne = jest.fn()
|
||||
this.updateMany = jest.fn(() => ({ toArray: () => [] }))
|
||||
|
||||
this.collection = jest.fn(() => ({
|
||||
insertOne: this.insertOne,
|
||||
find: this.find,
|
||||
insertMany: this.insertMany,
|
||||
findOne: this.findOne,
|
||||
findOneAndUpdate: this.findOneAndUpdate,
|
||||
count: this.count,
|
||||
deleteOne: this.deleteOne,
|
||||
deleteMany: this.deleteMany,
|
||||
updateOne: this.updateOne,
|
||||
updateMany: this.updateMany,
|
||||
}))
|
||||
|
||||
this.db = () => ({
|
||||
collection: this.collection,
|
||||
})
|
||||
}
|
||||
|
||||
mongodb.ObjectId = jest.requireActual("mongodb").ObjectId
|
||||
|
||||
module.exports = mongodb
|
||||
}
|
|
@ -3,8 +3,6 @@ import * as setup from "../utilities"
|
|||
import { databaseTestProviders } from "../../../../integrations/tests/utils"
|
||||
import { MongoClient, type Collection, BSON } from "mongodb"
|
||||
|
||||
jest.unmock("mongodb")
|
||||
|
||||
const collection = "test_collection"
|
||||
|
||||
const expectValidId = expect.stringMatching(/^\w{24}$/)
|
||||
|
@ -36,27 +34,27 @@ describe("/queries", () => {
|
|||
return await config.api.query.save(combinedQuery)
|
||||
}
|
||||
|
||||
async function withClient(
|
||||
callback: (client: MongoClient) => Promise<void>
|
||||
): Promise<void> {
|
||||
async function withClient<T>(
|
||||
callback: (client: MongoClient) => Promise<T>
|
||||
): Promise<T> {
|
||||
const ds = await databaseTestProviders.mongodb.datasource()
|
||||
const client = new MongoClient(ds.config!.connectionString)
|
||||
await client.connect()
|
||||
try {
|
||||
await callback(client)
|
||||
return await callback(client)
|
||||
} finally {
|
||||
await client.close()
|
||||
}
|
||||
}
|
||||
|
||||
async function withCollection(
|
||||
callback: (collection: Collection) => Promise<void>
|
||||
): Promise<void> {
|
||||
await withClient(async client => {
|
||||
async function withCollection<T>(
|
||||
callback: (collection: Collection) => Promise<T>
|
||||
): Promise<T> {
|
||||
return await withClient(async client => {
|
||||
const db = client.db(
|
||||
(await databaseTestProviders.mongodb.datasource()).config!.db
|
||||
)
|
||||
await callback(db.collection(collection))
|
||||
return await callback(db.collection(collection))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -327,6 +325,42 @@ describe("/queries", () => {
|
|||
})
|
||||
})
|
||||
|
||||
it("should be able to updateOne by ObjectId", async () => {
|
||||
const insertResult = await withCollection(c => c.insertOne({ name: "one" }))
|
||||
const query = await createQuery({
|
||||
fields: {
|
||||
json: {
|
||||
filter: { _id: { $eq: `ObjectId("${insertResult.insertedId}")` } },
|
||||
update: { $set: { name: "newName" } },
|
||||
},
|
||||
extra: {
|
||||
actionType: "updateOne",
|
||||
},
|
||||
},
|
||||
queryVerb: "update",
|
||||
})
|
||||
|
||||
const result = await config.api.query.execute(query._id!)
|
||||
|
||||
expect(result.data).toEqual([
|
||||
{
|
||||
acknowledged: true,
|
||||
matchedCount: 1,
|
||||
modifiedCount: 1,
|
||||
upsertedCount: 0,
|
||||
upsertedId: null,
|
||||
},
|
||||
])
|
||||
|
||||
await withCollection(async collection => {
|
||||
const doc = await collection.findOne({ name: { $eq: "newName" } })
|
||||
expect(doc).toEqual({
|
||||
_id: insertResult.insertedId,
|
||||
name: "newName",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it("should be able to delete all records", async () => {
|
||||
const query = await createQuery({
|
||||
fields: {
|
||||
|
@ -390,4 +424,85 @@ describe("/queries", () => {
|
|||
}
|
||||
})
|
||||
})
|
||||
|
||||
it("should throw an error if the incorrect actionType is specified", async () => {
|
||||
const verbs = ["read", "create", "update", "delete"]
|
||||
for (const verb of verbs) {
|
||||
const query = await createQuery({
|
||||
fields: { json: {}, extra: { actionType: "invalid" } },
|
||||
queryVerb: verb,
|
||||
})
|
||||
await config.api.query.execute(query._id!, undefined, { status: 400 })
|
||||
}
|
||||
})
|
||||
|
||||
it("should ignore extra brackets in query", async () => {
|
||||
const query = await createQuery({
|
||||
fields: {
|
||||
json: { foo: "te}st" },
|
||||
extra: {
|
||||
actionType: "insertOne",
|
||||
},
|
||||
},
|
||||
queryVerb: "create",
|
||||
})
|
||||
|
||||
const result = await config.api.query.execute(query._id!)
|
||||
expect(result.data).toEqual([
|
||||
{
|
||||
acknowledged: true,
|
||||
insertedId: expectValidId,
|
||||
},
|
||||
])
|
||||
|
||||
await withCollection(async collection => {
|
||||
const doc = await collection.findOne({ foo: { $eq: "te}st" } })
|
||||
expect(doc).toEqual({
|
||||
_id: expectValidBsonObjectId,
|
||||
foo: "te}st",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it("should ignore be able to save deeply nested data", async () => {
|
||||
const data = {
|
||||
foo: "bar",
|
||||
data: [
|
||||
{ cid: 1 },
|
||||
{ cid: 2 },
|
||||
{
|
||||
nested: {
|
||||
name: "test",
|
||||
ary: [1, 2, 3],
|
||||
aryOfObjects: [{ a: 1 }, { b: 2 }],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
const query = await createQuery({
|
||||
fields: {
|
||||
json: data,
|
||||
extra: {
|
||||
actionType: "insertOne",
|
||||
},
|
||||
},
|
||||
queryVerb: "create",
|
||||
})
|
||||
|
||||
const result = await config.api.query.execute(query._id!)
|
||||
expect(result.data).toEqual([
|
||||
{
|
||||
acknowledged: true,
|
||||
insertedId: expectValidId,
|
||||
},
|
||||
])
|
||||
|
||||
await withCollection(async collection => {
|
||||
const doc = await collection.findOne({ foo: { $eq: "bar" } })
|
||||
expect(doc).toEqual({
|
||||
_id: expectValidBsonObjectId,
|
||||
...data,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
} from "mongodb"
|
||||
import environment from "../environment"
|
||||
|
||||
interface MongoDBConfig {
|
||||
export interface MongoDBConfig {
|
||||
connectionString: string
|
||||
db: string
|
||||
tlsCertificateKeyFile: string
|
||||
|
@ -348,7 +348,7 @@ const getSchema = () => {
|
|||
|
||||
const SCHEMA: Integration = getSchema()
|
||||
|
||||
class MongoIntegration implements IntegrationBase {
|
||||
export class MongoIntegration implements IntegrationBase {
|
||||
private config: MongoDBConfig
|
||||
private client: MongoClient
|
||||
|
||||
|
|
|
@ -1,325 +0,0 @@
|
|||
const mongo = require("mongodb")
|
||||
|
||||
import { default as MongoDBIntegration } from "../mongodb"
|
||||
|
||||
jest.mock("mongodb")
|
||||
|
||||
class TestConfiguration {
|
||||
integration: any
|
||||
|
||||
constructor(config: any = {}) {
|
||||
this.integration = new MongoDBIntegration.integration(config)
|
||||
}
|
||||
}
|
||||
|
||||
describe("MongoDB Integration", () => {
|
||||
let config: any
|
||||
let indexName = "Users"
|
||||
|
||||
beforeEach(() => {
|
||||
config = new TestConfiguration()
|
||||
})
|
||||
|
||||
it("calls the create method with the correct params", async () => {
|
||||
const body = {
|
||||
name: "Hello",
|
||||
}
|
||||
await config.integration.create({
|
||||
index: indexName,
|
||||
json: body,
|
||||
extra: { collection: "testCollection", actionType: "insertOne" },
|
||||
})
|
||||
expect(config.integration.client.insertOne).toHaveBeenCalledWith(body)
|
||||
})
|
||||
|
||||
it("calls the read method with the correct params", async () => {
|
||||
const query = {
|
||||
json: {
|
||||
address: "test",
|
||||
},
|
||||
extra: { collection: "testCollection", actionType: "find" },
|
||||
}
|
||||
const response = await config.integration.read(query)
|
||||
expect(config.integration.client.find).toHaveBeenCalledWith(query.json)
|
||||
expect(response).toEqual(expect.any(Array))
|
||||
})
|
||||
|
||||
it("calls the delete method with the correct params", async () => {
|
||||
const query = {
|
||||
json: {
|
||||
filter: {
|
||||
id: "test",
|
||||
},
|
||||
options: {
|
||||
opt: "option",
|
||||
},
|
||||
},
|
||||
extra: { collection: "testCollection", actionType: "deleteOne" },
|
||||
}
|
||||
await config.integration.delete(query)
|
||||
expect(config.integration.client.deleteOne).toHaveBeenCalledWith(
|
||||
query.json.filter,
|
||||
query.json.options
|
||||
)
|
||||
})
|
||||
|
||||
it("calls the update method with the correct params", async () => {
|
||||
const query = {
|
||||
json: {
|
||||
filter: {
|
||||
id: "test",
|
||||
},
|
||||
update: {
|
||||
name: "TestName",
|
||||
},
|
||||
options: {
|
||||
upsert: false,
|
||||
},
|
||||
},
|
||||
extra: { collection: "testCollection", actionType: "updateOne" },
|
||||
}
|
||||
await config.integration.update(query)
|
||||
expect(config.integration.client.updateOne).toHaveBeenCalledWith(
|
||||
query.json.filter,
|
||||
query.json.update,
|
||||
query.json.options
|
||||
)
|
||||
})
|
||||
|
||||
it("throws an error when an invalid query.extra.actionType is passed for each method", async () => {
|
||||
const query = {
|
||||
extra: { collection: "testCollection", actionType: "deleteOne" },
|
||||
}
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await config.integration.read(query)
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
expect(error).toBeDefined()
|
||||
})
|
||||
|
||||
it("creates ObjectIds if the field contains a match on ObjectId", async () => {
|
||||
const query = {
|
||||
json: {
|
||||
filter: {
|
||||
_id: "ObjectId('ACBD12345678ABCD12345678')",
|
||||
name: "ObjectId('BBBB12345678ABCD12345678')",
|
||||
},
|
||||
update: {
|
||||
_id: "ObjectId('FFFF12345678ABCD12345678')",
|
||||
name: "ObjectId('CCCC12345678ABCD12345678')",
|
||||
},
|
||||
options: {
|
||||
upsert: false,
|
||||
},
|
||||
},
|
||||
extra: { collection: "testCollection", actionType: "updateOne" },
|
||||
}
|
||||
await config.integration.update(query)
|
||||
expect(config.integration.client.updateOne).toHaveBeenCalled()
|
||||
|
||||
const args = config.integration.client.updateOne.mock.calls[0]
|
||||
expect(args[0]).toEqual({
|
||||
_id: mongo.ObjectId.createFromHexString("ACBD12345678ABCD12345678"),
|
||||
name: mongo.ObjectId.createFromHexString("BBBB12345678ABCD12345678"),
|
||||
})
|
||||
expect(args[1]).toEqual({
|
||||
_id: mongo.ObjectId.createFromHexString("FFFF12345678ABCD12345678"),
|
||||
name: mongo.ObjectId.createFromHexString("CCCC12345678ABCD12345678"),
|
||||
})
|
||||
expect(args[2]).toEqual({
|
||||
upsert: false,
|
||||
})
|
||||
})
|
||||
|
||||
it("creates ObjectIds if the $ operator fields contains a match on ObjectId", async () => {
|
||||
const query = {
|
||||
json: {
|
||||
filter: {
|
||||
_id: {
|
||||
$eq: "ObjectId('ACBD12345678ABCD12345678')",
|
||||
},
|
||||
},
|
||||
update: {
|
||||
$set: {
|
||||
_id: "ObjectId('FFFF12345678ABCD12345678')",
|
||||
},
|
||||
},
|
||||
options: {
|
||||
upsert: true,
|
||||
},
|
||||
},
|
||||
extra: { collection: "testCollection", actionType: "updateOne" },
|
||||
}
|
||||
await config.integration.update(query)
|
||||
expect(config.integration.client.updateOne).toHaveBeenCalled()
|
||||
|
||||
const args = config.integration.client.updateOne.mock.calls[0]
|
||||
expect(args[0]).toEqual({
|
||||
_id: {
|
||||
$eq: mongo.ObjectId.createFromHexString("ACBD12345678ABCD12345678"),
|
||||
},
|
||||
})
|
||||
expect(args[1]).toEqual({
|
||||
$set: {
|
||||
_id: mongo.ObjectId.createFromHexString("FFFF12345678ABCD12345678"),
|
||||
},
|
||||
})
|
||||
expect(args[2]).toEqual({
|
||||
upsert: true,
|
||||
})
|
||||
})
|
||||
|
||||
it("supports findOneAndUpdate", async () => {
|
||||
const query = {
|
||||
json: {
|
||||
filter: {
|
||||
_id: {
|
||||
$eq: "ObjectId('ACBD12345678ABCD12345678')",
|
||||
},
|
||||
},
|
||||
update: {
|
||||
$set: {
|
||||
name: "UPDATED",
|
||||
age: 99,
|
||||
},
|
||||
},
|
||||
options: {
|
||||
upsert: false,
|
||||
},
|
||||
},
|
||||
extra: { collection: "testCollection", actionType: "findOneAndUpdate" },
|
||||
}
|
||||
await config.integration.read(query)
|
||||
expect(config.integration.client.findOneAndUpdate).toHaveBeenCalled()
|
||||
|
||||
const args = config.integration.client.findOneAndUpdate.mock.calls[0]
|
||||
expect(args[0]).toEqual({
|
||||
_id: {
|
||||
$eq: mongo.ObjectId.createFromHexString("ACBD12345678ABCD12345678"),
|
||||
},
|
||||
})
|
||||
expect(args[1]).toEqual({
|
||||
$set: {
|
||||
name: "UPDATED",
|
||||
age: 99,
|
||||
},
|
||||
})
|
||||
expect(args[2]).toEqual({
|
||||
upsert: false,
|
||||
includeResultMetadata: true,
|
||||
})
|
||||
})
|
||||
|
||||
it("can parse nested objects with arrays", async () => {
|
||||
const query = {
|
||||
json: `{
|
||||
"_id": {
|
||||
"$eq": "ObjectId('ACBD12345678ABCD12345678')"
|
||||
}
|
||||
},
|
||||
{
|
||||
"$set": {
|
||||
"value": {
|
||||
"data": [
|
||||
{ "cid": 1 },
|
||||
{ "cid": 2 },
|
||||
{ "nested": {
|
||||
"name": "test"
|
||||
}}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"upsert": true
|
||||
}`,
|
||||
extra: { collection: "testCollection", actionType: "updateOne" },
|
||||
}
|
||||
await config.integration.update(query)
|
||||
expect(config.integration.client.updateOne).toHaveBeenCalled()
|
||||
|
||||
const args = config.integration.client.updateOne.mock.calls[0]
|
||||
expect(args[0]).toEqual({
|
||||
_id: {
|
||||
$eq: mongo.ObjectId.createFromHexString("ACBD12345678ABCD12345678"),
|
||||
},
|
||||
})
|
||||
expect(args[1]).toEqual({
|
||||
$set: {
|
||||
value: {
|
||||
data: [
|
||||
{ cid: 1 },
|
||||
{ cid: 2 },
|
||||
{
|
||||
nested: {
|
||||
name: "test",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(args[2]).toEqual({
|
||||
upsert: true,
|
||||
})
|
||||
})
|
||||
|
||||
it("ignores braces within strings when parsing nested objects", async () => {
|
||||
const query = {
|
||||
json: `{
|
||||
"_id": {
|
||||
"$eq": "ObjectId('ACBD12345678ABCD12345678')"
|
||||
}
|
||||
},
|
||||
{
|
||||
"$set": {
|
||||
"value": {
|
||||
"data": [
|
||||
{ "cid": 1 },
|
||||
{ "cid": 2 },
|
||||
{ "nested": {
|
||||
"name": "te}st"
|
||||
}}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"upsert": true,
|
||||
"extra": "ad\\"{\\"d"
|
||||
}`,
|
||||
extra: { collection: "testCollection", actionType: "updateOne" },
|
||||
}
|
||||
await config.integration.update(query)
|
||||
expect(config.integration.client.updateOne).toHaveBeenCalled()
|
||||
|
||||
const args = config.integration.client.updateOne.mock.calls[0]
|
||||
expect(args[0]).toEqual({
|
||||
_id: {
|
||||
$eq: mongo.ObjectId.createFromHexString("ACBD12345678ABCD12345678"),
|
||||
},
|
||||
})
|
||||
expect(args[1]).toEqual({
|
||||
$set: {
|
||||
value: {
|
||||
data: [
|
||||
{ cid: 1 },
|
||||
{ cid: 2 },
|
||||
{
|
||||
nested: {
|
||||
name: "te}st",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(args[2]).toEqual({
|
||||
upsert: true,
|
||||
extra: 'ad"{"d',
|
||||
})
|
||||
})
|
||||
})
|
|
@ -5,7 +5,7 @@ import {
|
|||
PreviewQueryRequest,
|
||||
PreviewQueryResponse,
|
||||
} from "@budibase/types"
|
||||
import { TestAPI } from "./base"
|
||||
import { Expectations, TestAPI } from "./base"
|
||||
|
||||
export class QueryAPI extends TestAPI {
|
||||
save = async (body: Query): Promise<Query> => {
|
||||
|
@ -14,12 +14,14 @@ export class QueryAPI extends TestAPI {
|
|||
|
||||
execute = async (
|
||||
queryId: string,
|
||||
body?: ExecuteQueryRequest
|
||||
body?: ExecuteQueryRequest,
|
||||
expectations?: Expectations
|
||||
): Promise<ExecuteQueryResponse> => {
|
||||
return await this._post<ExecuteQueryResponse>(
|
||||
`/api/v2/queries/${queryId}`,
|
||||
{
|
||||
body,
|
||||
expectations,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue