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): SuggestedAction: Phase 1 #10035

Merged
merged 9 commits into from
Aug 1, 2024
Merged
15 changes: 14 additions & 1 deletion packages/server/dataloader/foreignKeyLoaderMakers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import getKysely from '../postgres/getKysely'
import {selectTemplateDimension, selectTemplateScale, selectTimelineEvent} from '../postgres/select'
import {
selectSuggestedAction,
selectTemplateDimension,
selectTemplateScale,
selectTimelineEvent
} from '../postgres/select'
import {foreignKeyLoaderMaker} from './foreignKeyLoaderMaker'
import {selectOrganizations, selectRetroReflections, selectTeams} from './primaryKeyLoaderMakers'

Expand Down Expand Up @@ -148,3 +153,11 @@ export const templateDimensionsByScaleId = foreignKeyLoaderMaker(
return selectTemplateDimension().where('scaleId', 'in', scaleIds).orderBy('sortOrder').execute()
}
)

export const _suggestedActionsByUserId = foreignKeyLoaderMaker(
'_suggestedActions',
'userId',
async (userIds) => {
return selectSuggestedAction().where('userId', 'in', userIds).execute()
}
)
5 changes: 5 additions & 0 deletions packages/server/dataloader/primaryKeyLoaderMakers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {getTeamPromptResponsesByIds} from '../postgres/queries/getTeamPromptResp
import getTemplateRefsByIds from '../postgres/queries/getTemplateRefsByIds'
import {getUsersByIds} from '../postgres/queries/getUsersByIds'
import {
selectSuggestedAction,
selectTemplateDimension,
selectTemplateScale,
selectTemplateScaleRef,
Expand Down Expand Up @@ -165,3 +166,7 @@ export const templateScales = primaryKeyLoaderMaker((ids: readonly string[]) =>
export const templateDimensions = primaryKeyLoaderMaker((ids: readonly string[]) => {
return selectTemplateDimension().where('id', 'in', ids).execute()
})

export const _suggestedActions = primaryKeyLoaderMaker((ids: readonly string[]) => {
return selectSuggestedAction().where('id', 'in', ids).execute()
})
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export const suggestedActionsByUserId = new RethinkForeignKeyLoaderMaker(
return r
.table('SuggestedAction')
.getAll(r.args(userIds), {index: 'userId'})
.filter({removedAt: null})
.filter((row: any) => row('removedAt').default(null).eq(null))
.run()
}
)
Expand Down
6 changes: 6 additions & 0 deletions packages/server/graphql/mutations/dismissSuggestedAction.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {GraphQLID, GraphQLNonNull} from 'graphql'
import getRethink from '../../database/rethinkDriver'
import getKysely from '../../postgres/getKysely'
import {getUserId} from '../../utils/authorization'
import standardError from '../../utils/standardError'
import {GQLContext} from '../graphql'
Expand Down Expand Up @@ -34,6 +35,11 @@ export default {
}

// RESOLUTION
await getKysely()
.updateTable('SuggestedAction')
.set({removedAt: now})
.where('id', '=', suggestedActionId)
.execute()
await r.table('SuggestedAction').get(suggestedActionId).update({removedAt: now}).run()

// no need to publish since that'll only affect their other open tabs
Expand Down
49 changes: 29 additions & 20 deletions packages/server/graphql/mutations/helpers/bootstrapNewUser.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import getRethink from '../../../database/rethinkDriver'
import AuthToken from '../../../database/types/AuthToken'
import SuggestedActionCreateNewTeam from '../../../database/types/SuggestedActionCreateNewTeam'
import SuggestedActionInviteYourTeam from '../../../database/types/SuggestedActionInviteYourTeam'
import SuggestedActionTryTheDemo from '../../../database/types/SuggestedActionTryTheDemo'
import TimelineEventJoinedParabol from '../../../database/types/TimelineEventJoinedParabol'
import User from '../../../database/types/User'
import generateUID from '../../../generateUID'
Expand Down Expand Up @@ -89,28 +86,43 @@ const bootstrapNewUser = async (

const teamsWithAutoJoin = teamsWithAutoJoinRes.flat().filter(isValid)
const tms = [] as string[]

const actions = [
{
id: generateUID(),
userId,
type: 'tryTheDemo' as const,
priority: 1
},
{
id: generateUID(),
userId,
type: 'createNewTeam' as const,
priority: 4
}
]
if (teamsWithAutoJoin.length > 0) {
await Promise.all(
teamsWithAutoJoin.map((team) => {
const teamId = team.id
tms.push(teamId)
const inviteYourTeam = {
id: generateUID(),
userId,
teamId,
type: 'inviteYourTeam' as const,
priority: 2
}
return Promise.all([
acceptTeamInvitation(team, userId, dataLoader),
isOrganic
? Promise.all([
r
.table('SuggestedAction')
.insert(new SuggestedActionInviteYourTeam({userId, teamId}))
.run()
pg.insertInto('SuggestedAction').values(inviteYourTeam).execute(),
r.table('SuggestedAction').insert(inviteYourTeam).run()
])
: r
.table('SuggestedAction')
.insert([
new SuggestedActionTryTheDemo({userId}),
new SuggestedActionCreateNewTeam({userId})
])
.run(),
: Promise.all([
pg.insertInto('SuggestedAction').values(actions).execute(),
r.table('SuggestedAction').insert(actions).run()
]),
analytics.autoJoined(newUser, teamId)
])
})
Expand All @@ -130,15 +142,12 @@ const bootstrapNewUser = async (
await Promise.all([
createTeamAndLeader(newUser as IUser, validNewTeam, dataLoader),
addSeedTasks(userId, teamId),
r.table('SuggestedAction').insert(new SuggestedActionInviteYourTeam({userId, teamId})).run(),
sendPromptToJoinOrg(newUser, dataLoader)
])
analytics.newOrg(newUser, orgId, teamId, true)
} else {
await r
.table('SuggestedAction')
.insert([new SuggestedActionTryTheDemo({userId}), new SuggestedActionCreateNewTeam({userId})])
.run()
await pg.insertInto('SuggestedAction').values(actions).execute()
await r.table('SuggestedAction').insert(actions).run()
}

analytics.accountCreated(newUser, !isOrganic, isPatient0)
Expand Down
24 changes: 24 additions & 0 deletions packages/server/graphql/mutations/helpers/createTeamAndLeader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import MeetingSettingsRetrospective from '../../../database/types/MeetingSetting
import Team from '../../../database/types/Team'
import TimelineEventCreatedTeam from '../../../database/types/TimelineEventCreatedTeam'
import {DataLoaderInstance} from '../../../dataloader/RootDataLoader'
import generateUID from '../../../generateUID'
import getKysely from '../../../postgres/getKysely'
import IUser from '../../../postgres/types/IUser'

Expand Down Expand Up @@ -42,6 +43,13 @@ export default async function createTeamAndLeader(
})

const pg = getKysely()
const suggestedAction = {
id: generateUID(),
userId,
teamId,
type: 'inviteYourTeam' as const,
priority: 2
}
await Promise.all([
pg
.with('TeamInsert', (qc) => qc.insertInto('Team').values(verifiedTeam))
Expand All @@ -63,11 +71,27 @@ export default async function createTeamAndLeader(
openDrawer: 'manageTeam'
})
)
.with('SuggestedActionInsert', (qc) =>
qc
.insertInto('SuggestedAction')
.values(suggestedAction)
.onConflict((oc) => oc.columns(['userId', 'type']).doNothing())
)
.insertInto('TimelineEvent')
.values(timelineEvent)
.execute(),
// add meeting settings
r.table('MeetingSettings').insert(meetingSettings).run()
])
const hasSuggestedAction = await r
.table('SuggestedAction')
.getAll(userId, {index: 'userId'})
.filter({type: 'inviteYourTeam'})
.count()
.ge(1)
.run()
if (!hasSuggestedAction) {
await r.table('SuggestedAction').insert(suggestedAction).run()
}
Comment on lines +86 to +95
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure the logic for checking existing suggested actions is correct.

The logic correctly checks for existing suggested actions and inserts a new one if none exist. However, consider using the same database (PostgreSQL) for consistency instead of mixing RethinkDB and PostgreSQL.

-  const hasSuggestedAction = await r
-    .table('SuggestedAction')
-    .getAll(userId, {index: 'userId'})
-    .filter({type: 'inviteYourTeam'})
-    .count()
-    .ge(1)
-    .run()
-  if (!hasSuggestedAction) {
-    await r.table('SuggestedAction').insert(suggestedAction).run()
-  }
+  const hasSuggestedAction = await pg
+    .selectFrom('SuggestedAction')
+    .select('id')
+    .where('userId', '=', userId)
+    .where('type', '=', 'inviteYourTeam')
+    .executeTakeFirst()
+  if (!hasSuggestedAction) {
+    await pg.insertInto('SuggestedAction').values(suggestedAction).execute()
+  }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const hasSuggestedAction = await r
.table('SuggestedAction')
.getAll(userId, {index: 'userId'})
.filter({type: 'inviteYourTeam'})
.count()
.ge(1)
.run()
if (!hasSuggestedAction) {
await r.table('SuggestedAction').insert(suggestedAction).run()
}
const hasSuggestedAction = await pg
.selectFrom('SuggestedAction')
.select('id')
.where('userId', '=', userId)
.where('type', '=', 'inviteYourTeam')
.executeTakeFirst()
if (!hasSuggestedAction) {
await pg.insertInto('SuggestedAction').values(suggestedAction).execute()
}

dataLoader.clearAll(['teams', 'users', 'teamMembers', 'timelineEvents', 'meetingSettings'])
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async (
.run(),
meetingMember: r.table('MeetingMember').getAll(userIdToDelete, {index: 'userId'}).delete(),
notification: r.table('Notification').getAll(userIdToDelete, {index: 'userId'}).delete(),
suggestedAction: r.table('SuggestedAction').getAll(userIdToDelete, {index: 'userId'}).delete(),
createdTasks: r
.table('Task')
.getAll(r.args(teamIds), {index: 'teamId'})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {Client} from 'pg'
import getPgConfig from '../getPgConfig'

export async function up() {
const client = new Client(getPgConfig())
await client.connect()
await client.query(`
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'SuggestedActionTypeEnum') THEN
CREATE TYPE "SuggestedActionTypeEnum" AS ENUM (
'inviteYourTeam',
'tryTheDemo',
'createNewTeam',
'tryRetroMeeting',
'tryActionMeeting'
);
END IF;
CREATE TABLE IF NOT EXISTS "SuggestedAction" (
"id" VARCHAR(100) PRIMARY KEY,
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
"priority" SMALLINT NOT NULL DEFAULT 0,
"removedAt" TIMESTAMP WITH TIME ZONE,
"type" "SuggestedActionTypeEnum" NOT NULL,
"teamId" VARCHAR(100),
"userId" VARCHAR(100) NOT NULL,
UNIQUE("userId", "type"),
CONSTRAINT "fk_userId"
FOREIGN KEY("userId")
REFERENCES "User"("id")
ON DELETE CASCADE,
CONSTRAINT "fk_teamId"
FOREIGN KEY("teamId")
REFERENCES "Team"("id")
ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS "idx_SuggestedAction_teamId" ON "SuggestedAction"("teamId");
CREATE INDEX IF NOT EXISTS "idx_SuggestedAction_userId" ON "SuggestedAction"("userId");
END $$;
`)
await client.end()
}

export async function down() {
const client = new Client(getPgConfig())
await client.connect()
await client.query(`
DROP TABLE "SuggestedAction";
DROP TYPE "SuggestedActionTypeEnum";
` /* Do undo magic */)
await client.end()
}
4 changes: 4 additions & 0 deletions packages/server/postgres/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,7 @@ export const selectTemplateScale = () => {
export const selectTemplateDimension = () => {
return getKysely().selectFrom('TemplateDimension').selectAll().where('removedAt', 'is', null)
}

export const selectSuggestedAction = () => {
return getKysely().selectFrom('SuggestedAction').selectAll().where('removedAt', 'is', null)
}
66 changes: 33 additions & 33 deletions packages/server/safeMutations/acceptTeamInvitation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {InvoiceItemType} from 'parabol-client/types/constEnums'
import TeamMemberId from '../../client/shared/gqlIds/TeamMemberId'
import adjustUserCount from '../billing/helpers/adjustUserCount'
import getRethink from '../database/rethinkDriver'
import SuggestedActionCreateNewTeam from '../database/types/SuggestedActionCreateNewTeam'
import {DataLoaderInstance} from '../dataloader/RootDataLoader'
import generateUID from '../generateUID'
import {DataLoaderWorker} from '../graphql/graphql'
Expand All @@ -23,38 +22,39 @@ const handleFirstAcceptedInvitation = async (
const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId)
const teamLead = teamMembers.find((tm) => tm.isLead)!
const {userId} = teamLead
const isNewTeamLead = await r
.table('SuggestedAction')
.getAll(userId, {index: 'userId'})
.filter({type: 'tryRetroMeeting'})
.count()
.eq(0)
.run()
if (!isNewTeamLead) return null
await r
.table('SuggestedAction')
.insert([
{
id: generateUID(),
createdAt: now,
priority: 3,
removedAt: null,
teamId,
type: 'tryRetroMeeting',
userId
},
new SuggestedActionCreateNewTeam({userId}),
{
id: generateUID(),
createdAt: now,
priority: 5,
removedAt: null,
teamId,
type: 'tryActionMeeting',
userId
}
])
.run()
const suggestedActions = await dataLoader.get('suggestedActionsByUserId').load(userId)
const hasTryRetro = suggestedActions.some((sa) => sa.type === 'tryRetroMeeting')
if (hasTryRetro) return null
const actions = [
{
id: generateUID(),
createdAt: now,
priority: 3,
removedAt: null,
teamId,
type: 'tryRetroMeeting' as const,
userId
},
{
id: generateUID(),
createdAt: now,
priority: 4,
removedAt: null,
type: 'createNewTeam' as const,
userId
},
{
id: generateUID(),
createdAt: now,
priority: 5,
removedAt: null,
teamId,
type: 'tryActionMeeting' as const,
userId
}
]
await getKysely().insertInto('SuggestedAction').values(actions).execute()
await r.table('SuggestedAction').insert(actions).run()
return userId
}

Expand Down
12 changes: 10 additions & 2 deletions packages/server/safeMutations/removeSuggestedAction.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import {sql} from 'kysely'
import getRethink from '../database/rethinkDriver'
import {TSuggestedActionTypeEnum} from '../graphql/types/SuggestedActionTypeEnum'
import getKysely from '../postgres/getKysely'
import {SuggestedAction} from '../postgres/pg'

const removeSuggestedAction = async (userId: string, type: TSuggestedActionTypeEnum) => {
const removeSuggestedAction = async (userId: string, type: SuggestedAction['type']) => {
const r = await getRethink()
await getKysely()
.updateTable('SuggestedAction')
.set({removedAt: sql`CURRENT_TIMESTAMP`})
.where('userId', '=', userId)
.where('type', '=', type)
.execute()
return r
.table('SuggestedAction')
.getAll(userId, {index: 'userId'})
Expand Down
Loading