trimViewRowInfo middleware and api test
This commit is contained in:
parent
5052f2cd68
commit
eaa7d9bf81
|
@ -93,7 +93,6 @@ export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function save(ctx: UserCtx) {
|
export async function save(ctx: UserCtx) {
|
||||||
const db = context.getAppDB()
|
|
||||||
let inputs = ctx.request.body
|
let inputs = ctx.request.body
|
||||||
inputs.tableId = ctx.params.tableId
|
inputs.tableId = ctx.params.tableId
|
||||||
|
|
||||||
|
@ -177,7 +176,6 @@ export async function destroy(ctx: UserCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function bulkDestroy(ctx: UserCtx) {
|
export async function bulkDestroy(ctx: UserCtx) {
|
||||||
const db = context.getAppDB()
|
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
const table = await sdk.tables.getTable(tableId)
|
const table = await sdk.tables.getTable(tableId)
|
||||||
let { rows } = ctx.request.body
|
let { rows } = ctx.request.body
|
||||||
|
@ -206,6 +204,7 @@ export async function bulkDestroy(ctx: UserCtx) {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
const db = context.getAppDB()
|
||||||
await db.bulkDocs(processedRows.map(row => ({ ...row, _deleted: true })))
|
await db.bulkDocs(processedRows.map(row => ({ ...row, _deleted: true })))
|
||||||
}
|
}
|
||||||
// remove any attachments that were on the rows from object storage
|
// remove any attachments that were on the rows from object storage
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { paramResource, paramSubResource } from "../../middleware/resourceId"
|
||||||
import { permissions } from "@budibase/backend-core"
|
import { permissions } from "@budibase/backend-core"
|
||||||
import { internalSearchValidator } from "./utils/validators"
|
import { internalSearchValidator } from "./utils/validators"
|
||||||
import noViewData from "../../middleware/noViewData"
|
import noViewData from "../../middleware/noViewData"
|
||||||
|
import trimViewRowInfo from "../../middleware/trimViewRowInfo"
|
||||||
const { PermissionType, PermissionLevel } = permissions
|
const { PermissionType, PermissionLevel } = permissions
|
||||||
|
|
||||||
const router: Router = new Router()
|
const router: Router = new Router()
|
||||||
|
@ -301,6 +302,7 @@ router
|
||||||
"/api/v2/views/:viewId/rows",
|
"/api/v2/views/:viewId/rows",
|
||||||
paramResource("viewId"),
|
paramResource("viewId"),
|
||||||
authorized(PermissionType.VIEW, PermissionLevel.WRITE),
|
authorized(PermissionType.VIEW, PermissionLevel.WRITE),
|
||||||
|
trimViewRowInfo(),
|
||||||
rowController.save
|
rowController.save
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1039,4 +1039,80 @@ describe("/rows", () => {
|
||||||
expect(response.body.rows).toHaveLength(0)
|
expect(response.body.rows).toHaveLength(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("view 2.0", () => {
|
||||||
|
function userTable(): Table {
|
||||||
|
return {
|
||||||
|
name: "user",
|
||||||
|
type: "user",
|
||||||
|
schema: {
|
||||||
|
name: {
|
||||||
|
type: FieldType.STRING,
|
||||||
|
name: "name",
|
||||||
|
},
|
||||||
|
surname: {
|
||||||
|
type: FieldType.STRING,
|
||||||
|
name: "name",
|
||||||
|
},
|
||||||
|
age: {
|
||||||
|
type: FieldType.NUMBER,
|
||||||
|
name: "age",
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
type: FieldType.STRING,
|
||||||
|
name: "address",
|
||||||
|
},
|
||||||
|
jobTitle: {
|
||||||
|
type: FieldType.STRING,
|
||||||
|
name: "jobTitle",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const randomRowData = () => ({
|
||||||
|
name: generator.first(),
|
||||||
|
surname: generator.last(),
|
||||||
|
age: generator.age(),
|
||||||
|
address: generator.address(),
|
||||||
|
jobTitle: generator.word(),
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("create", () => {
|
||||||
|
it("should persist a new row with only the provided view fields", async () => {
|
||||||
|
const table = await config.createTable(userTable())
|
||||||
|
const view = await config.api.viewV2.create({
|
||||||
|
tableId: table._id!,
|
||||||
|
columns: {
|
||||||
|
name: { visible: true },
|
||||||
|
surname: { visible: true },
|
||||||
|
address: { visible: true },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = randomRowData()
|
||||||
|
const newRow = await config.api.viewV2.row.create(view.id, {
|
||||||
|
tableId: config.table!._id,
|
||||||
|
_viewId: view.id,
|
||||||
|
...data,
|
||||||
|
})
|
||||||
|
|
||||||
|
const row = await config.api.row.get(table._id!, newRow._id!)
|
||||||
|
expect(row.body).toEqual({
|
||||||
|
name: data.name,
|
||||||
|
surname: data.surname,
|
||||||
|
address: data.address,
|
||||||
|
tableId: config.table!._id,
|
||||||
|
type: "row",
|
||||||
|
_id: expect.any(String),
|
||||||
|
_rev: expect.any(String),
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
updatedAt: expect.any(String),
|
||||||
|
})
|
||||||
|
expect(row.body._viewId).toBeUndefined()
|
||||||
|
expect(row.body.age).toBeUndefined()
|
||||||
|
expect(row.body.jobTitle).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { Ctx, Row } from "@budibase/types"
|
||||||
|
import * as utils from "../db/utils"
|
||||||
|
import sdk from "../sdk"
|
||||||
|
import { db } from "@budibase/backend-core"
|
||||||
|
|
||||||
|
export default () => async (ctx: Ctx<Row>, next: any) => {
|
||||||
|
const { body } = ctx.request
|
||||||
|
const { _viewId: viewId } = body
|
||||||
|
if (!viewId) {
|
||||||
|
ctx.throw(400, "_viewId is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tableId } = utils.extractViewInfoFromID(viewId)
|
||||||
|
const { _viewId, ...trimmedView } = await trimViewFields(
|
||||||
|
viewId,
|
||||||
|
tableId,
|
||||||
|
body
|
||||||
|
)
|
||||||
|
ctx.request.body = trimmedView
|
||||||
|
ctx.params.tableId = body.tableId
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function trimViewFields<T extends Row>(
|
||||||
|
viewId: string,
|
||||||
|
tableId: string,
|
||||||
|
data: T
|
||||||
|
): Promise<T> {
|
||||||
|
const view = await sdk.views.get(viewId)
|
||||||
|
if (!view?.columns || !Object.keys(view.columns).length) {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
const table = await sdk.tables.getTable(tableId)
|
||||||
|
const { schema } = sdk.views.enrichSchema(view!, table.schema)
|
||||||
|
const result: Record<string, any> = {}
|
||||||
|
for (const key of [
|
||||||
|
...Object.keys(schema),
|
||||||
|
...db.CONSTANT_EXTERNAL_ROW_COLS,
|
||||||
|
...db.CONSTANT_INTERNAL_ROW_COLS,
|
||||||
|
]) {
|
||||||
|
result[key] = data[key] !== null ? data[key] : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return result as T
|
||||||
|
}
|
|
@ -1,90 +0,0 @@
|
||||||
import { generator } from "@budibase/backend-core/tests"
|
|
||||||
import { FieldType, Table } from "@budibase/types"
|
|
||||||
jest.mock("../../../../sdk", () => ({
|
|
||||||
views: {
|
|
||||||
...jest.requireActual("../../../../sdk/app/views"),
|
|
||||||
get: jest.fn(),
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
import sdk from "../../../../sdk"
|
|
||||||
import { trimViewFields } from "../utils"
|
|
||||||
|
|
||||||
const mockGetView = sdk.views.get as jest.MockedFunction<typeof sdk.views.get>
|
|
||||||
|
|
||||||
describe("utils", () => {
|
|
||||||
const table: Table = {
|
|
||||||
name: generator.word(),
|
|
||||||
type: "table",
|
|
||||||
schema: {
|
|
||||||
name: {
|
|
||||||
name: "name",
|
|
||||||
type: FieldType.STRING,
|
|
||||||
},
|
|
||||||
age: {
|
|
||||||
name: "age",
|
|
||||||
type: FieldType.NUMBER,
|
|
||||||
},
|
|
||||||
address: {
|
|
||||||
name: "address",
|
|
||||||
type: FieldType.STRING,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.resetAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("trimViewFields", () => {
|
|
||||||
it("when no columns are defined, same data is returned", async () => {
|
|
||||||
mockGetView.mockResolvedValue({
|
|
||||||
version: 2,
|
|
||||||
id: generator.guid(),
|
|
||||||
name: generator.guid(),
|
|
||||||
tableId: generator.guid(),
|
|
||||||
})
|
|
||||||
|
|
||||||
const viewId = generator.guid()
|
|
||||||
const data = {
|
|
||||||
_id: generator.guid(),
|
|
||||||
name: generator.name(),
|
|
||||||
age: generator.age(),
|
|
||||||
address: generator.address(),
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await trimViewFields(viewId, table, data)
|
|
||||||
|
|
||||||
expect(result).toBe(data)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("when columns are defined, trim data is returned", async () => {
|
|
||||||
mockGetView.mockResolvedValue({
|
|
||||||
version: 2,
|
|
||||||
id: generator.guid(),
|
|
||||||
name: generator.guid(),
|
|
||||||
tableId: generator.guid(),
|
|
||||||
columns: {
|
|
||||||
name: { visible: true },
|
|
||||||
address: { visible: true },
|
|
||||||
age: { visible: false },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const viewId = generator.guid()
|
|
||||||
const data = {
|
|
||||||
_id: generator.guid(),
|
|
||||||
name: generator.name(),
|
|
||||||
age: generator.age(),
|
|
||||||
address: generator.address(),
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await trimViewFields(viewId, table, data)
|
|
||||||
|
|
||||||
expect(result).toEqual({
|
|
||||||
name: data.name,
|
|
||||||
address: data.address,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -135,22 +135,3 @@ export async function validate({
|
||||||
}
|
}
|
||||||
return { valid: Object.keys(errors).length === 0, errors }
|
return { valid: Object.keys(errors).length === 0, errors }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function trimViewFields<T extends Row>(
|
|
||||||
viewId: string,
|
|
||||||
table: Table,
|
|
||||||
data: T
|
|
||||||
): Promise<T> {
|
|
||||||
const view = await sdk.views.get(viewId)
|
|
||||||
if (!view?.columns || !Object.keys(view.columns).length) {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
const { schema } = sdk.views.enrichSchema(view!, table.schema)
|
|
||||||
const result: Record<string, any> = {}
|
|
||||||
for (const key of Object.keys(schema)) {
|
|
||||||
result[key] = data[key] !== null ? data[key] : undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return result as T
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,6 +7,14 @@ export class RowAPI extends TestAPI {
|
||||||
super(config)
|
super(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get = async (tableId: string, rowId: string) => {
|
||||||
|
return await this.request
|
||||||
|
.get(`/api/${tableId}/rows/${rowId}`)
|
||||||
|
.set(this.config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
}
|
||||||
|
|
||||||
patch = async (
|
patch = async (
|
||||||
tableId: string,
|
tableId: string,
|
||||||
row: PatchRowRequest,
|
row: PatchRowRequest,
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
import { CreateViewRequest, SortOrder, SortType, ViewV2 } from "@budibase/types"
|
import {
|
||||||
|
CreateViewRequest,
|
||||||
|
Row,
|
||||||
|
SortOrder,
|
||||||
|
SortType,
|
||||||
|
ViewV2,
|
||||||
|
} from "@budibase/types"
|
||||||
import TestConfiguration from "../TestConfiguration"
|
import TestConfiguration from "../TestConfiguration"
|
||||||
import { TestAPI } from "./base"
|
import { TestAPI } from "./base"
|
||||||
import { generator } from "@budibase/backend-core/tests"
|
import { generator } from "@budibase/backend-core/tests"
|
||||||
|
@ -93,4 +99,20 @@ export class ViewV2API extends TestAPI {
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(expectStatus)
|
.expect(expectStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
row = {
|
||||||
|
create: async (
|
||||||
|
viewId: string,
|
||||||
|
row: Row,
|
||||||
|
{ expectStatus } = { expectStatus: 200 }
|
||||||
|
): Promise<Row> => {
|
||||||
|
const result = await this.request
|
||||||
|
.post(`/api/v2/views/${viewId}/rows`)
|
||||||
|
.send(row)
|
||||||
|
.set(this.config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(expectStatus)
|
||||||
|
return result.body as Row
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue