diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts
index e677fe302c..6ea2834373 100644
--- a/packages/server/src/api/routes/tests/viewV2.spec.ts
+++ b/packages/server/src/api/routes/tests/viewV2.spec.ts
@@ -1904,6 +1904,59 @@ describe.each([
})
})
})
+
+ describe("calculation views", () => {
+ it("should not remove calculation columns when modifying table schema", async () => {
+ let table = await config.api.table.save(
+ saveTableRequest({
+ schema: {
+ name: {
+ name: "name",
+ type: FieldType.STRING,
+ },
+ age: {
+ name: "age",
+ type: FieldType.NUMBER,
+ },
+ },
+ })
+ )
+
+ let view = await config.api.viewV2.create({
+ tableId: table._id!,
+ name: generator.guid(),
+ type: ViewV2Type.CALCULATION,
+ schema: {
+ sum: {
+ visible: true,
+ calculationType: CalculationType.SUM,
+ field: "age",
+ },
+ },
+ })
+
+ table = await config.api.table.get(table._id!)
+ await config.api.table.save({
+ ...table,
+ schema: {
+ ...table.schema,
+ name: {
+ name: "name",
+ type: FieldType.STRING,
+ constraints: { presence: true },
+ },
+ },
+ })
+
+ view = await config.api.viewV2.get(view.id)
+ expect(Object.keys(view.schema!).sort()).toEqual([
+ "age",
+ "id",
+ "name",
+ "sum",
+ ])
+ })
+ })
})
describe("row operations", () => {
diff --git a/packages/server/src/api/routes/utils/validators.ts b/packages/server/src/api/routes/utils/validators.ts
index b589d44b31..22b8d32978 100644
--- a/packages/server/src/api/routes/utils/validators.ts
+++ b/packages/server/src/api/routes/utils/validators.ts
@@ -20,29 +20,28 @@ const OPTIONAL_NUMBER = Joi.number().optional().allow(null)
const OPTIONAL_BOOLEAN = Joi.boolean().optional().allow(null)
const APP_NAME_REGEX = /^[\w\s]+$/
-const validateViewSchemas: CustomValidator
= (table, helpers) => {
- if (table.views && Object.entries(table.views).length) {
- const requiredFields = Object.entries(table.schema)
- .filter(([_, v]) => isRequired(v.constraints))
+const validateViewSchemas: CustomValidator = (table, joiHelpers) => {
+ if (!table.views || Object.keys(table.views).length === 0) {
+ return table
+ }
+ const required = Object.keys(table.schema).filter(key =>
+ isRequired(table.schema[key].constraints)
+ )
+ if (required.length === 0) {
+ return table
+ }
+ for (const view of Object.values(table.views)) {
+ if (!sdk.views.isV2(view) || helpers.views.isCalculationView(view)) {
+ continue
+ }
+ const editable = Object.entries(view.schema || {})
+ .filter(([_, f]) => f.visible && !f.readonly)
.map(([key]) => key)
- if (requiredFields.length) {
- for (const view of Object.values(table.views)) {
- if (!sdk.views.isV2(view)) {
- continue
- }
-
- const editableViewFields = Object.entries(view.schema || {})
- .filter(([_, f]) => f.visible && !f.readonly)
- .map(([key]) => key)
- const missingField = requiredFields.find(
- f => !editableViewFields.includes(f)
- )
- if (missingField) {
- return helpers.message({
- custom: `To make field "${missingField}" required, this field must be present and writable in views: ${view.name}.`,
- })
- }
- }
+ const missingField = required.find(f => !editable.includes(f))
+ if (missingField) {
+ return joiHelpers.message({
+ custom: `To make field "${missingField}" required, this field must be present and writable in views: ${view.name}.`,
+ })
}
}
return table
diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts
index 629454fecc..3549882199 100644
--- a/packages/server/src/sdk/app/views/index.ts
+++ b/packages/server/src/sdk/app/views/index.ts
@@ -96,6 +96,13 @@ async function guardCalculationViewSchema(
continue
}
+ if (!schema.field) {
+ throw new HTTPError(
+ `Calculation field "${name}" is missing a "field" property`,
+ 400
+ )
+ }
+
const targetSchema = table.schema[schema.field]
if (!targetSchema) {
throw new HTTPError(
@@ -371,7 +378,8 @@ export function syncSchema(
if (view.schema) {
for (const fieldName of Object.keys(view.schema)) {
- if (!schema[fieldName]) {
+ const viewSchema = view.schema[fieldName]
+ if (!helpers.views.isCalculationField(viewSchema) && !schema[fieldName]) {
delete view.schema[fieldName]
}
}