From 0cde00842188bdb2400f728167e3ac8cbfa7aa72 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Fri, 21 Jun 2024 17:01:27 +0100 Subject: [PATCH 0001/1152] Update docker-compose.yaml for SQS. --- hosting/docker-compose.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index a72b36aef1d..d9811935e44 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -27,6 +27,8 @@ services: BB_ADMIN_USER_PASSWORD: ${BB_ADMIN_USER_PASSWORD} PLUGINS_DIR: ${PLUGINS_DIR} OFFLINE_MODE: ${OFFLINE_MODE:-} + SQS_SEARCH_ENABLE: "true" + COUCH_DB_SQL_URL: "http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:4984" depends_on: - worker-service - redis-service @@ -54,6 +56,8 @@ services: REDIS_URL: redis-service:6379 REDIS_PASSWORD: ${REDIS_PASSWORD} OFFLINE_MODE: ${OFFLINE_MODE:-} + SQS_SEARCH_ENABLE: "true" + COUCH_DB_SQL_URL: "http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:4984" depends_on: - redis-service - minio-service @@ -97,7 +101,7 @@ services: couchdb-service: restart: unless-stopped - image: budibase/couchdb + image: budibase/couchdb:v3.3.3-sqs environment: - COUCHDB_PASSWORD=${COUCH_DB_PASSWORD} - COUCHDB_USER=${COUCH_DB_USER} From f64c48addff56882906d43a4978b3d80b81c99a7 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Fri, 21 Jun 2024 17:09:39 +0100 Subject: [PATCH 0002/1152] Add some jitter to the migration interval, and increase to a minimum of 5 seconds. --- packages/cli/src/hosting/utils.ts | 3 +- .../src/components/Updating.svelte | 34 ++++++++----------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/packages/cli/src/hosting/utils.ts b/packages/cli/src/hosting/utils.ts index 5c3ac33f44e..05eb18dc1d5 100644 --- a/packages/cli/src/hosting/utils.ts +++ b/packages/cli/src/hosting/utils.ts @@ -46,7 +46,8 @@ export function setServiceImage(service: string, image: string) { export async function downloadDockerCompose() { const filename = composeFilename() try { - await downloadFile(COMPOSE_URL, `./${filename}`) + fs.copyFileSync("../../hosting/docker-compose.yaml", `./${filename}`) + //await downloadFile(COMPOSE_URL, `./${filename}`) } catch (err) { console.error(error(`Failed to retrieve compose file - ${err}`)) } diff --git a/packages/frontend-core/src/components/Updating.svelte b/packages/frontend-core/src/components/Updating.svelte index 7d14e57aba3..311a6b91c8e 100644 --- a/packages/frontend-core/src/components/Updating.svelte +++ b/packages/frontend-core/src/components/Updating.svelte @@ -2,36 +2,30 @@ export let isMigrationDone export let onMigrationDone export let timeoutSeconds = 60 // 1 minute - export let minTimeSeconds = 3 - const loadTime = Date.now() - const intervalMs = 1000 let timedOut = false - let secondsWaited = 0 async function checkMigrationsFinished() { - setTimeout(async () => { - const isMigrated = await isMigrationDone() - - const timeoutMs = timeoutSeconds * 1000 - if (!isMigrated || secondsWaited <= minTimeSeconds) { - if (loadTime + timeoutMs > Date.now()) { - secondsWaited += 1 - return checkMigrationsFinished() - } + let totalWaitMs = 0 + while (true) { + const waitForMs = 5000 + Math.random() * 5000 + await new Promise(resolve => setTimeout(resolve, waitForMs)) + totalWaitMs += waitForMs - return migrationTimeout() + const isMigrated = await isMigrationDone() + if (isMigrated) { + onMigrationDone() + return } - onMigrationDone() - }, intervalMs) + if (totalWaitMs > timeoutSeconds * 1000) { + timedOut = true + return + } + } } checkMigrationsFinished() - - function migrationTimeout() { - timedOut = true - }
From bb0a0ce109df5b87a36127087b37865356dce82c Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Tue, 25 Jun 2024 11:01:29 +0100 Subject: [PATCH 0003/1152] Fix lint. --- packages/cli/src/hosting/utils.ts | 3 +-- packages/frontend-core/src/components/Updating.svelte | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/hosting/utils.ts b/packages/cli/src/hosting/utils.ts index 05eb18dc1d5..5c3ac33f44e 100644 --- a/packages/cli/src/hosting/utils.ts +++ b/packages/cli/src/hosting/utils.ts @@ -46,8 +46,7 @@ export function setServiceImage(service: string, image: string) { export async function downloadDockerCompose() { const filename = composeFilename() try { - fs.copyFileSync("../../hosting/docker-compose.yaml", `./${filename}`) - //await downloadFile(COMPOSE_URL, `./${filename}`) + await downloadFile(COMPOSE_URL, `./${filename}`) } catch (err) { console.error(error(`Failed to retrieve compose file - ${err}`)) } diff --git a/packages/frontend-core/src/components/Updating.svelte b/packages/frontend-core/src/components/Updating.svelte index 311a6b91c8e..97e83e2322b 100644 --- a/packages/frontend-core/src/components/Updating.svelte +++ b/packages/frontend-core/src/components/Updating.svelte @@ -7,6 +7,7 @@ async function checkMigrationsFinished() { let totalWaitMs = 0 + // eslint-disable-next-line no-constant-condition while (true) { const waitForMs = 5000 + Math.random() * 5000 await new Promise(resolve => setTimeout(resolve, waitForMs)) From b66591f52f6f9666560078a4d1e127072c218c2a Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Tue, 2 Jul 2024 15:40:50 +0100 Subject: [PATCH 0004/1152] Remove superfluous environment variables. --- hosting/docker-compose.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index d9811935e44..d59acb8f9bb 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -27,8 +27,6 @@ services: BB_ADMIN_USER_PASSWORD: ${BB_ADMIN_USER_PASSWORD} PLUGINS_DIR: ${PLUGINS_DIR} OFFLINE_MODE: ${OFFLINE_MODE:-} - SQS_SEARCH_ENABLE: "true" - COUCH_DB_SQL_URL: "http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:4984" depends_on: - worker-service - redis-service @@ -56,8 +54,6 @@ services: REDIS_URL: redis-service:6379 REDIS_PASSWORD: ${REDIS_PASSWORD} OFFLINE_MODE: ${OFFLINE_MODE:-} - SQS_SEARCH_ENABLE: "true" - COUCH_DB_SQL_URL: "http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:4984" depends_on: - redis-service - minio-service @@ -101,7 +97,7 @@ services: couchdb-service: restart: unless-stopped - image: budibase/couchdb:v3.3.3-sqs + image: budibase/couchdb:v3.3.3 environment: - COUCHDB_PASSWORD=${COUCH_DB_PASSWORD} - COUCHDB_USER=${COUCH_DB_USER} From 0c8228edad3b1a288c5232e891ed0745f4774a53 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 5 Aug 2024 15:45:49 +0100 Subject: [PATCH 0005/1152] Initial work - some re-typing and updating the role tests to typescript - using role test API to make this a bit easier to adjust going forward. --- packages/backend-core/src/security/roles.ts | 42 +++- packages/server/src/api/controllers/role.ts | 5 +- .../server/src/api/routes/tests/role.spec.js | 182 ------------------ .../server/src/api/routes/tests/role.spec.ts | 164 ++++++++++++++++ .../src/tests/utilities/TestConfiguration.ts | 1 + .../server/src/tests/utilities/api/role.ts | 11 +- .../server/src/tests/utilities/structures.ts | 4 +- packages/types/src/api/web/role.ts | 4 +- packages/types/src/documents/app/role.ts | 2 +- packages/types/src/sdk/events/role.ts | 6 +- 10 files changed, 218 insertions(+), 203 deletions(-) delete mode 100644 packages/server/src/api/routes/tests/role.spec.js create mode 100644 packages/server/src/api/routes/tests/role.spec.ts diff --git a/packages/backend-core/src/security/roles.ts b/packages/backend-core/src/security/roles.ts index a64be6b3197..61d9786d32c 100644 --- a/packages/backend-core/src/security/roles.ts +++ b/packages/backend-core/src/security/roles.ts @@ -42,7 +42,7 @@ export class Role implements RoleDoc { _rev?: string name: string permissionId: string - inherits?: string + inherits?: string | string[] version?: string permissions = {} @@ -54,8 +54,10 @@ export class Role implements RoleDoc { this.version = RoleIDVersion.NAME } - addInheritance(inherits: string) { - this.inherits = inherits + addInheritance(inherits?: string | string[]) { + if (inherits) { + this.inherits = inherits + } return this } } @@ -113,7 +115,11 @@ export function builtinRoleToNumber(id: string) { if (!role) { break } - role = builtins[role.inherits!] + if (Array.isArray(role.inherits)) { + // TODO: role inheritance + } else { + role = builtins[role.inherits!] + } count++ } while (role !== null) return count @@ -130,7 +136,12 @@ export async function roleToNumber(id: string) { defaultPublic: true, })) as RoleDoc[] for (let role of hierarchy) { - if (role?.inherits && isBuiltin(role.inherits)) { + if (!role.inherits) { + continue + } + if (Array.isArray(role.inherits)) { + // TODO: role inheritance + } else if (isBuiltin(role.inherits)) { return builtinRoleToNumber(role.inherits) + 1 } } @@ -202,16 +213,27 @@ async function getAllUserRoles( let currentRole = await getRole(userRoleId, opts) let roles = currentRole ? [currentRole] : [] let roleIds = [userRoleId] + const rolesFound = (ids: string | string[]) => { + if (Array.isArray(ids)) { + return ids.filter(id => roleIds.includes(id)).length === ids.length + } else { + return roleIds.includes(ids) + } + } // get all the inherited roles while ( currentRole && currentRole.inherits && - roleIds.indexOf(currentRole.inherits) === -1 + !rolesFound(currentRole.inherits) ) { - roleIds.push(currentRole.inherits) - currentRole = await getRole(currentRole.inherits) - if (currentRole) { - roles.push(currentRole) + if (Array.isArray(currentRole.inherits)) { + // TODO: role inheritance + } else { + roleIds.push(currentRole.inherits) + currentRole = await getRole(currentRole.inherits) + if (currentRole) { + roles.push(currentRole) + } } } return roles diff --git a/packages/server/src/api/controllers/role.ts b/packages/server/src/api/controllers/role.ts index 3398c8102ce..3431724d7d5 100644 --- a/packages/server/src/api/controllers/role.ts +++ b/packages/server/src/api/controllers/role.ts @@ -182,7 +182,10 @@ export async function accessible(ctx: UserCtx) { let filteredRoles = [roleHeader] for (let role of orderedRoles) { filteredRoles = [role, ...filteredRoles] - if (role === inherits) { + if ( + (Array.isArray(inherits) && inherits.includes(role)) || + role === inherits + ) { break } } diff --git a/packages/server/src/api/routes/tests/role.spec.js b/packages/server/src/api/routes/tests/role.spec.js deleted file mode 100644 index 4575f9b213c..00000000000 --- a/packages/server/src/api/routes/tests/role.spec.js +++ /dev/null @@ -1,182 +0,0 @@ -const { roles, events, permissions } = require("@budibase/backend-core") -const setup = require("./utilities") -const { PermissionLevel } = require("@budibase/types") -const { basicRole } = setup.structures -const { BUILTIN_ROLE_IDS } = roles -const { BuiltinPermissionID } = permissions - -describe("/roles", () => { - let request = setup.getRequest() - let config = setup.getConfig() - - afterAll(setup.afterAll) - - beforeAll(async () => { - await config.init() - }) - - const createRole = async role => { - if (!role) { - role = basicRole() - } - - return request - .post(`/api/roles`) - .send(role) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - } - - describe("create", () => { - it("returns a success message when role is successfully created", async () => { - const role = basicRole() - const res = await createRole(role) - - expect(res.body._id).toBeDefined() - expect(res.body._rev).toBeDefined() - expect(events.role.updated).not.toBeCalled() - expect(events.role.created).toBeCalledTimes(1) - expect(events.role.created).toBeCalledWith(res.body) - }) - }) - - describe("update", () => { - it("updates a role", async () => { - const role = basicRole() - let res = await createRole(role) - jest.clearAllMocks() - res = await createRole(res.body) - - expect(res.body._id).toBeDefined() - expect(res.body._rev).toBeDefined() - expect(events.role.created).not.toBeCalled() - expect(events.role.updated).toBeCalledTimes(1) - expect(events.role.updated).toBeCalledWith(res.body) - }) - }) - - describe("fetch", () => { - beforeAll(async () => { - // Recreate the app - await config.init() - }) - - it("should list custom roles, plus 2 default roles", async () => { - const customRole = await config.createRole() - - const res = await request - .get(`/api/roles`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - - expect(res.body.length).toBe(5) - - const adminRole = res.body.find(r => r._id === BUILTIN_ROLE_IDS.ADMIN) - expect(adminRole).toBeDefined() - expect(adminRole.inherits).toEqual(BUILTIN_ROLE_IDS.POWER) - expect(adminRole.permissionId).toEqual(BuiltinPermissionID.ADMIN) - - const powerUserRole = res.body.find(r => r._id === BUILTIN_ROLE_IDS.POWER) - expect(powerUserRole).toBeDefined() - expect(powerUserRole.inherits).toEqual(BUILTIN_ROLE_IDS.BASIC) - expect(powerUserRole.permissionId).toEqual(BuiltinPermissionID.POWER) - - const customRoleFetched = res.body.find(r => r._id === customRole.name) - expect(customRoleFetched).toBeDefined() - expect(customRoleFetched.inherits).toEqual(BUILTIN_ROLE_IDS.BASIC) - expect(customRoleFetched.permissionId).toEqual( - BuiltinPermissionID.READ_ONLY - ) - }) - - it("should be able to get the role with a permission added", async () => { - const table = await config.createTable() - await config.api.permission.add({ - roleId: BUILTIN_ROLE_IDS.POWER, - resourceId: table._id, - level: PermissionLevel.READ, - }) - const res = await request - .get(`/api/roles`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - expect(res.body.length).toBeGreaterThan(0) - const power = res.body.find(role => role._id === BUILTIN_ROLE_IDS.POWER) - expect(power.permissions[table._id]).toEqual(["read"]) - }) - }) - - describe("destroy", () => { - it("should delete custom roles", async () => { - const customRole = await config.createRole({ - name: "user", - permissionId: BuiltinPermissionID.READ_ONLY, - inherits: BUILTIN_ROLE_IDS.BASIC, - }) - delete customRole._rev_tree - await request - .delete(`/api/roles/${customRole._id}/${customRole._rev}`) - .set(config.defaultHeaders()) - .expect(200) - await request - .get(`/api/roles/${customRole._id}`) - .set(config.defaultHeaders()) - .expect(404) - expect(events.role.deleted).toBeCalledTimes(1) - expect(events.role.deleted).toBeCalledWith(customRole) - }) - }) - - describe("accessible", () => { - it("should be able to fetch accessible roles (with builder)", async () => { - const res = await request - .get("/api/roles/accessible") - .set(config.defaultHeaders()) - .expect(200) - expect(res.body.length).toBe(5) - expect(typeof res.body[0]).toBe("string") - }) - - it("should be able to fetch accessible roles (basic user)", async () => { - const res = await request - .get("/api/roles/accessible") - .set(await config.basicRoleHeaders()) - .expect(200) - expect(res.body.length).toBe(2) - expect(res.body[0]).toBe("BASIC") - expect(res.body[1]).toBe("PUBLIC") - }) - - it("should be able to fetch accessible roles (no user)", async () => { - const res = await request - .get("/api/roles/accessible") - .set(config.publicHeaders()) - .expect(200) - expect(res.body.length).toBe(1) - expect(res.body[0]).toBe("PUBLIC") - }) - - it("should not fetch higher level accessible roles when a custom role header is provided", async () => { - await createRole({ - name: `CUSTOM_ROLE`, - inherits: roles.BUILTIN_ROLE_IDS.BASIC, - permissionId: permissions.BuiltinPermissionID.READ_ONLY, - version: "name", - }) - const res = await request - .get("/api/roles/accessible") - .set({ - ...config.defaultHeaders(), - "x-budibase-role": "CUSTOM_ROLE", - }) - .expect(200) - expect(res.body.length).toBe(3) - expect(res.body[0]).toBe("CUSTOM_ROLE") - expect(res.body[1]).toBe("BASIC") - expect(res.body[2]).toBe("PUBLIC") - }) - }) -}) diff --git a/packages/server/src/api/routes/tests/role.spec.ts b/packages/server/src/api/routes/tests/role.spec.ts new file mode 100644 index 00000000000..127be789b90 --- /dev/null +++ b/packages/server/src/api/routes/tests/role.spec.ts @@ -0,0 +1,164 @@ +import { roles, events, permissions } from "@budibase/backend-core" +import * as setup from "./utilities" +import { PermissionLevel } from "@budibase/types" + +const { basicRole } = setup.structures +const { BUILTIN_ROLE_IDS } = roles +const { BuiltinPermissionID } = permissions + +describe("/roles", () => { + let config = setup.getConfig() + + afterAll(setup.afterAll) + + beforeAll(async () => { + await config.init() + }) + + describe("create", () => { + it("returns a success message when role is successfully created", async () => { + const role = basicRole() + const res = await config.api.roles.save(role, { + status: 200, + }) + + expect(res._id).toBeDefined() + expect(res._rev).toBeDefined() + expect(events.role.updated).not.toHaveBeenCalled() + expect(events.role.created).toHaveBeenCalledTimes(1) + expect(events.role.created).toHaveBeenCalledWith(res) + }) + }) + + describe("update", () => { + it("updates a role", async () => { + const role = basicRole() + let res = await config.api.roles.save(role, { + status: 200, + }) + jest.clearAllMocks() + res = await config.api.roles.save(res, { + status: 200, + }) + + expect(res._id).toBeDefined() + expect(res._rev).toBeDefined() + expect(events.role.created).not.toHaveBeenCalled() + expect(events.role.updated).toHaveBeenCalledTimes(1) + expect(events.role.updated).toHaveBeenCalledWith(res) + }) + }) + + describe("fetch", () => { + beforeAll(async () => { + // Recreate the app + await config.init() + }) + + it("should list custom roles, plus 2 default roles", async () => { + const customRole = await config.createRole() + + const res = await config.api.roles.fetch({ + status: 200, + }) + + expect(res.length).toBe(5) + + const adminRole = res.find(r => r._id === BUILTIN_ROLE_IDS.ADMIN) + expect(adminRole).toBeDefined() + expect(adminRole!.inherits).toEqual(BUILTIN_ROLE_IDS.POWER) + expect(adminRole!.permissionId).toEqual(BuiltinPermissionID.ADMIN) + + const powerUserRole = res.find(r => r._id === BUILTIN_ROLE_IDS.POWER) + expect(powerUserRole).toBeDefined() + expect(powerUserRole!.inherits).toEqual(BUILTIN_ROLE_IDS.BASIC) + expect(powerUserRole!.permissionId).toEqual(BuiltinPermissionID.POWER) + + const customRoleFetched = res.find(r => r._id === customRole.name) + expect(customRoleFetched).toBeDefined() + expect(customRoleFetched!.inherits).toEqual(BUILTIN_ROLE_IDS.BASIC) + expect(customRoleFetched!.permissionId).toEqual( + BuiltinPermissionID.READ_ONLY + ) + }) + + it("should be able to get the role with a permission added", async () => { + const table = await config.createTable() + await config.api.permission.add({ + roleId: BUILTIN_ROLE_IDS.POWER, + resourceId: table._id!, + level: PermissionLevel.READ, + }) + const res = await config.api.roles.fetch() + expect(res.length).toBeGreaterThan(0) + const power = res.find(role => role._id === BUILTIN_ROLE_IDS.POWER) + expect(power?.permissions[table._id!]).toEqual(["read"]) + }) + }) + + describe("destroy", () => { + it("should delete custom roles", async () => { + const customRole = await config.createRole({ + name: "user", + permissionId: BuiltinPermissionID.READ_ONLY, + inherits: BUILTIN_ROLE_IDS.BASIC, + }) + await config.api.roles.destroy(customRole, { + status: 200, + }) + await config.api.roles.find(customRole._id!, { + status: 404, + }) + expect(events.role.deleted).toHaveBeenCalledTimes(1) + expect(events.role.deleted).toHaveBeenCalledWith(customRole) + }) + }) + + describe("accessible", () => { + it("should be able to fetch accessible roles (with builder)", async () => { + const res = await config.api.roles.accessible(config.defaultHeaders(), { + status: 200, + }) + expect(res.length).toBe(5) + expect(typeof res[0]).toBe("string") + }) + + it("should be able to fetch accessible roles (basic user)", async () => { + const headers = await config.basicRoleHeaders() + const res = await config.api.roles.accessible(headers, { + status: 200, + }) + expect(res.length).toBe(2) + expect(res[0]).toBe("BASIC") + expect(res[1]).toBe("PUBLIC") + }) + + it("should be able to fetch accessible roles (no user)", async () => { + const res = await config.api.roles.accessible(config.publicHeaders(), { + status: 200, + }) + expect(res.length).toBe(1) + expect(res[0]).toBe("PUBLIC") + }) + + it("should not fetch higher level accessible roles when a custom role header is provided", async () => { + const customRoleName = "CUSTOM_ROLE" + await config.api.roles.save({ + name: customRoleName, + inherits: roles.BUILTIN_ROLE_IDS.BASIC, + permissionId: permissions.BuiltinPermissionID.READ_ONLY, + version: "name", + }) + const res = await config.api.roles.accessible( + { "x-budibase-role": customRoleName }, + { + status: 200, + } + ) + expect(res.length).toBe(3) + expect(res[0]).toBe(customRoleName) + expect(res[1]).toBe("BASIC") + expect(res[2]).toBe("PUBLIC") + }) + }) +}) diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index 3d53149385d..0255268097e 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -517,6 +517,7 @@ export default class TestConfiguration { const headers: any = { Accept: "application/json", + Cookie: "", } if (appId) { headers[constants.Header.APP_ID] = appId diff --git a/packages/server/src/tests/utilities/api/role.ts b/packages/server/src/tests/utilities/api/role.ts index 4defbc12201..31bffc6f85d 100644 --- a/packages/server/src/tests/utilities/api/role.ts +++ b/packages/server/src/tests/utilities/api/role.ts @@ -4,6 +4,7 @@ import { FindRoleResponse, SaveRoleRequest, SaveRoleResponse, + Role, } from "@budibase/types" import { Expectations, TestAPI } from "./base" @@ -27,14 +28,18 @@ export class RoleAPI extends TestAPI { }) } - destroy = async (roleId: string, expectations?: Expectations) => { - return await this._delete(`/api/roles/${roleId}`, { + destroy = async (role: Role, expectations?: Expectations) => { + return await this._delete(`/api/roles/${role._id}/${role._rev}`, { expectations, }) } - accesssible = async (expectations?: Expectations) => { + accessible = async ( + headers: Record, + expectations?: Expectations + ) => { return await this._get(`/api/roles/accessible`, { + headers, expectations, }) } diff --git a/packages/server/src/tests/utilities/structures.ts b/packages/server/src/tests/utilities/structures.ts index 698f6d8236f..e572447ab4e 100644 --- a/packages/server/src/tests/utilities/structures.ts +++ b/packages/server/src/tests/utilities/structures.ts @@ -30,6 +30,7 @@ import { BBReferenceFieldSubType, JsonFieldSubType, AutoFieldSubType, + Role, } from "@budibase/types" import { LoopInput } from "../../definitions/automations" import { merge } from "lodash" @@ -492,11 +493,12 @@ export function basicLinkedRow( } } -export function basicRole() { +export function basicRole(): Role { return { name: `NewRole_${utils.newid()}`, inherits: roles.BUILTIN_ROLE_IDS.BASIC, permissionId: permissions.BuiltinPermissionID.READ_ONLY, + permissions: {}, version: "name", } } diff --git a/packages/types/src/api/web/role.ts b/packages/types/src/api/web/role.ts index c37dee60e0f..642f815cc44 100644 --- a/packages/types/src/api/web/role.ts +++ b/packages/types/src/api/web/role.ts @@ -4,9 +4,9 @@ export interface SaveRoleRequest { _id?: string _rev?: string name: string - inherits: string + inherits?: string | string[] permissionId: string - version: string + version?: string } export interface SaveRoleResponse extends Role {} diff --git a/packages/types/src/documents/app/role.ts b/packages/types/src/documents/app/role.ts index f32ba810b00..669e8f523c9 100644 --- a/packages/types/src/documents/app/role.ts +++ b/packages/types/src/documents/app/role.ts @@ -2,7 +2,7 @@ import { Document } from "../document" export interface Role extends Document { permissionId: string - inherits?: string + inherits?: string | string[] permissions: { [key: string]: string[] } version?: string name: string diff --git a/packages/types/src/sdk/events/role.ts b/packages/types/src/sdk/events/role.ts index b04b9b8ee56..ce17b34dc4a 100644 --- a/packages/types/src/sdk/events/role.ts +++ b/packages/types/src/sdk/events/role.ts @@ -3,19 +3,19 @@ import { BaseEvent } from "./event" export interface RoleCreatedEvent extends BaseEvent { roleId: string permissionId: string - inherits?: string + inherits?: string | string[] } export interface RoleUpdatedEvent extends BaseEvent { roleId: string permissionId: string - inherits?: string + inherits?: string | string[] } export interface RoleDeletedEvent extends BaseEvent { roleId: string permissionId: string - inherits?: string + inherits?: string | string[] } export interface RoleAssignedEvent extends BaseEvent { From fa80d9913969a7da6869608ad07841eb401d54dc Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 15 Aug 2024 16:14:00 +0100 Subject: [PATCH 0006/1152] Add initial new UI for views --- .../backend/DataTable/TableDataTable.svelte | 110 +++++----- .../backend/DataTable/ViewV2DataTable.svelte | 49 ++--- .../DeleteConfirmationModal.svelte | 2 +- .../TableNavItem/TableNavItem.svelte | 16 +- .../TableNavigator/TableNavigator.svelte | 15 -- .../ViewNavItem/ViewNavItem.svelte | 71 ------- .../table/[tableId]/[viewId]/_layout.svelte | 24 +++ .../table/[tableId]/[viewId]/index.svelte | 5 + .../_components/DeleteViewModal.svelte} | 0 .../_components}/EditViewModal.svelte | 0 .../[tableId]/_components/ViewNavBar.svelte | 200 ++++++++++++++++++ .../data/table/[tableId]/_layout.svelte | 16 +- .../data/table/[tableId]/view/index.svelte | 19 ++ .../view/v1/[viewName]/_layout.svelte | 24 +++ .../[tableId]/view/v1/[viewName]/index.svelte | 18 ++ .../data/table/[tableId]/view/v1/index.svelte | 5 + 16 files changed, 386 insertions(+), 188 deletions(-) delete mode 100644 packages/builder/src/components/backend/TableNavigator/ViewNavItem/ViewNavItem.svelte create mode 100644 packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/[viewId]/_layout.svelte create mode 100644 packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/[viewId]/index.svelte rename packages/builder/src/{components/backend/TableNavigator/ViewNavItem/DeleteConfirmationModal.svelte => pages/builder/app/[application]/data/table/[tableId]/_components/DeleteViewModal.svelte} (100%) rename packages/builder/src/{components/backend/TableNavigator/ViewNavItem => pages/builder/app/[application]/data/table/[tableId]/_components}/EditViewModal.svelte (100%) create mode 100644 packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/ViewNavBar.svelte create mode 100644 packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/view/index.svelte create mode 100644 packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/view/v1/[viewName]/_layout.svelte create mode 100644 packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/view/v1/[viewName]/index.svelte create mode 100644 packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/view/v1/index.svelte diff --git a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte index 525421f996e..22a7fabf244 100644 --- a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte @@ -57,64 +57,52 @@ } -
- - - {#if isUsersTable && $appStore.features.disableUserMetadata} - - {/if} - - - - {#if !isUsersTable} - - {/if} - - {#if !isUsersTable} - - {/if} - {#if relationshipsEnabled} - - {/if} - {#if isUsersTable} - - {:else} - - {/if} - - {#if isUsersTable} - - {:else} - - {/if} - - - - - - - - -
- - + + + {#if isUsersTable && $appStore.features.disableUserMetadata} + + {/if} + + + + {#if !isUsersTable} + + {/if} + + {#if !isUsersTable} + + {/if} + {#if relationshipsEnabled} + + {/if} + {#if isUsersTable} + + {:else} + + {/if} + + {#if isUsersTable} + + {:else} + + {/if} + + + + + + + + diff --git a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte index 646b764a2cd..5f7a0feb674 100644 --- a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte @@ -19,34 +19,21 @@ } -
- - - - - - - - - -
- - + + + + + + + + + diff --git a/packages/builder/src/components/backend/TableNavigator/TableNavItem/DeleteConfirmationModal.svelte b/packages/builder/src/components/backend/TableNavigator/TableNavItem/DeleteConfirmationModal.svelte index 03da9f3fd3e..c0e030845f6 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableNavItem/DeleteConfirmationModal.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableNavItem/DeleteConfirmationModal.svelte @@ -104,7 +104,7 @@
{/if} -

Please enter the app name below to confirm.

+

Please enter the table name below to confirm.

diff --git a/packages/builder/src/components/backend/TableNavigator/TableNavItem/TableNavItem.svelte b/packages/builder/src/components/backend/TableNavigator/TableNavItem/TableNavItem.svelte index ab79a8fff09..6b64096e2e7 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableNavItem/TableNavItem.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableNavItem/TableNavItem.svelte @@ -20,14 +20,6 @@ const getContextMenuItems = () => { return [ - { - icon: "Delete", - name: "Delete", - keyBind: null, - visible: true, - disabled: false, - callback: deleteConfirmationModal.show, - }, { icon: "Edit", name: "Edit", @@ -36,6 +28,14 @@ disabled: false, callback: editModal.show, }, + { + icon: "Delete", + name: "Delete", + keyBind: null, + visible: true, + disabled: false, + callback: deleteConfirmationModal.show, + }, ] } diff --git a/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte b/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte index f21230d7a6c..6ab500b65d4 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte @@ -1,7 +1,5 @@ - - - - - - diff --git a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/[viewId]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/[viewId]/_layout.svelte new file mode 100644 index 00000000000..9c1baa723e2 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/[viewId]/_layout.svelte @@ -0,0 +1,24 @@ + + + diff --git a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/[viewId]/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/[viewId]/index.svelte new file mode 100644 index 00000000000..c2281710bad --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/[viewId]/index.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/builder/src/components/backend/TableNavigator/ViewNavItem/DeleteConfirmationModal.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/DeleteViewModal.svelte similarity index 100% rename from packages/builder/src/components/backend/TableNavigator/ViewNavItem/DeleteConfirmationModal.svelte rename to packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/DeleteViewModal.svelte diff --git a/packages/builder/src/components/backend/TableNavigator/ViewNavItem/EditViewModal.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/EditViewModal.svelte similarity index 100% rename from packages/builder/src/components/backend/TableNavigator/ViewNavItem/EditViewModal.svelte rename to packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/EditViewModal.svelte diff --git a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/ViewNavBar.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/ViewNavBar.svelte new file mode 100644 index 00000000000..20ecdc3f830 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/ViewNavBar.svelte @@ -0,0 +1,200 @@ + + + + +{#if table} + + +{/if} + +{#if editableView} + + +{/if} + + diff --git a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_layout.svelte index 8c60dbdd69a..4a33f8a9673 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_layout.svelte @@ -3,6 +3,7 @@ import { tables, builderStore } from "stores/builder" import * as routify from "@roxi/routify" import { onDestroy } from "svelte" + import ViewNavBar from "./_components/ViewNavBar.svelte" $: tableId = $tables.selectedTableId $: builderStore.selectResource(tableId) @@ -20,4 +21,17 @@ onDestroy(stopSyncing) - +
+ + +
+ + diff --git a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/view/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/view/index.svelte new file mode 100644 index 00000000000..623cd224dbc --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/view/index.svelte @@ -0,0 +1,19 @@ + diff --git a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/view/v1/[viewName]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/view/v1/[viewName]/_layout.svelte new file mode 100644 index 00000000000..df3a18c2bb8 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/view/v1/[viewName]/_layout.svelte @@ -0,0 +1,24 @@ + + + diff --git a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/view/v1/[viewName]/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/view/v1/[viewName]/index.svelte new file mode 100644 index 00000000000..51149b602d2 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/view/v1/[viewName]/index.svelte @@ -0,0 +1,18 @@ + + +{#if selectedView} + +{:else}Create your first table to start building{/if} + + diff --git a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/view/v1/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/view/v1/index.svelte new file mode 100644 index 00000000000..c11ca870230 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/view/v1/index.svelte @@ -0,0 +1,5 @@ + From d313968eaa69f07306db5bf207135d96a8c1c380 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 15 Aug 2024 16:33:39 +0100 Subject: [PATCH 0007/1152] Add view CRUD and table editing via new UI --- .../backend/DataTable/TableDataTable.svelte | 4 -- .../DatasourceNavigator.svelte | 4 +- .../TableNavigator/TableNavigator.svelte | 5 +- .../backend/TableNavigator/utils.js | 4 ++ .../_components/CreateViewModal.svelte} | 54 ++++++++++--------- .../[tableId]/_components/ViewNavBar.svelte | 41 ++++++++++---- 6 files changed, 66 insertions(+), 46 deletions(-) rename packages/builder/src/{components/backend/DataTable/modals/grid/GridCreateViewModal.svelte => pages/builder/app/[application]/data/table/[tableId]/_components/CreateViewModal.svelte} (51%) diff --git a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte index 22a7fabf244..2dd1eec0a8e 100644 --- a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte @@ -9,7 +9,6 @@ import GridAddColumnModal from "components/backend/DataTable/modals/grid/GridCreateColumnModal.svelte" import GridCreateEditRowModal from "components/backend/DataTable/modals/grid/GridCreateEditRowModal.svelte" import GridEditUserModal from "components/backend/DataTable/modals/grid/GridEditUserModal.svelte" - import GridCreateViewButton from "components/backend/DataTable/buttons/grid/GridCreateViewButton.svelte" import GridImportButton from "components/backend/DataTable/buttons/grid/GridImportButton.svelte" import GridExportButton from "components/backend/DataTable/buttons/grid/GridExportButton.svelte" import GridFilterButton from "components/backend/DataTable/buttons/grid/GridFilterButton.svelte" @@ -77,9 +76,6 @@ - {#if !isUsersTable} - - {/if} {#if !isUsersTable} diff --git a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte index e0745c15a15..a6078e38fbd 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte @@ -39,9 +39,7 @@ const selectTable = tableId => { tables.select(tableId) - if (!$isActive("./table/:tableId")) { - $goto(`./table/${tableId}`) - } + $goto(`./table/${tableId}`) } function openNode(datasource) { diff --git a/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte b/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte index 6ab500b65d4..f97bd2487b9 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte @@ -1,14 +1,11 @@
diff --git a/packages/builder/src/components/backend/TableNavigator/utils.js b/packages/builder/src/components/backend/TableNavigator/utils.js index ae7aaa0f0a5..a76a14c1a4f 100644 --- a/packages/builder/src/components/backend/TableNavigator/utils.js +++ b/packages/builder/src/components/backend/TableNavigator/utils.js @@ -66,3 +66,7 @@ export const parseFile = e => { reader.readAsText(file) }) } + +export const alphabetical = (a, b) => { + return a.name?.toLowerCase() > b.name?.toLowerCase() ? 1 : -1 +} diff --git a/packages/builder/src/components/backend/DataTable/modals/grid/GridCreateViewModal.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/CreateViewModal.svelte similarity index 51% rename from packages/builder/src/components/backend/DataTable/modals/grid/GridCreateViewModal.svelte rename to packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/CreateViewModal.svelte index eea64a4d9e7..77477054b8d 100644 --- a/packages/builder/src/components/backend/DataTable/modals/grid/GridCreateViewModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/CreateViewModal.svelte @@ -1,16 +1,21 @@ - - - + + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/ViewNavBar.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/ViewNavBar.svelte index 20ecdc3f830..fb7d46f2595 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/ViewNavBar.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/ViewNavBar.svelte @@ -6,7 +6,7 @@ contextMenuStore, } from "stores/builder" import IntegrationIcon from "components/backend/DatasourceNavigator/IntegrationIcon.svelte" - import { Icon } from "@budibase/bbui" + import { Icon, Button } from "@budibase/bbui" import { params, url } from "@roxi/routify" import EditViewModal from "./EditViewModal.svelte" import DeleteViewModal from "./DeleteViewModal.svelte" @@ -16,8 +16,11 @@ import { tick } from "svelte" import { DB_TYPE_EXTERNAL } from "constants/backend" import { TableNames } from "constants" + import { alphabetical } from "components/backend/TableNavigator/utils" + import CreateViewModal from "./CreateViewModal.svelte" // Editing table + let createViewModal let editTableModal let deleteTableModal @@ -101,10 +104,6 @@ } ) } - - const alphabetical = (a, b) => { - return a.name < b.name ? -1 : 1 - }
@@ -126,7 +125,13 @@ {/if} {#if tableEditable} - + {/if} {#each views as view (view.id)} @@ -145,15 +150,33 @@ {/if} openViewContextMenu(e, view)} - s hoverable name="MoreSmallList" + color="var(--spectrum-global-color-gray-600)" + hoverColor="var(--spectrum-global-color-gray-900)" /> {/each} + {#if !views.length && tableEditable} + + + To create subsets of data, control access and more, create a view. + + {/if} + {#if views.length} + + {/if}
-{#if table} +{#if table && tableEditable} + {/if} @@ -183,7 +206,7 @@ align-items: center; gap: var(--spacing-m); transition: background 130ms ease-out, color 130ms ease-out; - color: var(--spectrum-global-color-gray-700); + color: var(--spectrum-global-color-gray-600); } .title { font-size: 16px; From 40e7f58131b7af6795201e5a887c2a2a9bb809bd Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 15 Aug 2024 19:49:30 +0100 Subject: [PATCH 0008/1152] Add automatic overflow menu popover for views that don't fit --- packages/bbui/src/Menu/Item.svelte | 2 +- .../_components/CreateViewModal.svelte | 10 +- .../[tableId]/_components/ViewNavBar.svelte | 165 ++++++++++++++---- 3 files changed, 134 insertions(+), 43 deletions(-) diff --git a/packages/bbui/src/Menu/Item.svelte b/packages/bbui/src/Menu/Item.svelte index 05a33adda97..758b819a62c 100644 --- a/packages/bbui/src/Menu/Item.svelte +++ b/packages/bbui/src/Menu/Item.svelte @@ -35,7 +35,7 @@