App migrations finished

This commit is contained in:
Rory Powell 2022-05-20 12:29:31 +01:00
parent 498c130e71
commit 756f5b51aa
27 changed files with 228 additions and 40 deletions

View File

@ -12,6 +12,7 @@ import {
ViewFilterUpdatedEvent,
ViewUpdatedEvent,
View,
ViewCalculation,
Table,
TableExportFormat,
} from "@budibase/types"
@ -53,7 +54,7 @@ export function filterDeleted() {
publishEvent(Event.VIEW_FILTER_DELETED, properties)
}
export function calculationCreated() {
export function calculationCreated(calculation: ViewCalculation) {
const properties: ViewCalculationCreatedEvent = {}
publishEvent(Event.VIEW_CALCULATION_CREATED, properties)
}

View File

@ -69,7 +69,7 @@ jest.mock("../../../events", () => {
assigned: jest.fn(),
unassigned: jest.fn(),
},
row: {
rows: {
imported: jest.fn(),
},
screen: {

View File

@ -3,11 +3,11 @@ import { generateQueryID } from "../../../../db/utils"
import { ImportInfo, ImportSource } from "./sources/base"
import { OpenAPI2 } from "./sources/openapi2"
import { OpenAPI3 } from "./sources/openapi3"
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"
import { Datasource, Query } from "@budibase/types"
interface ImportResult {
errorQueries: Query[]
@ -83,7 +83,7 @@ export class RestImporter {
// events
const count = successQueries.length
const importSource = this.source.getImportSource()
const datasource = await db.get(datasourceId)
const datasource: Datasource = await db.get(datasourceId)
events.query.imported(datasource, importSource, count)
for (let query of successQueries) {
events.query.created(datasource, query)

View File

@ -1,4 +1,4 @@
import { Query, QueryParameter } from "../../../../../../definitions/datasource"
import { Query, QueryParameter } from "@budibase/types"
import { URL } from "url"
export interface ImportInfo {

View File

@ -55,8 +55,8 @@ describe("/tables", () => {
expect(events.table.created).toBeCalledWith(res.body)
expect(events.table.imported).toBeCalledTimes(1)
expect(events.table.imported).toBeCalledWith(res.body, "csv")
expect(events.row.imported).toBeCalledTimes(1)
expect(events.row.imported).toBeCalledWith(res.body, "csv", 1)
expect(events.rowsimported).toBeCalledTimes(1)
expect(events.rows.imported).toBeCalledWith(res.body, "csv", 1)
})
it("should apply authorization to endpoint", async () => {

View File

@ -5,6 +5,7 @@ import * as layouts from "./app/layouts"
import * as queries from "./app/queries"
import * as roles from "./app/roles"
import * as tables from "./app/tables"
import * as screens from "./app/screens"
/**
* Date:
@ -22,4 +23,5 @@ export const run = async (appDb: any) => {
await queries.backfill(appDb)
await roles.backfill(appDb)
await tables.backfill(appDb)
await screens.backfill(appDb)
}

View File

@ -1,8 +0,0 @@
import { events } from "@budibase/backend-core"
import { Row } from "@budibase/types"
export const backfill = async (appDb: any) => {
const rows: Row[] = []
const count = rows.length
events.rows.created(count)
}

View File

@ -1 +1,22 @@
// SCREEN_CREATED = "screen:created",
import { events, db } from "@budibase/backend-core"
import { getScreenParams } from "../../../../db/utils"
import { Screen } from "@budibase/types"
const getScreens = async (appDb: any): Promise<Screen[]> => {
const response = await appDb.allDocs(
getScreenParams(null, {
include_docs: true,
})
)
return response.rows.map((row: any) => row.doc)
}
export const backfill = async (appDb: any) => {
if (db.isDevAppID(appDb.name)) {
const screens = await getScreens(appDb)
for (const screen of screens) {
events.screen.created(screen)
}
}
}

View File

@ -17,6 +17,20 @@ export const backfill = async (appDb: any) => {
for (const table of tables) {
events.table.created(table)
if (table.views) {
for (const view of Object.values(table.views)) {
events.view.created(view)
if (view.calculation) {
events.view.calculationCreated(view.calculation)
}
if (view.filters?.length) {
events.view.filterCreated()
}
}
}
}
}
}

View File

@ -1,3 +0,0 @@
// VIEW_CREATED = "view:created",
// VIEW_FILTER_CREATED = "view:filter:created",
// VIEW_CALCULATION_CREATED = "view:calculation:created",

View File

@ -0,0 +1,14 @@
import { events, db } from "@budibase/backend-core"
import { Row } from "@budibase/types"
import { getUniqueRows } from "../../../../utilities/usageQuota/rows"
// Rows is a special circumstance where we get rows across all apps
// therefore migration is performed at the global level
export const backfill = async () => {
const allApps = await db.getAllApps({ all: true })
const appIds = allApps ? allApps.map((app: { appId: any }) => app.appId) : []
const rows: Row[] = await getUniqueRows(appIds)
const rowCount = rows ? rows.length : 0
events.rows.created(rowCount)
}

View File

@ -29,7 +29,11 @@ describe("migrations", () => {
await config.createRole()
await config.createRole()
await config.createTable()
await config.createView()
await config.createTable()
await config.createView(structures.view(config.table._id))
await config.createScreen()
await config.createScreen()
jest.clearAllMocks()
const migration = MIGRATIONS.filter(
@ -46,6 +50,10 @@ describe("migrations", () => {
expect(events.query.created).toBeCalledTimes(2)
expect(events.role.created).toBeCalledTimes(2)
expect(events.table.created).toBeCalledTimes(3)
expect(events.view.created).toBeCalledTimes(2)
expect(events.view.calculationCreated).toBeCalledTimes(1)
expect(events.view.filterCreated).toBeCalledTimes(1)
expect(events.screen.created).toBeCalledTimes(2)
})
})
})

View File

@ -409,7 +409,6 @@ class TestConfiguration {
throw "Test requires table to be configured."
}
const view = config || {
map: "function(doc) { emit(doc[doc.key], doc._id); } ",
tableId: this.table._id,
name: "ViewTest",
}

View File

@ -30,6 +30,41 @@ exports.basicTable = () => {
}
}
exports.basicView = tableId => {
return {
tableId,
name: "ViewTest",
}
}
exports.filterView = tableId => {
return {
...this.basicView(tableId),
filters: [
{
value: 0,
condition: "MT",
key: "count",
},
],
}
}
exports.calculationView = tableId => {
return {
...this.basicView(tableId),
field: "count",
calculation: "sum",
}
}
exports.view = tableId => {
return {
...this.filterView(tableId),
...this.calculationView(tableId),
}
}
exports.automationStep = (actionDefinition = ACTION_DEFINITIONS.CREATE_ROW) => {
return {
id: uuidv4(),

View File

@ -1,4 +1,6 @@
export interface Automation {
import { Document } from "./document"
export interface Automation extends Document {
definition: {
steps: AutomationStep[]
trigger: AutomationTrigger

View File

@ -1 +1,3 @@
export interface Datasource {}
import { Document } from "./document"
export interface Datasource extends Document {}

View File

@ -1,6 +1,6 @@
export interface Document {
_id: string
_id?: string
_rev?: string
createdAt: string
updatedAt: string
createdAt?: string
updatedAt?: string
}

View File

@ -1 +1,3 @@
export interface Layout {}
import { Document } from "./document"
export interface Layout extends Document {}

View File

@ -1,3 +1,44 @@
export interface Query {
import { Document } from "./document"
export interface Query extends Document {
datasourceId: string
name: string
parameters: QueryParameter[]
fields: RestQueryFields | any
transformer: string | null
schema: any
readable: boolean
queryVerb: string
}
export interface QueryParameter {
name: string
default: string
}
export interface RestQueryFields {
path: string
queryString?: string
headers: { [key: string]: any }
disabledHeaders: { [key: string]: any }
requestBody: any
bodyType: string
json: object
method: string
authConfigId: string
pagination: PaginationConfig | null
paginationValues: PaginationValues | null
}
export interface PaginationConfig {
type: string
location: string
pageParam: string
sizeParam: string | null
responseParam: string | null
}
export interface PaginationValues {
page: string | number | null
limit: number | null
}

View File

@ -1 +1,3 @@
export interface Row {}
import { Document } from "./document"
export interface Row extends Document {}

View File

@ -1 +1,3 @@
export interface Screen {}
import { Document } from "./document"
export interface Screen extends Document {}

View File

@ -1 +1,6 @@
export interface Table {}
import { Document } from "./document"
import { View } from "./view"
export interface Table extends Document {
views: { [key: string]: View }
}

View File

@ -1,3 +1,5 @@
export interface UserMetadata {
import { Document } from "./document"
export interface UserMetadata extends Document {
roleId: string
}

View File

@ -1 +1,43 @@
export interface View {}
export interface View {
name: string
tableId: string
field?: string
filters: ViewFilter[]
schema: ViewSchema
calculation?: ViewCalculation
}
export type ViewSchema = ViewCountOrSumSchema | ViewStatisticsSchema
export interface ViewCountOrSumSchema {
field: string
value: string
}
/**
e.g:
"min": {
"type": "number"
},
"max": {
"type": "number"
}
*/
export interface ViewStatisticsSchema {
[key: string]: {
type: string
}
}
export interface ViewFilter {
value: any
condition: string
key: string
conjunction?: string
}
export enum ViewCalculation {
SUM = "sum",
COUNT = "count",
STATISTICS = "stats",
}

View File

@ -139,12 +139,13 @@ describe("/api/global/users", () => {
}
await createUser(user)
const savedUser = await config.getUser(user.email)
expect(events.user.created).toBeCalledTimes(1)
expect(events.user.updated).not.toBeCalled()
expect(events.role.assigned).toBeCalledTimes(2)
expect(events.role.assigned).toBeCalledWith("role1")
expect(events.role.assigned).toBeCalledWith("role2")
expect(events.role.assigned).toBeCalledWith(savedUser, "role1")
expect(events.role.assigned).toBeCalledWith(savedUser, "role2")
})
})
@ -243,12 +244,13 @@ describe("/api/global/users", () => {
"app_456": "role2",
}
await updateUser(user)
const savedUser = await config.getUser(user.email)
expect(events.user.created).not.toBeCalled()
expect(events.user.updated).toBeCalledTimes(1)
expect(events.role.assigned).toBeCalledTimes(2)
expect(events.role.assigned).toBeCalledWith("role1")
expect(events.role.assigned).toBeCalledWith("role2")
expect(events.role.assigned).toBeCalledWith(savedUser, "role1")
expect(events.role.assigned).toBeCalledWith(savedUser, "role2")
})
it("should be able to unassign app roles", async () => {
@ -262,12 +264,13 @@ describe("/api/global/users", () => {
user.roles = {}
await updateUser(user)
const savedUser = await config.getUser(user.email)
expect(events.user.created).not.toBeCalled()
expect(events.user.updated).toBeCalledTimes(1)
expect(events.role.unassigned).toBeCalledTimes(2)
expect(events.role.unassigned).toBeCalledWith("role1")
expect(events.role.unassigned).toBeCalledWith("role2")
expect(events.role.unassigned).toBeCalledWith(savedUser, "role1")
expect(events.role.unassigned).toBeCalledWith(savedUser, "role2")
})
it("should be able to update existing app roles", async () => {
@ -284,13 +287,14 @@ describe("/api/global/users", () => {
"app_456": "role2-edit",
}
await updateUser(user)
const savedUser = await config.getUser(user.email)
expect(events.user.created).not.toBeCalled()
expect(events.user.updated).toBeCalledTimes(1)
expect(events.role.unassigned).toBeCalledTimes(1)
expect(events.role.unassigned).toBeCalledWith("role2")
expect(events.role.unassigned).toBeCalledWith(savedUser, "role2")
expect(events.role.assigned).toBeCalledTimes(1)
expect(events.role.assigned).toBeCalledWith("role2-edit")
expect(events.role.assigned).toBeCalledWith(savedUser, "role2-edit")
})
})

View File

@ -127,6 +127,7 @@ export const save = async (
} else {
response = await putUserFn()
}
user._rev = response.rev
eventHelpers.handleSaveEvents(user, dbUser)