Final update to view mapping and typing.

This commit is contained in:
mike12345567 2024-11-15 17:54:14 +00:00
parent 82c7c089cb
commit cdf7cb9fab
7 changed files with 124 additions and 68 deletions

View File

@ -47,12 +47,10 @@ export async function search(ctx: UserCtx, next: Next) {
export async function viewSearch(ctx: UserCtx, next: Next) {
ctx.request.body = getSearchParameters(ctx)
await rowController.views.searchView({
...ctx,
params: {
viewId: ctx.params.viewId,
},
})
ctx.params = {
viewId: ctx.params.viewId,
}
await rowController.views.searchView(ctx)
await next()
}

View File

@ -1,72 +1,88 @@
import { search as stringSearch } from "./utils"
import * as controller from "../view"
import { ViewV2, UserCtx } from "@budibase/types"
import { ViewV2, UserCtx, UISearchFilter, PublicAPIView } from "@budibase/types"
import { Next } from "koa"
import { merge } from "lodash"
function fixView(view: ViewV2, params?: { viewId: string }) {
if (!params || !view) {
return view
function viewRequest(view: PublicAPIView, params?: { viewId: string }) {
const viewV2: ViewV2 = view
if (!viewV2) {
return viewV2
}
if (params?.viewId) {
view.id = params.viewId
viewV2.id = params.viewId
}
if (!view.query) {
view.query = {}
viewV2.query = {}
} else {
// public API only has one form of query
viewV2.queryUI = viewV2.query as UISearchFilter
}
view.version = 2
return view
viewV2.version = 2
return viewV2
}
function viewResponse(view: ViewV2): PublicAPIView {
// remove our internal structure - always un-necessary
delete view.query
return {
...view,
query: view.queryUI,
}
}
function viewsResponse(views: ViewV2[]): PublicAPIView[] {
return views.map(viewResponse)
}
export async function search(ctx: UserCtx, next: Next) {
const { name } = ctx.request.body
await controller.v2.fetch(ctx)
ctx.body = stringSearch(ctx.body.data, name)
ctx.body.data = viewsResponse(stringSearch(ctx.body.data, name))
await next()
}
export async function create(ctx: UserCtx, next: Next) {
await controller.v2.create(
merge(ctx, {
request: {
body: fixView(ctx.request.body),
},
})
)
ctx = merge(ctx, {
request: {
body: viewRequest(ctx.request.body),
},
})
await controller.v2.create(ctx)
ctx.body.data = viewResponse(ctx.body.data)
await next()
}
export async function read(ctx: UserCtx, next: Next) {
await controller.v2.get(
merge(ctx, {
params: {
viewId: ctx.params.viewId,
},
})
)
ctx = merge(ctx, {
params: {
viewId: ctx.params.viewId,
},
})
await controller.v2.get(ctx)
ctx.body.data = viewResponse(ctx.body.data)
await next()
}
export async function update(ctx: UserCtx, next: Next) {
const viewId = ctx.params.viewId
await controller.v2.update(
merge(ctx, {
request: {
body: {
data: fixView(ctx.request.body, { viewId }),
},
ctx = merge(ctx, {
request: {
body: {
data: viewRequest(ctx.request.body, { viewId }),
},
params: {
viewId,
},
})
)
},
params: {
viewId,
},
})
await controller.v2.update(ctx)
ctx.body.data = viewResponse(ctx.body.data)
await next()
}
export async function destroy(ctx: UserCtx, next: Next) {
await controller.v2.remove(ctx)
ctx.body = ctx.table
await next()
}

View File

@ -50,6 +50,7 @@ export async function searchView(
result.rows.forEach(r => (r._viewId = view.id))
ctx.body = result
}
function getSortOptions(request: SearchViewRowRequest, view: ViewV2) {
if (request.sort) {
return {

View File

@ -1,4 +1,4 @@
import controller from "../../controllers/public/rows"
import controller, { viewSearch } from "../../controllers/public/rows"
import Endpoint from "./utils/Endpoint"
import { externalSearchValidator } from "../utils/validators"
@ -200,7 +200,7 @@ read.push(
new Endpoint(
"post",
"/views/:viewId/rows/search",
controller.search
controller.viewSearch
).addMiddleware(externalSearchValidator())
)

View File

@ -6,6 +6,7 @@ import {
ViewV2Schema,
ViewV2,
ViewV2Type,
PublicAPIView,
} from "@budibase/types"
import { HttpMethod, MakeRequestResponse, generateMakeRequest } from "./utils"
import TestConfiguration from "../../../../tests/utilities/TestConfiguration"
@ -148,17 +149,17 @@ export class PublicViewAPI {
}
async create(
view: Omit<ViewV2, "id" | "version">,
view: Omit<PublicAPIView, "id" | "version">,
expectations?: PublicAPIExpectations
): Promise<Response<ViewV2>> {
): Promise<Response<PublicAPIView>> {
return this.request.send("post", "/views", view, expectations)
}
async update(
viewId: string,
view: Omit<ViewV2, "id" | "version">,
view: Omit<PublicAPIView, "id" | "version">,
expectations?: PublicAPIExpectations
): Promise<Response<ViewV2>> {
): Promise<Response<PublicAPIView>> {
return this.request.send("put", `/views/${viewId}`, view, expectations)
}
@ -177,20 +178,14 @@ export class PublicViewAPI {
async find(
viewId: string,
expectations?: PublicAPIExpectations
): Promise<Response<ViewV2>> {
): Promise<Response<PublicAPIView>> {
return this.request.send("get", `/views/${viewId}`, undefined, expectations)
}
async fetch(
expectations?: PublicAPIExpectations
): Promise<Response<ViewV2[]>> {
return this.request.send("get", "/views", undefined, expectations)
}
async search(
viewName: string,
expectations?: PublicAPIExpectations
): Promise<Response<ViewV2[]>> {
): Promise<Response<PublicAPIView[]>> {
return this.request.send(
"post",
"/views/search",

View File

@ -1,6 +1,6 @@
import * as setup from "../../tests/utilities"
import { basicTable } from "../../../../tests/utilities/structures"
import { Table } from "@budibase/types"
import { BasicOperator, Table, UILogicalOperator } from "@budibase/types"
import { PublicAPIRequest } from "./Request"
import { generator } from "@budibase/backend-core/tests"
@ -34,14 +34,10 @@ describe("check public API security", () => {
it("should be able to update a view", async () => {
const view = await request.views.create(baseView(), { status: 201 })
await request.views.update(
view.data.id,
{
...view.data,
name: "new name",
},
{ status: 200 }
)
const response = await request.views.update(view.data.id, {
...view.data,
name: "new name",
})
})
it("should be able to search views", async () => {
@ -77,9 +73,18 @@ describe("check public API security", () => {
{
...baseView(),
query: {
string: {
name: "hello",
},
logicalOperator: UILogicalOperator.ANY,
groups: [
{
filters: [
{
operator: BasicOperator.STRING,
field: "name",
value: "hello",
},
],
},
],
},
},
{ status: 201 }

View File

@ -12,6 +12,10 @@ import {
ViewV2Type,
SortOrder,
SortType,
UILogicalOperator,
BasicOperator,
ArrayOperator,
RangeOperator,
} from "@budibase/types"
import Joi, { CustomValidator } from "joi"
import { ValidSnippetNameRegex, helpers } from "@budibase/shared-core"
@ -69,6 +73,43 @@ export function tableValidator() {
)
}
function searchUIFilterValidator() {
const logicalOperator = Joi.string().valid(
...Object.values(UILogicalOperator)
)
const operators = [
...Object.values(BasicOperator),
...Object.values(ArrayOperator),
...Object.values(RangeOperator),
]
const filters = Joi.array().items(
Joi.object({
operator: Joi.string()
.valid(...operators)
.required(),
field: Joi.string().required(),
// could do with better validation of value based on operator
value: Joi.any().required(),
})
)
return Joi.object({
logicalOperator,
onEmptyFilter: Joi.string().valid(...Object.values(EmptyFilterOption)),
groups: Joi.array().items(
Joi.object({
logicalOperator,
filters,
groups: Joi.array().items(
Joi.object({
filters,
logicalOperator,
})
),
})
),
})
}
export function viewValidator() {
return auth.joiValidator.body(
Joi.object({
@ -78,7 +119,7 @@ export function viewValidator() {
type: Joi.string().optional().valid(null, ViewV2Type.CALCULATION),
primaryDisplay: OPTIONAL_STRING,
schema: Joi.object().required(),
query: searchFiltersValidator().optional(),
query: searchUIFilterValidator().optional(),
sort: Joi.object({
field: Joi.string().required(),
order: Joi.string()