diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 0e19973322f..c8382936cbf 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.46.0" + ".": "7.46.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fe32e47683..9ba2ccc73c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.46.1](https://github.com/ParabolInc/parabol/compare/v7.46.0...v7.46.1) (2024-09-06) + + +### Fixed + +* multiple slack notifications ([#10190](https://github.com/ParabolInc/parabol/issues/10190)) ([c4444ef](https://github.com/ParabolInc/parabol/commit/c4444ef9814a30bb2659427d17f639dbf151f46e)) + + +### Changed + +* **rethinkdb:** Comment: Phase 2 ([#10180](https://github.com/ParabolInc/parabol/issues/10180)) ([9148205](https://github.com/ParabolInc/parabol/commit/91482054870809d133fbc70af078b033d55c6ace)) + ## [7.46.0](https://github.com/ParabolInc/parabol/compare/v7.45.2...v7.46.0) (2024-09-04) diff --git a/package.json b/package.json index d3864155421..e400e0588ab 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "An open-source app for building smarter, more agile teams.", "author": "Parabol Inc. (http://github.com/ParabolInc)", "license": "AGPL-3.0", - "version": "7.46.0", + "version": "7.46.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 60f90f1391d..f6824bc2865 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.46.0", + "version": "7.46.1", "description": "A cron job scheduler", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/chronos#readme", @@ -25,6 +25,6 @@ }, "dependencies": { "cron": "^2.3.1", - "parabol-server": "7.46.0" + "parabol-server": "7.46.1" } } diff --git a/packages/client/package.json b/packages/client/package.json index 9c7eecb93f3..411b8ad2710 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -3,7 +3,7 @@ "description": "An open-source app for building smarter, more agile teams.", "author": "Parabol Inc. (http://github.com/ParabolInc)", "license": "AGPL-3.0", - "version": "7.46.0", + "version": "7.46.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 833139fa5a0..02c3a9a08d9 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.46.0", + "version": "7.46.1", "description": "A service that computes embedding vectors from Parabol objects", "author": "Jordan Husney ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/embedder#readme", diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 488642e3f6f..181bf194d3a 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.46.0", + "version": "7.46.1", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -27,8 +27,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.46.0", - "parabol-server": "7.46.0", + "parabol-client": "7.46.1", + "parabol-server": "7.46.1", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 6c027507821..36efbeb68df 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -2,7 +2,7 @@ "name": "integration-tests", "author": "Parabol Inc. (http://github.com/ParabolInc)", "license": "AGPL-3.0", - "version": "7.46.0", + "version": "7.46.1", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/dataloader/integrationAuthLoaders.ts b/packages/server/dataloader/integrationAuthLoaders.ts index 6fda9b74f56..6ae61e334be 100644 --- a/packages/server/dataloader/integrationAuthLoaders.ts +++ b/packages/server/dataloader/integrationAuthLoaders.ts @@ -187,6 +187,7 @@ export const slackNotificationsByTeamIdAndEvent = (parent: RootDataLoader) => { .flat() return keys.map((key) => { + const usedChannelIds = new Set() return res .filter((doc) => doc.teamId === key.teamId && doc.event === key.event) .map((notification) => { @@ -200,6 +201,11 @@ export const slackNotificationsByTeamIdAndEvent = (parent: RootDataLoader) => { } }) .filter(isValid) + .filter(({channelId}) => { + if (!channelId || usedChannelIds.has(channelId)) return false + usedChannelIds.add(channelId) + return true + }) }) }) } diff --git a/packages/server/package.json b/packages/server/package.json index b540cb84d97..6830512698a 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -3,7 +3,7 @@ "description": "An open-source app for building smarter, more agile teams.", "author": "Parabol Inc. (http://github.com/ParabolInc)", "license": "AGPL-3.0", - "version": "7.46.0", + "version": "7.46.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.46.0", + "parabol-client": "7.46.1", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/packages/server/postgres/migrations/1724884922936_Comment-phase2.ts b/packages/server/postgres/migrations/1724884922936_Comment-phase2.ts new file mode 100644 index 00000000000..896774f9f81 --- /dev/null +++ b/packages/server/postgres/migrations/1724884922936_Comment-phase2.ts @@ -0,0 +1,140 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + try { + console.log('Adding index') + await r + .table('Comment') + .indexCreate('updatedAtId', (row: any) => [row('updatedAt'), row('id')]) + .run() + await r.table('Comment').indexWait().run() + } catch { + // index already exists + } + + console.log('Adding index complete') + + // must truncate because some rows didn't have a threadParentId + await sql`TRUNCATE TABLE "Comment"`.execute(pg) + + const MAX_PG_PARAMS = 65545 + const PG_COLS = [ + 'id', + 'createdAt', + 'updatedAt', + 'isActive', + 'isAnonymous', + 'threadParentId', + 'reactjis', + 'content', + 'createdBy', + 'plaintextContent', + 'discussionId', + 'threadSortOrder' + ] as const + type Comment = { + [K in (typeof PG_COLS)[number]]: any + } + const BATCH_SIZE = Math.trunc(MAX_PG_PARAMS / PG_COLS.length) + + let curUpdatedAt = r.minval + let curId = r.minval + for (let i = 0; i < 1e6; i++) { + console.log('inserting row', i * BATCH_SIZE, String(curUpdatedAt), String(curId)) + const rawRowsToInsert = (await r + .table('Comment') + .between([curUpdatedAt, curId], [r.maxval, r.maxval], { + index: 'updatedAtId', + leftBound: 'open', + rightBound: 'closed' + }) + .orderBy({index: 'updatedAtId'}) + .limit(BATCH_SIZE) + .pluck(...PG_COLS) + .run()) as Comment[] + + const rowsToInsert = rawRowsToInsert + .map((row) => { + const {plaintextContent, threadSortOrder, reactjis, ...rest} = row as any + return { + ...rest, + plaintextContent: plaintextContent.slice(0, 2000), + threadSortOrder: threadSortOrder ? Math.trunc(threadSortOrder) : 0, + reactjis: reactjis?.map((r: any) => `(${r.id},${r.userId})`) ?? [] + } + }) + .filter((row) => row.discussionId) + + if (rowsToInsert.length === 0) break + const lastRow = rowsToInsert[rowsToInsert.length - 1] + curUpdatedAt = lastRow.updatedAt + curId = lastRow.id + await Promise.all( + rowsToInsert.map(async (row) => { + try { + await pg + .insertInto('Comment') + .values(row) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + if (e.constraint === 'fk_createdBy') { + await pg + .insertInto('Comment') + .values({...row, createdBy: null}) + .onConflict((oc) => oc.doNothing()) + .execute() + return + } + if (e.constraint === 'fk_discussionId') { + console.log(`Skipping ${row.id} because it has no discussion`) + return + } + console.log(e, row) + } + }) + ) + } + + // if the threadParentId references an id that does not exist, set it to null + console.log('adding threadParentId constraint') + await pg + .updateTable('Comment') + .set({threadParentId: null}) + .where(({eb, selectFrom}) => + eb( + 'id', + 'in', + selectFrom('Comment as child') + .select('child.id') + .leftJoin('Comment as parent', 'child.threadParentId', 'parent.id') + .where('parent.id', 'is', null) + .where('child.threadParentId', 'is not', null) + ) + ) + .execute() + await pg.schema + .alterTable('Comment') + .addForeignKeyConstraint('fk_threadParentId', ['threadParentId'], 'Comment', ['id']) + .onDelete('set null') + .execute() +} + +export async function down() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql`TRUNCATE TABLE "Comment" CASCADE`.execute(pg) +}