Merge branch 'master' of github.com:Budibase/budibase into v3-ui
This commit is contained in:
commit
d1bc83ab0a
|
@ -272,6 +272,7 @@ export const flags = new FlagSet({
|
||||||
SQS: Flag.boolean(env.isDev()),
|
SQS: Flag.boolean(env.isDev()),
|
||||||
[FeatureFlag.AI_CUSTOM_CONFIGS]: Flag.boolean(env.isDev()),
|
[FeatureFlag.AI_CUSTOM_CONFIGS]: Flag.boolean(env.isDev()),
|
||||||
[FeatureFlag.ENRICHED_RELATIONSHIPS]: Flag.boolean(env.isDev()),
|
[FeatureFlag.ENRICHED_RELATIONSHIPS]: Flag.boolean(env.isDev()),
|
||||||
|
[FeatureFlag.TABLES_DEFAULT_ADMIN]: Flag.boolean(env.isDev()),
|
||||||
})
|
})
|
||||||
|
|
||||||
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T
|
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T
|
||||||
|
|
|
@ -28,6 +28,7 @@ import {
|
||||||
import { cloneDeep } from "lodash"
|
import { cloneDeep } from "lodash"
|
||||||
import { generateIdForRow } from "./utils"
|
import { generateIdForRow } from "./utils"
|
||||||
import { helpers } from "@budibase/shared-core"
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
import { HTTPError } from "@budibase/backend-core"
|
||||||
|
|
||||||
export async function handleRequest<T extends Operation>(
|
export async function handleRequest<T extends Operation>(
|
||||||
operation: T,
|
operation: T,
|
||||||
|
@ -102,6 +103,11 @@ export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
|
||||||
|
|
||||||
export async function destroy(ctx: UserCtx) {
|
export async function destroy(ctx: UserCtx) {
|
||||||
const source = await utils.getSource(ctx)
|
const source = await utils.getSource(ctx)
|
||||||
|
|
||||||
|
if (sdk.views.isView(source) && helpers.views.isCalculationView(source)) {
|
||||||
|
throw new HTTPError("Cannot delete rows through a calculation view", 400)
|
||||||
|
}
|
||||||
|
|
||||||
const _id = ctx.request.body._id
|
const _id = ctx.request.body._id
|
||||||
const { row } = await handleRequest(Operation.DELETE, source, {
|
const { row } = await handleRequest(Operation.DELETE, source, {
|
||||||
id: breakRowIdField(_id),
|
id: breakRowIdField(_id),
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
} from "../../../utilities/rowProcessor"
|
} from "../../../utilities/rowProcessor"
|
||||||
import * as utils from "./utils"
|
import * as utils from "./utils"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { context } from "@budibase/backend-core"
|
import { context, HTTPError } from "@budibase/backend-core"
|
||||||
import { finaliseRow, updateRelatedFormula } from "./staticFormula"
|
import { finaliseRow, updateRelatedFormula } from "./staticFormula"
|
||||||
import {
|
import {
|
||||||
FieldType,
|
FieldType,
|
||||||
|
@ -16,6 +16,7 @@ import {
|
||||||
PatchRowRequest,
|
PatchRowRequest,
|
||||||
PatchRowResponse,
|
PatchRowResponse,
|
||||||
Row,
|
Row,
|
||||||
|
Table,
|
||||||
UserCtx,
|
UserCtx,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
|
@ -97,15 +98,26 @@ export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
|
||||||
|
|
||||||
export async function destroy(ctx: UserCtx) {
|
export async function destroy(ctx: UserCtx) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const { tableId } = utils.getSourceId(ctx)
|
const source = await utils.getSource(ctx)
|
||||||
|
|
||||||
|
if (sdk.views.isView(source) && helpers.views.isCalculationView(source)) {
|
||||||
|
throw new HTTPError("Cannot delete rows through a calculation view", 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
let table: Table
|
||||||
|
if (sdk.views.isView(source)) {
|
||||||
|
table = await sdk.views.getTable(source.id)
|
||||||
|
} else {
|
||||||
|
table = source
|
||||||
|
}
|
||||||
|
|
||||||
const { _id } = ctx.request.body
|
const { _id } = ctx.request.body
|
||||||
let row = await db.get<Row>(_id)
|
let row = await db.get<Row>(_id)
|
||||||
let _rev = ctx.request.body._rev || row._rev
|
let _rev = ctx.request.body._rev || row._rev
|
||||||
|
|
||||||
if (row.tableId !== tableId) {
|
if (row.tableId !== table._id) {
|
||||||
throw "Supplied tableId doesn't match the row's tableId"
|
throw "Supplied tableId doesn't match the row's tableId"
|
||||||
}
|
}
|
||||||
const table = await sdk.tables.getTable(tableId)
|
|
||||||
// update the row to include full relationships before deleting them
|
// update the row to include full relationships before deleting them
|
||||||
row = await outputProcessing(table, row, {
|
row = await outputProcessing(table, row, {
|
||||||
squash: false,
|
squash: false,
|
||||||
|
@ -115,7 +127,7 @@ export async function destroy(ctx: UserCtx) {
|
||||||
await linkRows.updateLinks({
|
await linkRows.updateLinks({
|
||||||
eventType: linkRows.EventType.ROW_DELETE,
|
eventType: linkRows.EventType.ROW_DELETE,
|
||||||
row,
|
row,
|
||||||
tableId,
|
tableId: table._id!,
|
||||||
})
|
})
|
||||||
// remove any attachments that were on the row from object storage
|
// remove any attachments that were on the row from object storage
|
||||||
await AttachmentCleanup.rowDelete(table, [row])
|
await AttachmentCleanup.rowDelete(table, [row])
|
||||||
|
@ -123,7 +135,7 @@ export async function destroy(ctx: UserCtx) {
|
||||||
await updateRelatedFormula(table, row)
|
await updateRelatedFormula(table, row)
|
||||||
|
|
||||||
let response
|
let response
|
||||||
if (tableId === InternalTables.USER_METADATA) {
|
if (table._id === InternalTables.USER_METADATA) {
|
||||||
ctx.params = {
|
ctx.params = {
|
||||||
id: _id,
|
id: _id,
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,9 +133,7 @@ export async function buildSqlFieldList(
|
||||||
|
|
||||||
let fields: string[] = []
|
let fields: string[] = []
|
||||||
if (sdk.views.isView(source)) {
|
if (sdk.views.isView(source)) {
|
||||||
fields = Object.keys(helpers.views.basicFields(source)).filter(
|
fields = Object.keys(helpers.views.basicFields(source))
|
||||||
key => source.schema?.[key]?.visible !== false
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
fields = extractRealFields(source)
|
fields = extractRealFields(source)
|
||||||
}
|
}
|
||||||
|
|
|
@ -800,6 +800,34 @@ describe.each([
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
isInternal &&
|
||||||
|
it("shouldn't trigger a complex type check on a group by field if field is invisible", async () => {
|
||||||
|
const table = await config.api.table.save(
|
||||||
|
saveTableRequest({
|
||||||
|
schema: {
|
||||||
|
field: {
|
||||||
|
name: "field",
|
||||||
|
type: FieldType.JSON,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await config.api.viewV2.create(
|
||||||
|
{
|
||||||
|
tableId: table._id!,
|
||||||
|
name: generator.guid(),
|
||||||
|
type: ViewV2Type.CALCULATION,
|
||||||
|
schema: {
|
||||||
|
field: { visible: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 201,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("update", () => {
|
describe("update", () => {
|
||||||
|
@ -1904,6 +1932,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", () => {
|
describe("row operations", () => {
|
||||||
|
@ -2148,6 +2229,32 @@ describe.each([
|
||||||
})
|
})
|
||||||
await config.api.row.get(table._id!, rows[1]._id!, { status: 200 })
|
await config.api.row.get(table._id!, rows[1]._id!, { status: 200 })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should not be possible to delete a row in a calculation view", async () => {
|
||||||
|
const row = await config.api.row.save(table._id!, {})
|
||||||
|
|
||||||
|
const view = await config.api.viewV2.create({
|
||||||
|
tableId: table._id!,
|
||||||
|
name: generator.guid(),
|
||||||
|
type: ViewV2Type.CALCULATION,
|
||||||
|
schema: {
|
||||||
|
id: { visible: true },
|
||||||
|
one: { visible: true },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await config.api.row.delete(
|
||||||
|
view.id,
|
||||||
|
{ _id: row._id! },
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
message: "Cannot delete rows through a calculation view",
|
||||||
|
status: 400,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("read", () => {
|
describe("read", () => {
|
||||||
|
@ -3157,6 +3264,123 @@ describe.each([
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should be able to filter rows on the view itself", async () => {
|
||||||
|
const table = await config.api.table.save(
|
||||||
|
saveTableRequest({
|
||||||
|
schema: {
|
||||||
|
quantity: {
|
||||||
|
type: FieldType.NUMBER,
|
||||||
|
name: "quantity",
|
||||||
|
},
|
||||||
|
price: {
|
||||||
|
type: FieldType.NUMBER,
|
||||||
|
name: "price",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const view = await config.api.viewV2.create({
|
||||||
|
tableId: table._id!,
|
||||||
|
name: generator.guid(),
|
||||||
|
type: ViewV2Type.CALCULATION,
|
||||||
|
query: {
|
||||||
|
equal: {
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
sum: {
|
||||||
|
visible: true,
|
||||||
|
calculationType: CalculationType.SUM,
|
||||||
|
field: "price",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await config.api.row.bulkImport(table._id!, {
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
quantity: 1,
|
||||||
|
price: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quantity: 1,
|
||||||
|
price: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quantity: 2,
|
||||||
|
price: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const { rows } = await config.api.viewV2.search(view.id, {
|
||||||
|
query: {},
|
||||||
|
})
|
||||||
|
expect(rows).toHaveLength(1)
|
||||||
|
expect(rows[0].sum).toEqual(3)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to filter on group by fields", async () => {
|
||||||
|
const table = await config.api.table.save(
|
||||||
|
saveTableRequest({
|
||||||
|
schema: {
|
||||||
|
quantity: {
|
||||||
|
type: FieldType.NUMBER,
|
||||||
|
name: "quantity",
|
||||||
|
},
|
||||||
|
price: {
|
||||||
|
type: FieldType.NUMBER,
|
||||||
|
name: "price",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const view = await config.api.viewV2.create({
|
||||||
|
tableId: table._id!,
|
||||||
|
name: generator.guid(),
|
||||||
|
type: ViewV2Type.CALCULATION,
|
||||||
|
schema: {
|
||||||
|
quantity: { visible: true },
|
||||||
|
sum: {
|
||||||
|
visible: true,
|
||||||
|
calculationType: CalculationType.SUM,
|
||||||
|
field: "price",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await config.api.row.bulkImport(table._id!, {
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
quantity: 1,
|
||||||
|
price: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quantity: 1,
|
||||||
|
price: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quantity: 2,
|
||||||
|
price: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const { rows } = await config.api.viewV2.search(view.id, {
|
||||||
|
query: {
|
||||||
|
equal: {
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(rows).toHaveLength(1)
|
||||||
|
expect(rows[0].sum).toEqual(3)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
!isLucene &&
|
!isLucene &&
|
||||||
|
|
|
@ -20,29 +20,28 @@ const OPTIONAL_NUMBER = Joi.number().optional().allow(null)
|
||||||
const OPTIONAL_BOOLEAN = Joi.boolean().optional().allow(null)
|
const OPTIONAL_BOOLEAN = Joi.boolean().optional().allow(null)
|
||||||
const APP_NAME_REGEX = /^[\w\s]+$/
|
const APP_NAME_REGEX = /^[\w\s]+$/
|
||||||
|
|
||||||
const validateViewSchemas: CustomValidator<Table> = (table, helpers) => {
|
const validateViewSchemas: CustomValidator<Table> = (table, joiHelpers) => {
|
||||||
if (table.views && Object.entries(table.views).length) {
|
if (!table.views || Object.keys(table.views).length === 0) {
|
||||||
const requiredFields = Object.entries(table.schema)
|
return table
|
||||||
.filter(([_, v]) => isRequired(v.constraints))
|
}
|
||||||
|
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)
|
.map(([key]) => key)
|
||||||
if (requiredFields.length) {
|
const missingField = required.find(f => !editable.includes(f))
|
||||||
for (const view of Object.values(table.views)) {
|
if (missingField) {
|
||||||
if (!sdk.views.isV2(view)) {
|
return joiHelpers.message({
|
||||||
continue
|
custom: `To make field "${missingField}" required, this field must be present and writable in views: ${view.name}.`,
|
||||||
}
|
})
|
||||||
|
|
||||||
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}.`,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return table
|
return table
|
||||||
|
|
|
@ -68,9 +68,7 @@ async function buildInternalFieldList(
|
||||||
const { relationships, allowedFields } = opts || {}
|
const { relationships, allowedFields } = opts || {}
|
||||||
let schemaFields: string[] = []
|
let schemaFields: string[] = []
|
||||||
if (sdk.views.isView(source)) {
|
if (sdk.views.isView(source)) {
|
||||||
schemaFields = Object.keys(helpers.views.basicFields(source)).filter(
|
schemaFields = Object.keys(helpers.views.basicFields(source))
|
||||||
key => source.schema?.[key]?.visible !== false
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
schemaFields = Object.keys(source.schema).filter(
|
schemaFields = Object.keys(source.schema).filter(
|
||||||
key => source.schema[key].visible !== false
|
key => source.schema[key].visible !== false
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { Row, Table } from "@budibase/types"
|
import { FeatureFlag, Row, Table } from "@budibase/types"
|
||||||
|
|
||||||
import * as external from "./external"
|
import * as external from "./external"
|
||||||
import * as internal from "./internal"
|
import * as internal from "./internal"
|
||||||
import { isExternal } from "./utils"
|
import { isExternal } from "./utils"
|
||||||
import { setPermissions } from "../permissions"
|
import { setPermissions } from "../permissions"
|
||||||
import { roles } from "@budibase/backend-core"
|
import { features, roles } from "@budibase/backend-core"
|
||||||
|
|
||||||
export async function create(
|
export async function create(
|
||||||
table: Omit<Table, "_id" | "_rev">,
|
table: Omit<Table, "_id" | "_rev">,
|
||||||
|
@ -18,10 +18,16 @@ export async function create(
|
||||||
createdTable = await internal.create(table, rows, userId)
|
createdTable = await internal.create(table, rows, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
await setPermissions(createdTable._id!, {
|
const setExplicitPermission = await features.flags.isEnabled(
|
||||||
writeRole: roles.BUILTIN_ROLE_IDS.ADMIN,
|
FeatureFlag.TABLES_DEFAULT_ADMIN
|
||||||
readRole: roles.BUILTIN_ROLE_IDS.ADMIN,
|
)
|
||||||
})
|
|
||||||
|
if (setExplicitPermission) {
|
||||||
|
await setPermissions(createdTable._id!, {
|
||||||
|
writeRole: roles.BUILTIN_ROLE_IDS.ADMIN,
|
||||||
|
readRole: roles.BUILTIN_ROLE_IDS.ADMIN,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return createdTable
|
return createdTable
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
CalculationType,
|
CalculationType,
|
||||||
canGroupBy,
|
canGroupBy,
|
||||||
|
FeatureFlag,
|
||||||
FieldType,
|
FieldType,
|
||||||
isNumeric,
|
isNumeric,
|
||||||
PermissionLevel,
|
PermissionLevel,
|
||||||
|
@ -13,7 +14,7 @@ import {
|
||||||
ViewV2ColumnEnriched,
|
ViewV2ColumnEnriched,
|
||||||
ViewV2Enriched,
|
ViewV2Enriched,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { context, docIds, HTTPError } from "@budibase/backend-core"
|
import { context, docIds, features, HTTPError } from "@budibase/backend-core"
|
||||||
import {
|
import {
|
||||||
helpers,
|
helpers,
|
||||||
PROTECTED_EXTERNAL_COLUMNS,
|
PROTECTED_EXTERNAL_COLUMNS,
|
||||||
|
@ -94,6 +95,13 @@ async function guardCalculationViewSchema(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!schema.field) {
|
||||||
|
throw new HTTPError(
|
||||||
|
`Calculation field "${name}" is missing a "field" property`,
|
||||||
|
400
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const targetSchema = table.schema[schema.field]
|
const targetSchema = table.schema[schema.field]
|
||||||
if (!targetSchema) {
|
if (!targetSchema) {
|
||||||
throw new HTTPError(
|
throw new HTTPError(
|
||||||
|
@ -241,12 +249,17 @@ export async function create(
|
||||||
await guardViewSchema(tableId, viewRequest)
|
await guardViewSchema(tableId, viewRequest)
|
||||||
const view = await pickApi(tableId).create(tableId, viewRequest)
|
const view = await pickApi(tableId).create(tableId, viewRequest)
|
||||||
|
|
||||||
// Set permissions to be the same as the table
|
const setExplicitPermission = await features.flags.isEnabled(
|
||||||
const tablePerms = await sdk.permissions.getResourcePerms(tableId)
|
FeatureFlag.TABLES_DEFAULT_ADMIN
|
||||||
await sdk.permissions.setPermissions(view.id, {
|
)
|
||||||
writeRole: tablePerms[PermissionLevel.WRITE].role,
|
if (setExplicitPermission) {
|
||||||
readRole: tablePerms[PermissionLevel.READ].role,
|
// Set permissions to be the same as the table
|
||||||
})
|
const tablePerms = await sdk.permissions.getResourcePerms(tableId)
|
||||||
|
await sdk.permissions.setPermissions(view.id, {
|
||||||
|
writeRole: tablePerms[PermissionLevel.WRITE].role,
|
||||||
|
readRole: tablePerms[PermissionLevel.READ].role,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
@ -368,7 +381,8 @@ export function syncSchema(
|
||||||
|
|
||||||
if (view.schema) {
|
if (view.schema) {
|
||||||
for (const fieldName of Object.keys(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]
|
delete view.schema[fieldName]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,13 @@ export function calculationFields(view: UnsavedViewV2) {
|
||||||
return pickBy(view.schema || {}, isCalculationField)
|
return pickBy(view.schema || {}, isCalculationField)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function basicFields(view: UnsavedViewV2) {
|
export function isVisible(field: ViewFieldMetadata) {
|
||||||
return pickBy(view.schema || {}, field => !isCalculationField(field))
|
return field.visible !== false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function basicFields(view: UnsavedViewV2, opts?: { visible?: boolean }) {
|
||||||
|
const { visible = true } = opts || {}
|
||||||
|
return pickBy(view.schema || {}, field => {
|
||||||
|
return !isCalculationField(field) && (!visible || isVisible(field))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ export enum FeatureFlag {
|
||||||
PER_CREATOR_PER_USER_PRICE_ALERT = "PER_CREATOR_PER_USER_PRICE_ALERT",
|
PER_CREATOR_PER_USER_PRICE_ALERT = "PER_CREATOR_PER_USER_PRICE_ALERT",
|
||||||
AI_CUSTOM_CONFIGS = "AI_CUSTOM_CONFIGS",
|
AI_CUSTOM_CONFIGS = "AI_CUSTOM_CONFIGS",
|
||||||
ENRICHED_RELATIONSHIPS = "ENRICHED_RELATIONSHIPS",
|
ENRICHED_RELATIONSHIPS = "ENRICHED_RELATIONSHIPS",
|
||||||
|
TABLES_DEFAULT_ADMIN = "TABLES_DEFAULT_ADMIN",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TenantFeatureFlags {
|
export interface TenantFeatureFlags {
|
||||||
|
|
Loading…
Reference in New Issue