Mark calculation views explicitly instead of figuring it out implicitly.

This commit is contained in:
Sam Rose 2024-10-07 16:33:14 +01:00
parent d518f66cae
commit 672469526e
No known key found for this signature in database
7 changed files with 88 additions and 2 deletions

View File

@ -127,6 +127,7 @@ export async function create(ctx: Ctx<CreateViewRequest, ViewResponse>) {
const parsedView: Omit<RequiredKeys<ViewV2>, "id" | "version"> = {
name: view.name,
type: view.type,
tableId: view.tableId,
query: view.query,
queryUI: view.queryUI,
@ -163,6 +164,7 @@ export async function update(ctx: Ctx<UpdateViewRequest, ViewResponse>) {
const parsedView: RequiredKeys<ViewV2> = {
id: view.id,
name: view.name,
type: view.type,
version: view.version,
tableId: view.tableId,
query: view.query,

View File

@ -26,6 +26,7 @@ import {
BBReferenceFieldSubType,
NumericCalculationFieldMetadata,
ViewV2Schema,
ViewV2Type,
} from "@budibase/types"
import { generator, mocks } from "@budibase/backend-core/tests"
import { DatabaseName, getDatasource } from "../../../integrations/tests/utils"
@ -155,7 +156,7 @@ describe.each([
})
it("can persist views with all fields", async () => {
const newView: Required<Omit<CreateViewRequest, "queryUI">> = {
const newView: Required<Omit<CreateViewRequest, "queryUI" | "type">> = {
name: generator.name(),
tableId: table._id!,
primaryDisplay: "id",
@ -549,6 +550,7 @@ describe.each([
let view = await config.api.viewV2.create({
tableId: table._id!,
name: generator.guid(),
type: ViewV2Type.CALCULATION,
schema: {
sum: {
visible: true,
@ -571,6 +573,29 @@ describe.each([
expect(sum.calculationType).toEqual(CalculationType.SUM)
expect(sum.field).toEqual("Price")
})
it("cannot create a view with calculation fields unless it has the right type", async () => {
await config.api.viewV2.create(
{
tableId: table._id!,
name: generator.guid(),
schema: {
sum: {
visible: true,
calculationType: CalculationType.SUM,
field: "Price",
},
},
},
{
status: 400,
body: {
message:
"Calculation fields are not allowed in non-calculation views",
},
}
)
})
})
describe("update", () => {
@ -615,7 +640,9 @@ describe.each([
it("can update all fields", async () => {
const tableId = table._id!
const updatedData: Required<Omit<UpdateViewRequest, "queryUI">> = {
const updatedData: Required<
Omit<UpdateViewRequest, "queryUI" | "type">
> = {
version: view.version,
id: view.id,
tableId,
@ -830,6 +857,32 @@ describe.each([
)
})
it("cannot update view type after creation", async () => {
const view = await config.api.viewV2.create({
tableId: table._id!,
name: generator.guid(),
schema: {
id: { visible: true },
Price: {
visible: true,
},
},
})
await config.api.viewV2.update(
{
...view,
type: ViewV2Type.CALCULATION,
},
{
status: 400,
body: {
message: "Cannot update view type after creation",
},
}
)
})
isInternal &&
it("updating schema will only validate modified field", async () => {
let view = await config.api.viewV2.create({
@ -902,6 +955,7 @@ describe.each([
view = await config.api.viewV2.create({
tableId: table._id!,
name: generator.guid(),
type: ViewV2Type.CALCULATION,
schema: {
country: {
visible: true,
@ -2639,6 +2693,7 @@ describe.each([
it("should be able to search by calculations", async () => {
const view = await config.api.viewV2.create({
tableId: table._id!,
type: ViewV2Type.CALCULATION,
name: generator.guid(),
schema: {
"Quantity Sum": {
@ -2673,6 +2728,7 @@ describe.each([
const view = await config.api.viewV2.create({
tableId: table._id!,
name: generator.guid(),
type: ViewV2Type.CALCULATION,
schema: {
quantity: {
visible: true,
@ -2711,6 +2767,7 @@ describe.each([
const view = await config.api.viewV2.create({
tableId: table._id!,
name: generator.guid(),
type: ViewV2Type.CALCULATION,
schema: {
aggregate: {
visible: true,
@ -2771,6 +2828,7 @@ describe.each([
const view = await config.api.viewV2.create({
tableId: table._id!,
name: generator.guid(),
type: ViewV2Type.CALCULATION,
schema: {
count: {
visible: true,
@ -2805,6 +2863,7 @@ describe.each([
{
tableId: table._id!,
name: generator.guid(),
type: ViewV2Type.CALCULATION,
schema: {
count: {
visible: true,
@ -2853,6 +2912,7 @@ describe.each([
const view = await config.api.viewV2.create({
tableId: table._id!,
name: generator.guid(),
type: ViewV2Type.CALCULATION,
schema: {
sum: {
visible: true,

View File

@ -70,6 +70,9 @@ export async function update(tableId: string, view: ViewV2): Promise<ViewV2> {
if (!existingView || !existingView.name) {
throw new HTTPError(`View ${view.id} not found in table ${tableId}`, 404)
}
if (isV2(existingView) && existingView.type !== view.type) {
throw new HTTPError(`Cannot update view type after creation`, 400)
}
delete views[existingView.name]
views[view.name] = view

View File

@ -111,6 +111,13 @@ async function guardViewSchema(
if (helpers.views.isCalculationView(view)) {
await guardCalculationViewSchema(table, view)
} else {
if (helpers.views.hasCalculationFields(view)) {
throw new HTTPError(
"Calculation fields are not allowed in non-calculation views",
400
)
}
}
await checkReadonlyFields(table, view)

View File

@ -59,6 +59,10 @@ export async function update(tableId: string, view: ViewV2): Promise<ViewV2> {
throw new HTTPError(`View ${view.id} not found in table ${tableId}`, 404)
}
if (isV2(existingView) && existingView.type !== view.type) {
throw new HTTPError(`Cannot update view type after creation`, 400)
}
delete table.views[existingView.name]
table.views[view.name] = view
await db.put(table)

View File

@ -3,6 +3,7 @@ import {
ViewCalculationFieldMetadata,
ViewFieldMetadata,
ViewV2,
ViewV2Type,
} from "@budibase/types"
import { pickBy } from "lodash"
@ -21,6 +22,10 @@ export function isBasicViewField(
type UnsavedViewV2 = Omit<ViewV2, "id" | "version">
export function isCalculationView(view: UnsavedViewV2) {
return view.type === ViewV2Type.CALCULATION
}
export function hasCalculationFields(view: UnsavedViewV2) {
return Object.values(view.schema || {}).some(isCalculationField)
}

View File

@ -79,10 +79,15 @@ export enum CalculationType {
MAX = "max",
}
export enum ViewV2Type {
CALCULATION = "calculation",
}
export interface ViewV2 {
version: 2
id: string
name: string
type?: ViewV2Type
primaryDisplay?: string
tableId: string
query?: LegacyFilter[] | SearchFilters