query / update events + tests

This commit is contained in:
Rory Powell 2022-04-07 00:38:18 +01:00
parent 8a08e9322f
commit ac8573b67e
17 changed files with 148 additions and 71 deletions

View File

@ -38,9 +38,6 @@ exports.Events = {
ORG_LOGO_UPDATED: "org:info:logo:updated",
ORG_PLATFORM_URL_UPDATED: "org:platformurl:updated",
// ORG / NPS
NPS_SUBMITTED: "nps:submitted",
// ORG / UPDATE
UPDATE_VERSION_CHECKED: "version:checked",
@ -77,8 +74,8 @@ exports.Events = {
QUERY_CREATED: "query:created",
QUERY_UPDATED: "query:updated",
QUERY_DELETED: "query:deleted",
QUERY_IMPORTED: "query:imported",
QUERY_RUN: "query:run",
QUERY_IMPORT: "query:import",
// QUERY_RUN: "query:run",
QUERY_PREVIEWED: "query:previewed",
// TABLE
@ -101,10 +98,7 @@ exports.Events = {
VIEW_CALCULATION_DELETED: "view:calculation:created",
// ROW
ROW_CREATED: "row:created",
ROW_UPDATED: "row:updated",
ROW_DELETED: "row:deleted",
ROW_IMPORTED: "row:imported",
// ROW_CREATED: "row:created",
// BUILDER
BUILDER_SERVED: "builder:served",
@ -134,7 +128,7 @@ exports.Events = {
LICENSE_UPGRADED: "license:upgraded",
LICENSE_DOWNGRADED: "license:downgraded",
LICENSE_UPDATED: "license:updated",
LICENSE_PAIRED: "license:paired",
LICENSE_ACTIVATED: "license:activated",
LICENSE_QUOTA_EXCEEDED: "license:quota:exceeded",
// ACCOUNT

View File

@ -16,9 +16,9 @@ exports.updated = () => {
events.processEvent(Events.LICENSE_UPDATED, properties)
}
exports.paired = () => {
exports.activated = () => {
const properties = {}
events.processEvent(Events.LICENSE_PAIRED, properties)
events.processEvent(Events.LICENSE_ACTIVATED, properties)
}
exports.quotaExceeded = (quotaName, value) => {

View File

@ -23,12 +23,8 @@ exports.versionChecked = version => {
events.processEvent(Events.UPDATE_VERSION_CHECKED, properties)
}
// TODO
exports.analyticsOptOut = () => {
const properties = {}
events.processEvent(Events.ANALYTICS_OPT_OUT, properties)
}
exports.npsSubmitted = () => {
const properties = {}
events.processEvent(Events.NPS_SUBMITTED, properties)
}

View File

@ -6,29 +6,27 @@ exports.created = () => {
events.processEvent(Events.QUERY_CREATED, properties)
}
// TODO
exports.updated = () => {
const properties = {}
events.processEvent(Events.QUERY_UPDATED, properties)
}
// TODO
exports.deleted = () => {
const properties = {}
events.processEvent(Events.QUERY_DELETED, properties)
}
// TODO
exports.imported = () => {
exports.import = () => {
const properties = {}
events.processEvent(Events.QUERY_IMPORTED, properties)
events.processEvent(Events.QUERY_IMPORT, properties)
}
// TODO
exports.run = () => {
const properties = {}
events.processEvent(Events.QUERY_RUN, properties)
}
// exports.run = () => {
// const properties = {}
// events.processEvent(Events.QUERY_RUN, properties)
// }
// TODO
exports.previewed = () => {

View File

@ -1,26 +1,7 @@
const events = require("../events")
const { Events } = require("../constants")
// const events = require("../events")
// const { Events } = require("../constants")
exports.created = () => {
const properties = {}
events.processEvent(Events.ROW_CREATED, properties)
}
// TODO
exports.imported = () => {
const properties = {}
events.processEvent(Events.ROW_IMPORTED, properties)
exports.rowCreated()
}
// TODO
exports.updated = () => {
const properties = {}
events.processEvent(Events.ROW_UPDATED, properties)
}
// TODO
exports.deleted = () => {
const properties = {}
events.processEvent(Events.ROW_DELETED, properties)
}
// exports.created = () => {
// const properties = {}
// events.processEvent(Events.ROW_CREATED, properties)
// }

View File

@ -54,7 +54,13 @@ jest.mock("../../../events", () => {
platformURLUpdated: jest.fn(),
versionChecked: jest.fn(),
analyticsOptOut: jest.fn(),
npsSubmitted: jest.fn(),
},
query: {
created: jest.fn(),
updated: jest.fn(),
deleted: jest.fn(),
import: jest.fn(),
previewed: jest.fn(),
},
}
})

View File

@ -14,11 +14,6 @@ exports.endUserPing = async ctx => {
return
}
// posthogClient.identify({
// distinctId: ctx.user && ctx.user._id,
// properties: {},
// })
analytics.captureEvent(ctx.user._id, "budibase:end_user_ping", {
appId: ctx.appId,
})

View File

@ -7,6 +7,8 @@ import { Query } from "./../../../../definitions/common"
import { Curl } from "./sources/curl"
// @ts-ignore
import { getAppDB } from "@budibase/backend-core/context"
import { events } from "@budibase/backend-core"
interface ImportResult {
errorQueries: Query[]
queries: Query[]
@ -36,7 +38,7 @@ export class RestImporter {
}
importQueries = async (datasourceId: string): Promise<ImportResult> => {
// constuct the queries
// construct the queries
let queries = await this.source.getQueries(datasourceId)
// validate queries
@ -76,9 +78,20 @@ export class RestImporter {
}
})
const successQueries = Object.values(queryIndex)
// events
const count = successQueries.length
const importSource = this.source.getImportSource()
const datasource = await db.get(datasourceId)
events.query.import({ datasource, importSource, count })
for (let query of successQueries) {
events.query.created(query)
}
return {
errorQueries,
queries: Object.values(queryIndex),
queries: successQueries,
}
}
}

View File

@ -17,6 +17,7 @@ export abstract class ImportSource {
abstract isSupported(data: string): Promise<boolean>
abstract getInfo(): Promise<ImportInfo>
abstract getQueries(datasourceId: string): Promise<Query[]>
abstract getImportSource(): string
constructQuery = (
datasourceId: string,

View File

@ -71,6 +71,10 @@ export class Curl extends ImportSource {
}
}
getImportSource(): string {
return "curl"
}
getQueries = async (datasourceId: string): Promise<Query[]> => {
const url = this.getUrl()
const name = url.pathname

View File

@ -70,6 +70,10 @@ export class OpenAPI2 extends OpenAPISource {
}
}
getImportSource(): string {
return "openapi2.0"
}
getQueries = async (datasourceId: string): Promise<Query[]> => {
const url = this.getUrl()
const queries = []

View File

@ -106,6 +106,10 @@ export class OpenAPI3 extends OpenAPISource {
}
}
getImportSource(): string {
return "openapi3.0"
}
getQueries = async (datasourceId: string): Promise<Query[]> => {
let url: string | URL | undefined
if (this.document.servers?.length) {

View File

@ -1,9 +1,10 @@
const TestConfig = require("../../../../../tests/utilities/TestConfiguration")
const { RestImporter } = require("../index")
const fs = require("fs")
const path = require('path')
const { events} = require("@budibase/backend-core")
const { mocks } = require("@budibase/backend-core/testUtils")
mocks.date.mock()
const getData = (file) => {
return fs.readFileSync(path.join(__dirname, `../sources/tests/${file}`), "utf8")
@ -103,9 +104,13 @@ describe("Rest Importer", () => {
const testImportQueries = async (key, data, assertions) => {
await init(data)
const importResult = await restImporter.importQueries("datasourceId")
const datasource = await config.createDatasource()
const importResult = await restImporter.importQueries(datasource._id)
expect(importResult.errorQueries.length).toBe(0)
expect(importResult.queries.length).toBe(assertions[key].count)
expect(events.query.import).toBeCalledTimes(1)
const eventData = { datasource, importSource: assertions[key].source, count: assertions[key].count}
expect(events.query.import).toBeCalledWith(eventData)
jest.clearAllMocks()
}
@ -116,32 +121,41 @@ describe("Rest Importer", () => {
// openapi2 (swagger)
"oapi2CrudJson" : {
count: 6,
source: "openapi2.0",
},
"oapi2CrudYaml" :{
count: 6,
source: "openapi2.0"
},
"oapi2PetstoreJson" : {
count: 20,
source: "openapi2.0"
},
"oapi2PetstoreYaml" :{
count: 20,
source: "openapi2.0"
},
// openapi3
"oapi3CrudJson" : {
count: 6,
source: "openapi3.0"
},
"oapi3CrudYaml" :{
count: 6,
source: "openapi3.0"
},
"oapi3PetstoreJson" : {
count: 19,
source: "openapi3.0"
},
"oapi3PetstoreYaml" :{
count: 19,
source: "openapi3.0"
},
// curl
"curl": {
count: 1
count: 1,
source: "curl"
}
}
await runTest(testImportQueries, assertions)

View File

@ -7,6 +7,7 @@ import { invalidateDynamicVariables } from "../../../threads/utils"
import { QUERY_THREAD_TIMEOUT } from "../../../environment"
import { getAppDB } from "@budibase/backend-core/context"
import { quotas } from "@budibase/pro"
import { events } from "@budibase/backend-core"
const Runner = new Thread(ThreadType.QUERY, {
timeoutMs: QUERY_THREAD_TIMEOUT || 10000,
@ -80,11 +81,18 @@ export async function save(ctx: any) {
const db = getAppDB()
const query = ctx.request.body
const datasource = await db.get(query.datasourceId)
let eventFn
if (!query._id) {
query._id = generateQueryID(query.datasourceId)
eventFn = () => events.query.created(datasource, query)
} else {
eventFn = () => events.query.updated(datasource, query)
}
const response = await db.put(query)
eventFn()
query._rev = response.rev
ctx.body = query
@ -124,6 +132,7 @@ export async function preview(ctx: any) {
})
const { rows, keys, info, extra } = await quotas.addQuery(runFn)
events.query.previewed(datasource)
ctx.body = {
rows,
schemaFields: [...new Set(keys)],
@ -211,4 +220,5 @@ export async function destroy(ctx: any) {
await db.remove(ctx.params.queryId, ctx.params.revId)
ctx.message = `Query deleted.`
ctx.status = 200
events.query.deleted()
}

View File

@ -1,5 +1,6 @@
const setup = require("./utilities")
const { events } = require("@budibase/backend-core")
const version = require("../../../../package.json").version
describe("/dev", () => {
let request = setup.getRequest()
@ -19,7 +20,21 @@ describe("/dev", () => {
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
expect(events.app.reverted.mock.calls.length).toBe(1)
expect(events.app.reverted).toBeCalledTimes(1)
})
})
describe("version", () => {
it("should get the installation version", async () => {
const res = await request
.get(`/api/dev/version`)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
expect(res.body.version).toBe(version)
expect(events.org.versionChecked).toBeCalledTimes(1)
expect(events.org.versionChecked).toBeCalledWith(version)
})
})
})

View File

@ -15,6 +15,7 @@ const { checkCacheForDynamicVariable } = require("../../../threads/utils")
const { basicQuery, basicDatasource } = setup.structures
const { mocks } = require("@budibase/backend-core/testUtils")
mocks.date.mock()
const { events } = require("@budibase/backend-core")
describe("/queries", () => {
let request = setup.getRequest()
@ -40,16 +41,21 @@ describe("/queries", () => {
return { datasource, query }
}
describe("create", () => {
it("should create a new query", async () => {
const { _id } = await config.createDatasource()
const query = basicQuery(_id)
const res = await request
const createQuery = async (query) => {
return request
.post(`/api/queries`)
.send(query)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
}
describe("create", () => {
it("should create a new query", async () => {
const { _id } = await config.createDatasource()
const query = basicQuery(_id)
jest.clearAllMocks()
const res = await createQuery(query)
expect(res.res.statusMessage).toEqual(
`Query ${query.name} saved successfully.`
@ -61,6 +67,33 @@ describe("/queries", () => {
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
})
expect(events.query.created).toBeCalledTimes(1)
expect(events.query.updated).not.toBeCalled()
})
})
describe("update", () => {
it("should update query", async () => {
const { _id } = await config.createDatasource()
const query = basicQuery(_id)
const res = await createQuery(query)
jest.clearAllMocks()
query._id = res.body._id
query._rev = res.body._rev
await createQuery(query)
expect(res.res.statusMessage).toEqual(
`Query ${query.name} saved successfully.`
)
expect(res.body).toEqual({
_rev: res.body._rev,
_id: res.body._id,
...query,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
})
expect(events.query.created).not.toBeCalled()
expect(events.query.updated).toBeCalledTimes(1)
})
})
@ -155,6 +188,7 @@ describe("/queries", () => {
.expect(200)
expect(res.body).toEqual([])
expect(events.query.deleted).toBeCalledTimes(1)
})
it("should apply authorization to endpoint", async () => {
@ -183,6 +217,9 @@ describe("/queries", () => {
// these responses come from the mock
expect(res.body.schemaFields).toEqual(["a", "b"])
expect(res.body.rows.length).toEqual(1)
expect(events.query.previewed).toBeCalledTimes(1)
datasource.config = { schema: "public" }
expect(events.query.previewed).toBeCalledWith(datasource)
})
it("should apply authorization to endpoint", async () => {

View File

@ -62,7 +62,11 @@ const getEventFns = async (db, config) => {
// platform url
const platformUrl = config.config.platformUrl
if (platformUrl && platformUrl !== "http://localhost:10000") {
if (
platformUrl &&
platformUrl !== "http://localhost:10000" &&
env.SELF_HOSTED
) {
fns.push(events.org.platformURLUpdated)
}
break
@ -119,7 +123,8 @@ const getEventFns = async (db, config) => {
if (
platformUrl &&
platformUrl !== "http://localhost:10000" &&
existingPlatformUrl !== platformUrl
existingPlatformUrl !== platformUrl &&
env.SELF_HOSTED
) {
fns.push(events.org.platformURLUpdated)
}