Skip to content

Commit

Permalink
chore(rethinkdb): SuggestedAction: Phase 1 (#10035)
Browse files Browse the repository at this point in the history
Signed-off-by: Matt Krick <[email protected]>
Co-authored-by: Jordan Husney <[email protected]>
  • Loading branch information
mattkrick and jordanh authored Aug 1, 2024
1 parent a27769c commit d00da10
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 58 deletions.
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()
}
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

0 comments on commit d00da10

Please sign in to comment.