Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(rethinkdb): NewMeeting: Phase 2 #10266

Open
wants to merge 66 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
bbd3d12
chore: write to PG
mattkrick Aug 27, 2024
217606e
fix: refactor comment to SDL
mattkrick Aug 27, 2024
fd13993
add comment
mattkrick Aug 27, 2024
918b493
Merge branch 'master' into chore/Comment-phase1
mattkrick Aug 28, 2024
424a54b
migrate existing Comments
mattkrick Aug 29, 2024
c0278ae
switch reads to PG
mattkrick Aug 29, 2024
4cf9c8f
fix replies
mattkrick Aug 29, 2024
d61559a
Merge branch 'master' into chore/Comment-phase3
mattkrick Sep 6, 2024
da853e7
init migration
mattkrick Sep 6, 2024
fb941f6
add writes to PG
mattkrick Sep 9, 2024
0efe604
Merge branch 'master' into chore/ReflectPrompt-phase1
mattkrick Sep 9, 2024
814d9ef
refactor add/move reflect template prompt to sdl
mattkrick Sep 9, 2024
134b3b6
refactor 4 mutations to sdl
mattkrick Sep 9, 2024
2f8cf27
fix downmigration
mattkrick Sep 9, 2024
9f372a1
Merge branch 'master' into chore/ReflectPrompt-phase1
mattkrick Sep 9, 2024
171ded7
use explicit columns
mattkrick Sep 9, 2024
a38beb4
migrate existing rows
mattkrick Sep 9, 2024
8a64083
progress
mattkrick Sep 9, 2024
fcc66a9
use explicit props
mattkrick Sep 9, 2024
c44f72c
use explicit props nullable
mattkrick Sep 9, 2024
9c2b951
Merge branch 'chore/ReflectPrompt-phase1' into chore/ReflectPrompt-ph…
mattkrick Sep 9, 2024
e628e0b
remove writes to R
mattkrick Sep 10, 2024
21fc97c
fix tsc
mattkrick Sep 10, 2024
ec73789
fix bad old migrations referencing types
mattkrick Sep 10, 2024
d18a710
Merge branch 'chore/ReflectPrompt-phase1' into chore/ReflectPrompt-ph…
mattkrick Sep 10, 2024
551ce33
Merge branch 'chore/ReflectPrompt-phase2' into chore/ReflectPrompt-ph…
mattkrick Sep 10, 2024
726f632
chore: migrate PasswordResetRequest
mattkrick Sep 10, 2024
9c7839d
use 2 queries for resets
mattkrick Sep 10, 2024
89040df
clean up clientside sortOrder
mattkrick Sep 10, 2024
5843139
Merge branch 'master' into chore/ReflectPrompt-phase2
mattkrick Sep 11, 2024
cc6bcbc
Merge branch 'chore/ReflectPrompt-phase2' into chore/ReflectPrompt-ph…
mattkrick Sep 11, 2024
69fa463
Merge branch 'chore/ReflectPrompt-phase3' into chore/PasswordResetReq…
mattkrick Sep 11, 2024
8abce47
chore: migrate PushInvitation
mattkrick Sep 11, 2024
c30e68f
fix test
mattkrick Sep 11, 2024
845366f
Merge branch 'master' into chore/NewMeeting-phase1
mattkrick Sep 11, 2024
feb72b8
add types
mattkrick Sep 12, 2024
ad4b876
Merge branch 'master' into chore/NewMeeting-phase1
mattkrick Sep 12, 2024
7d84391
fix name conflict
mattkrick Sep 12, 2024
a33d00f
remove as much coercion as possible
mattkrick Sep 12, 2024
a25d978
Merge branch 'master' into chore/NewMeeting-phase1
mattkrick Sep 12, 2024
cdc0a02
fix migration name
mattkrick Sep 12, 2024
835dd65
Merge branch 'master' into chore/NewMeeting-phase1
mattkrick Sep 13, 2024
5a155ca
revert processRecurrence test
mattkrick Sep 13, 2024
3a0d530
progress
mattkrick Sep 15, 2024
30c1ab3
finish adding writes to PG
mattkrick Sep 17, 2024
e2f8b30
fix downmigration
mattkrick Sep 17, 2024
2eb4482
self review
mattkrick Sep 17, 2024
7f256a2
first pass
mattkrick Sep 23, 2024
36fe2f8
chore: add default value for meetinCount
mattkrick Sep 24, 2024
9e3c1bc
equality checker first pass
mattkrick Sep 24, 2024
7eebaba
on delete cascade
mattkrick Sep 24, 2024
7c374ed
Merge branch 'master' into chore/NewMeeting-phase1
mattkrick Sep 24, 2024
24624f1
merge master
mattkrick Sep 24, 2024
a2200ce
Merge branch 'chore/NewMeeting-phase1b' into chore/NewMeeting-phase2
mattkrick Sep 24, 2024
c2acd22
fixup equality checker
mattkrick Sep 24, 2024
57790ac
fix stopMeetingSeries
mattkrick Sep 24, 2024
6a23205
fix trigger
mattkrick Sep 24, 2024
feb3e4b
fix trigger
mattkrick Sep 24, 2024
b06ab38
Merge branch 'chore/NewMeeting-phase1b' into chore/NewMeeting-phase2
mattkrick Sep 24, 2024
2a28f19
remove comma after execute()
mattkrick Sep 25, 2024
f2b7b79
fix equality check
mattkrick Sep 25, 2024
703b109
fix endTime index
mattkrick Sep 27, 2024
046198b
Merge branch 'chore/NewMeeting-phase1' into chore/NewMeeting-phase1b
mattkrick Sep 27, 2024
8c12687
fix tsc
mattkrick Sep 27, 2024
4e708b5
Merge branch 'chore/NewMeeting-phase1b' into chore/NewMeeting-phase2
mattkrick Sep 27, 2024
d7dca32
Merge branch 'master' into chore/NewMeeting-phase2
mattkrick Sep 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 46 additions & 16 deletions packages/server/graphql/private/mutations/checkRethinkPgEquality.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import getRethink from '../../../database/rethinkDriver'
import getFileStoreManager from '../../../fileStorage/getFileStoreManager'
import getKysely from '../../../postgres/getKysely'
import {selectNewMeetings} from '../../../postgres/select'
import {checkRowCount, checkTableEq} from '../../../postgres/utils/checkEqBase'
import {
compareDateAlmostEqual,
compareRValStringAsNumber,
compareRValUndefinedAsFalse,
compareRValUndefinedAsNull,
compareRValUndefinedAsNullAndTruncateRVal,
compareRValUndefinedAsZero,
defaultEqFn
} from '../../../postgres/utils/rethinkEqualityFns'
import {MutationResolvers} from '../resolverTypes'
Expand All @@ -33,37 +35,65 @@ const checkRethinkPgEquality: MutationResolvers['checkRethinkPgEquality'] = asyn
) => {
const r = await getRethink()

if (tableName === 'TeamMember') {
if (tableName === 'NewMeeting') {
const rowCountResult = await checkRowCount(tableName)
const rethinkQuery = (joinedAt: Date, id: string | number) => {
const rethinkQuery = (updatedAt: Date, id: string | number) => {
return r
.table('TeamMember' as any)
.between([joinedAt, id], [r.maxval, r.maxval], {
.table('NewMeeting' as any)
.between([updatedAt, id], [r.maxval, r.maxval], {
index: 'updatedAtId',
leftBound: 'open',
rightBound: 'closed'
})
.orderBy({index: 'updatedAtId'}) as any
}
const pgQuery = async (ids: string[]) => {
return getKysely().selectFrom('TeamMember').selectAll().where('id', 'in', ids).execute()
return selectNewMeetings().where('id', 'in', ids).execute()
}
const errors = await checkTableEq(
rethinkQuery,
pgQuery,
{
id: defaultEqFn,
isNotRemoved: compareRValUndefinedAsFalse,
isLead: compareRValUndefinedAsFalse,
isSpectatingPoker: compareRValUndefinedAsFalse,
email: defaultEqFn,
openDrawer: compareRValUndefinedAsNull,
picture: defaultEqFn,
preferredName: compareRValUndefinedAsNullAndTruncateRVal(100),
teamId: defaultEqFn,
userId: defaultEqFn,
isLegacy: compareRValUndefinedAsFalse,
createdAt: compareDateAlmostEqual,
updatedAt: compareDateAlmostEqual
updatedAt: compareDateAlmostEqual,
createdBy: defaultEqFn,
endedAt: compareRValUndefinedAsNull,
facilitatorStageId: defaultEqFn,
facilitatorUserId: defaultEqFn,
meetingCount: compareRValUndefinedAsZero,
meetingNumber: compareRValUndefinedAsZero,
name: compareRValUndefinedAsNullAndTruncateRVal(100),
summarySentAt: compareRValUndefinedAsNull,
teamId: defaultEqFn,
meetingType: defaultEqFn,
phases: defaultEqFn,
showConversionModal: compareRValUndefinedAsFalse,
meetingSeriesId: compareRValUndefinedAsNull,
scheduledEndTime: compareRValUndefinedAsNull,
summary: compareRValUndefinedAsNullAndTruncateRVal(10000),
sentimentScore: compareRValUndefinedAsNull,
usedReactjis: compareRValUndefinedAsNull,
slackTs: compareRValStringAsNumber,
engagement: compareRValUndefinedAsNull,
totalVotes: compareRValUndefinedAsNull,
maxVotesPerGroup: compareRValUndefinedAsNull,
disableAnonymity: compareRValUndefinedAsNull,
commentCount: compareRValUndefinedAsNull,
taskCount: compareRValUndefinedAsNull,
agendaItemCount: compareRValUndefinedAsNull,
storyCount: compareRValUndefinedAsNull,
templateId: compareRValUndefinedAsNull,
topicCount: compareRValUndefinedAsNull,
reflectionCount: compareRValUndefinedAsNull,
transcription: compareRValUndefinedAsNull,
recallBotId: compareRValUndefinedAsNull,
videoMeetingURL: compareRValUndefinedAsNull,
autogroupReflectionGroups: compareRValUndefinedAsNull,
resetReflectionGroups: compareRValUndefinedAsNull,
templateRefId: compareRValUndefinedAsNull,
meetingPrompt: compareRValUndefinedAsNull
},
maxErrors
)
Expand Down
170 changes: 170 additions & 0 deletions packages/server/postgres/migrations/1726602922665_NewMeeting-phase2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
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<any>({
dialect: new PostgresDialect({
pool: getPg()
})
})

try {
console.log('Adding index')
await r
.table('NewMeeting')
.indexCreate('updatedAtId', (row: any) => [row('updatedAt'), row('id')])
.run()
await r.table('NewMeeting').indexWait().run()
} catch {
// index already exists
}

console.log('Adding index complete')

await sql`ALTER TABLE "NewMeeting" DISABLE TRIGGER "check_meeting_overlap"`.execute(pg)
const MAX_PG_PARAMS = 65545
const PG_COLS = [
'id',
'isLegacy',
'createdAt',
'updatedAt',
'createdBy',
'endedAt',
'facilitatorStageId',
'facilitatorUserId',
'meetingCount',
'meetingNumber',
'name',
'summarySentAt',
'teamId',
'meetingType',
'phases',
'showConversionModal',
'meetingSeriesId',
'scheduledEndTime',
'summary',
'sentimentScore',
'usedReactjis',
'slackTs',
'engagement',
'totalVotes',
'maxVotesPerGroup',
'disableAnonymity',
'commentCount',
'taskCount',
'agendaItemCount',
'storyCount',
'templateId',
'topicCount',
'reflectionCount',
'transcription',
'recallBotId',
'videoMeetingURL',
'autogroupReflectionGroups',
'resetReflectionGroups',
'templateRefId',
'meetingPrompt'
] as const
type NewMeeting = {
[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

const insertRow = async (row) => {
if (!row.facilitatorStageId) {
console.log('Meeting has no facilitatorId, skipping insert', row.id, row.teamId)
return
}
try {
await pg
.insertInto('NewMeeting')
.values(row)
.onConflict((oc) => oc.doNothing())
.execute()
} catch (e) {
if (e.constraint === 'fk_createdBy') {
return insertRow({...row, createdBy: null})
}
if (e.constraint === 'fk_facilitatorUserId') {
return insertRow({...row, facilitatorUserId: null})
}
if (e.constraint === 'fk_teamId') {
console.log('Meeting has no team, skipping insert', row.id)
return
}
if (e.constraint === 'fk_meetingSeriesId') {
return insertRow({...row, meetingSeriesId: null})
}
if (e.constraint === 'fk_templateId') {
console.log('Meeting has no template, skipping insert', row.id)
return
}
throw e
}
}
for (let i = 0; i < 1e6; i++) {
console.log('inserting row', i * BATCH_SIZE, String(curUpdatedAt), String(curId))
const rawRowsToInsert = (await r
.table('NewMeeting')
.between([curUpdatedAt, curId], [r.maxval, r.maxval], {
index: 'updatedAtId',
leftBound: 'open',
rightBound: 'closed'
})
.orderBy({index: 'updatedAtId'})
.limit(BATCH_SIZE)
.pluck(...PG_COLS)
.run()) as NewMeeting[]

const rowsToInsert = rawRowsToInsert.map((row) => {
const {
phases,
name,
summary,
usedReactjis,
slackTs,
transcription,
autogroupReflectionGroups,
resetReflectionGroups,
meetingPrompt,
meetingCount,
...rest
} = row as any
return {
...rest,
phases: JSON.stringify(phases),
name: name.slice(0, 100),
summary: summary ? summary.slice(0, 10000) : null,
usedReactjis: JSON.stringify(usedReactjis),
slackTs: isNaN(Number(slackTs)) ? null : Number(slackTs),
transcription: JSON.stringify(transcription),
autogroupReflectionGroups: JSON.stringify(autogroupReflectionGroups),
resetReflectionGroups: JSON.stringify(resetReflectionGroups),
meetingPrompt: meetingPrompt ? meetingPrompt.slice(0, 255) : null,
meetingCount: meetingCount || 0
}
})

if (rowsToInsert.length === 0) break
const lastRow = rowsToInsert[rowsToInsert.length - 1]
curUpdatedAt = lastRow.updatedAt
curId = lastRow.id
await Promise.all(rowsToInsert.map(async (row) => insertRow(row)))
}
await sql`ALTER TABLE "NewMeeting" ENABLE TRIGGER "check_meeting_overlap"`.execute(pg)
}

export async function down() {
const pg = new Kysely<any>({
dialect: new PostgresDialect({
pool: getPg()
})
})
await sql`TRUNCATE TABLE "NewMeeting" CASCADE`.execute(pg)
}
2 changes: 1 addition & 1 deletion packages/server/postgres/utils/checkEqBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export async function checkTableEq(
const pgRow = pgRowsById[id]

if (!pgRow) {
errors.push({id, prop: id, rVal: id, pgVal: null})
errors.push({id, prop: '', rVal: null, pgVal: null})
if (errors.length >= maxErrors) return errors
continue
}
Expand Down
28 changes: 25 additions & 3 deletions packages/server/postgres/utils/rethinkEqualityFns.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
import isValidDate from 'parabol-client/utils/isValidDate'
import stringSimilarity from 'string-similarity'

function sortObjectKeys(obj: any): any {
if (Array.isArray(obj)) {
// If it's an array, recurse into each element
return obj.map(sortObjectKeys)
} else if (obj !== null && typeof obj === 'object') {
if (obj instanceof Date) return obj
// If it's an object, sort the keys and recurse on each value
const sortedObj: {[key: string]: any} = {}
Object.keys(obj)
.sort()
.forEach((key) => {
sortedObj[key] = sortObjectKeys(obj[key])
})
return sortedObj
} else {
// If it's a primitive value, just return it
return obj
}
}

export const defaultEqFn = (a: unknown, b: unknown) => {
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime()
if (Array.isArray(a) && Array.isArray(b)) return JSON.stringify(a) === JSON.stringify(b)
if (typeof a === 'object' && typeof b === 'object') return JSON.stringify(a) === JSON.stringify(b)
if (Array.isArray(a) && Array.isArray(b))
return JSON.stringify(sortObjectKeys(a)) === JSON.stringify(sortObjectKeys(b))
if (typeof a === 'object' && typeof b === 'object')
return JSON.stringify(sortObjectKeys(a)) === JSON.stringify(sortObjectKeys(b))
return a === b
}
export const compareDateAlmostEqual = (rVal: unknown, pgVal: unknown) => {
Expand Down Expand Up @@ -47,7 +69,7 @@ export const compareRValUndefinedAsEmptyArray = (rVal: unknown, pgVal: unknown)
}

export const compareRValStringAsNumber = (rVal: unknown, pgVal: unknown) => {
const normalizedRVal = Number(rVal)
const normalizedRVal = rVal ? Number(rVal) : null
return defaultEqFn(normalizedRVal, pgVal)
}

Expand Down
Loading