diff --git a/hosting/single/runner.sh b/hosting/single/runner.sh index 770b23eec1..9dc7aa25d8 100644 --- a/hosting/single/runner.sh +++ b/hosting/single/runner.sh @@ -77,7 +77,7 @@ mkdir -p ${DATA_DIR}/minio chown -R couchdb:couchdb ${DATA_DIR}/couch redis-server --requirepass $REDIS_PASSWORD > /dev/stdout 2>&1 & /bbcouch-runner.sh & -minio server --console-address ":9001" ${DATA_DIR}/minio > /dev/stdout 2>&1 & +/minio/minio server --console-address ":9001" ${DATA_DIR}/minio > /dev/stdout 2>&1 & /etc/init.d/nginx restart if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then # Add monthly cron job to renew certbot certificate diff --git a/lerna.json b/lerna.json index c87304af4e..d102c3f41f 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.12.8", + "version": "2.12.11", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/src/index.ts b/packages/backend-core/src/index.ts index c7cf9f56cc..ffffd8240a 100644 --- a/packages/backend-core/src/index.ts +++ b/packages/backend-core/src/index.ts @@ -30,7 +30,6 @@ export * as timers from "./timers" export { default as env } from "./environment" export * as blacklist from "./blacklist" export * as docUpdates from "./docUpdates" -export * from "./utils/Duration" export { SearchParams } from "./db" // Add context to tenancy for backwards compatibility // only do this for external usages to prevent internal diff --git a/packages/backend-core/src/objectStore/utils.ts b/packages/backend-core/src/objectStore/utils.ts index dba5f3d1c2..4c3a84ba91 100644 --- a/packages/backend-core/src/objectStore/utils.ts +++ b/packages/backend-core/src/objectStore/utils.ts @@ -18,8 +18,12 @@ export const ObjectStoreBuckets = { } const bbTmp = join(tmpdir(), ".budibase") -if (!fs.existsSync(bbTmp)) { +try { fs.mkdirSync(bbTmp) +} catch (e: any) { + if (e.code !== "EEXIST") { + throw e + } } export function budibaseTempDir() { diff --git a/packages/backend-core/src/queue/inMemoryQueue.ts b/packages/backend-core/src/queue/inMemoryQueue.ts index a8add7ecb6..af2ec6dbaa 100644 --- a/packages/backend-core/src/queue/inMemoryQueue.ts +++ b/packages/backend-core/src/queue/inMemoryQueue.ts @@ -36,7 +36,7 @@ class InMemoryQueue { * @param opts This is not used by the in memory queue as there is no real use * case when in memory, but is the same API as Bull */ - constructor(name: string, opts?: any) { + constructor(name: string, opts = null) { this._name = name this._opts = opts this._messages = [] diff --git a/packages/backend-core/src/queue/queue.ts b/packages/backend-core/src/queue/queue.ts index c0d1861de3..0658147709 100644 --- a/packages/backend-core/src/queue/queue.ts +++ b/packages/backend-core/src/queue/queue.ts @@ -2,18 +2,11 @@ import env from "../environment" import { getRedisOptions } from "../redis/utils" import { JobQueue } from "./constants" import InMemoryQueue from "./inMemoryQueue" -import BullQueue, { QueueOptions } from "bull" +import BullQueue from "bull" import { addListeners, StalledFn } from "./listeners" -import { Duration } from "../utils" import * as timers from "../timers" -import * as Redis from "ioredis" -// the queue lock is held for 5 minutes -const QUEUE_LOCK_MS = Duration.fromMinutes(5).toMs() -// queue lock is refreshed every 30 seconds -const QUEUE_LOCK_RENEW_INTERNAL_MS = Duration.fromSeconds(30).toMs() -// cleanup the queue every 60 seconds -const CLEANUP_PERIOD_MS = Duration.fromSeconds(60).toMs() +const CLEANUP_PERIOD_MS = 60 * 1000 let QUEUES: BullQueue.Queue[] | InMemoryQueue[] = [] let cleanupInterval: NodeJS.Timeout @@ -28,14 +21,7 @@ export function createQueue( opts: { removeStalledCb?: StalledFn } = {} ): BullQueue.Queue { const { opts: redisOpts, redisProtocolUrl } = getRedisOptions() - const queueConfig: QueueOptions = { - redis: redisProtocolUrl! || (redisOpts as Redis.RedisOptions), - settings: { - maxStalledCount: 0, - lockDuration: QUEUE_LOCK_MS, - lockRenewTime: QUEUE_LOCK_RENEW_INTERNAL_MS, - }, - } + const queueConfig: any = redisProtocolUrl || { redis: redisOpts } let queue: any if (!env.isTest()) { queue = new BullQueue(jobQueue, queueConfig) diff --git a/packages/backend-core/src/utils/Duration.ts b/packages/backend-core/src/utils/Duration.ts deleted file mode 100644 index f376c2f7c7..0000000000 --- a/packages/backend-core/src/utils/Duration.ts +++ /dev/null @@ -1,49 +0,0 @@ -export enum DurationType { - MILLISECONDS = "milliseconds", - SECONDS = "seconds", - MINUTES = "minutes", - HOURS = "hours", - DAYS = "days", -} - -const conversion: Record = { - milliseconds: 1, - seconds: 1000, - minutes: 60 * 1000, - hours: 60 * 60 * 1000, - days: 24 * 60 * 60 * 1000, -} - -export class Duration { - static convert(from: DurationType, to: DurationType, duration: number) { - const milliseconds = duration * conversion[from] - return milliseconds / conversion[to] - } - - static from(from: DurationType, duration: number) { - return { - to: (to: DurationType) => { - return Duration.convert(from, to, duration) - }, - toMs: () => { - return Duration.convert(from, DurationType.MILLISECONDS, duration) - }, - } - } - - static fromSeconds(duration: number) { - return Duration.from(DurationType.SECONDS, duration) - } - - static fromMinutes(duration: number) { - return Duration.from(DurationType.MINUTES, duration) - } - - static fromHours(duration: number) { - return Duration.from(DurationType.HOURS, duration) - } - - static fromDays(duration: number) { - return Duration.from(DurationType.DAYS, duration) - } -} diff --git a/packages/backend-core/src/utils/index.ts b/packages/backend-core/src/utils/index.ts index ac17227459..318a7f13ba 100644 --- a/packages/backend-core/src/utils/index.ts +++ b/packages/backend-core/src/utils/index.ts @@ -1,4 +1,3 @@ export * from "./hashing" export * from "./utils" export * from "./stringUtils" -export * from "./Duration" diff --git a/packages/backend-core/src/utils/tests/Duration.spec.ts b/packages/backend-core/src/utils/tests/Duration.spec.ts deleted file mode 100644 index 46b996f788..0000000000 --- a/packages/backend-core/src/utils/tests/Duration.spec.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Duration, DurationType } from "../Duration" - -describe("duration", () => { - it("should convert minutes to milliseconds", () => { - expect(Duration.fromMinutes(5).toMs()).toBe(300000) - }) - - it("should convert seconds to milliseconds", () => { - expect(Duration.fromSeconds(30).toMs()).toBe(30000) - }) - - it("should convert days to milliseconds", () => { - expect(Duration.fromDays(1).toMs()).toBe(86400000) - }) - - it("should convert minutes to days", () => { - expect(Duration.fromMinutes(1440).to(DurationType.DAYS)).toBe(1) - }) -}) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 3ae4a6c1e2..92d581d930 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -49,7 +49,12 @@ describe.each([ let table: Table let tableId: string - afterAll(setup.afterAll) + afterAll(async () => { + if (dsProvider) { + await dsProvider.stopContainer() + } + setup.afterAll() + }) beforeAll(async () => { await config.init() @@ -521,20 +526,17 @@ describe.each([ const rowUsage = await getRowUsage() const queryUsage = await getQueryUsage() - const res = await config.api.row.patch(table._id!, { + const row = await config.api.row.patch(table._id!, { _id: existing._id!, _rev: existing._rev!, tableId: table._id!, name: "Updated Name", }) - expect((res as any).res.statusMessage).toEqual( - `${table.name} updated successfully.` - ) - expect(res.body.name).toEqual("Updated Name") - expect(res.body.description).toEqual(existing.description) + expect(row.name).toEqual("Updated Name") + expect(row.description).toEqual(existing.description) - const savedRow = await loadRow(res.body._id, table._id!) + const savedRow = await loadRow(row._id!, table._id!) expect(savedRow.body.description).toEqual(existing.description) expect(savedRow.body.name).toEqual("Updated Name") diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index c239c596fe..4743bca814 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -492,6 +492,67 @@ describe("/tables", () => { } }) + it("should succeed when the row is created from the other side of the relationship", async () => { + // We found a bug just after releasing this feature where if the row was created from the + // users table, not the table linking to it, the migration would succeed but lose the data. + // This happened because the order of the documents in the link was reversed. + const table = await config.api.table.create({ + name: "table", + type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, + sourceType: TableSourceType.INTERNAL, + schema: { + "user relationship": { + type: FieldType.LINK, + fieldName: "test", + name: "user relationship", + constraints: { + type: "array", + presence: false, + }, + relationshipType: RelationshipType.MANY_TO_ONE, + tableId: InternalTable.USER_METADATA, + }, + }, + }) + + let testRow = await config.api.row.save(table._id!, {}) + + await Promise.all( + users.map(u => + config.api.row.patch(InternalTable.USER_METADATA, { + tableId: InternalTable.USER_METADATA, + _rev: u._rev!, + _id: u._id!, + test: [testRow], + }) + ) + ) + + await config.api.table.migrate(table._id!, { + oldColumn: table.schema["user relationship"], + newColumn: { + name: "user column", + type: FieldType.BB_REFERENCE, + subtype: FieldSubtype.USERS, + }, + }) + + const migratedTable = await config.api.table.get(table._id!) + expect(migratedTable.schema["user column"]).toBeDefined() + expect(migratedTable.schema["user relationship"]).not.toBeDefined() + + const resp = await config.api.row.get(table._id!, testRow._id!) + const migratedRow = resp.body as Row + + expect(migratedRow["user column"]).toBeDefined() + expect(migratedRow["user relationship"]).not.toBeDefined() + expect(migratedRow["user column"]).toHaveLength(3) + expect(migratedRow["user column"].map((u: Row) => u._id)).toEqual( + expect.arrayContaining(users.map(u => u._id)) + ) + }) + it("should successfully migrate a many-to-many user relationship to a users column", async () => { const table = await config.api.table.create({ name: "table", diff --git a/packages/server/src/tests/utilities/api/row.ts b/packages/server/src/tests/utilities/api/row.ts index bb880bb7da..20b1d6f9ee 100644 --- a/packages/server/src/tests/utilities/api/row.ts +++ b/packages/server/src/tests/utilities/api/row.ts @@ -55,7 +55,13 @@ export class RowAPI extends TestAPI { .send(row) .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(expectStatus) + if (resp.status !== expectStatus) { + throw new Error( + `Expected status ${expectStatus} but got ${ + resp.status + }, body: ${JSON.stringify(resp.body)}` + ) + } return resp.body as Row } @@ -77,13 +83,20 @@ export class RowAPI extends TestAPI { sourceId: string, row: PatchRowRequest, { expectStatus } = { expectStatus: 200 } - ) => { - return this.request + ): Promise => { + let resp = await this.request .patch(`/api/${sourceId}/rows`) .send(row) .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(expectStatus) + if (resp.status !== expectStatus) { + throw new Error( + `Expected status ${expectStatus} but got ${ + resp.status + }, body: ${JSON.stringify(resp.body)}` + ) + } + return resp.body as Row } delete = async ( diff --git a/packages/types/src/sdk/featureFlag.ts b/packages/types/src/sdk/featureFlag.ts index e3935bc7ee..ca0046696a 100644 --- a/packages/types/src/sdk/featureFlag.ts +++ b/packages/types/src/sdk/featureFlag.ts @@ -1,8 +1,7 @@ export enum FeatureFlag { LICENSING = "LICENSING", - // Feature IDs in Posthog - PER_CREATOR_PER_USER_PRICE = "18873", - PER_CREATOR_PER_USER_PRICE_ALERT = "18530", + PER_CREATOR_PER_USER_PRICE = "PER_CREATOR_PER_USER_PRICE", + PER_CREATOR_PER_USER_PRICE_ALERT = "PER_CREATOR_PER_USER_PRICE_ALERT", } export interface TenantFeatureFlags { diff --git a/pull_request_template.md b/pull_request_template.md index fa0f7f2143..1dd1a1d45d 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,16 +1,12 @@ ## Description - _Describe the problem or feature in addition to a link to the relevant github issues._ -Addresses: - +### Addresses: - `` - ...more if required ## App Export - - If possible, attach an app export file along with your request template to make QA testing easier, with minimal setup. ## Screenshots - _If a UI facing feature, a short video of the happy path, and some screenshots of the new functionality._ diff --git a/scripts/install-minio.sh b/scripts/install-minio.sh index b1e0d9ee80..fede984377 100755 --- a/scripts/install-minio.sh +++ b/scripts/install-minio.sh @@ -2,9 +2,9 @@ if [[ $TARGETARCH == arm* ]] ; then echo "INSTALLING ARM64 MINIO" - wget wget https://dl.min.io/server/minio/release/linux-arm64/archive/minio.deb -O minio.deb + wget https://dl.min.io/server/minio/release/linux-arm64/minio else echo "INSTALLING AMD64 MINIO" - wget wget https://dl.min.io/server/minio/release/linux-amd64/archive/minio.deb -O minio.deb + wget https://dl.min.io/server/minio/release/linux-amd64/minio fi -dpkg -i minio.deb +chmod +x minio \ No newline at end of file