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): MeetingSettings: Phase 1 #10088

Merged
merged 3 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 0 additions & 23 deletions packages/server/database/rethinkDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@ import SlackAuth from '../database/types/SlackAuth'
import SlackNotification from '../database/types/SlackNotification'
import TeamInvitation from '../database/types/TeamInvitation'
import {AnyMeeting, AnyMeetingSettings, AnyMeetingTeamMember} from '../postgres/types/Meeting'
import {ScheduledJobUnion} from '../types/custom'
import getRethinkConfig from './getRethinkConfig'
import {R} from './stricterR'
import AgendaItem from './types/AgendaItem'
import AtlassianAuth from './types/AtlassianAuth'
import Comment from './types/Comment'
import FailedAuthRequest from './types/FailedAuthRequest'
import MassInvitation from './types/MassInvitation'
import NotificationKickedOut from './types/NotificationKickedOut'
import NotificationMeetingStageTimeLimitEnd from './types/NotificationMeetingStageTimeLimitEnd'
Expand All @@ -31,10 +28,6 @@ export type RethinkSchema = {
type: AgendaItem
index: 'teamId' | 'meetingId'
}
AtlassianAuth: {
type: AtlassianAuth
index: 'atlassianUserId' | 'userId' | 'teamId'
}
Comment: {
type: Comment
index: 'discussionId'
Expand All @@ -43,18 +36,6 @@ export type RethinkSchema = {
type: RetrospectivePrompt
index: 'teamId' | 'templateId'
}
EmailVerification: {
type: any
index: 'email' | 'token'
}
FailedAuthRequest: {
type: FailedAuthRequest
index: 'email' | 'ip'
}
GQLRequest: {
type: any
index: 'id'
}
MassInvitation: {
type: MassInvitation
index: 'teamMemberId'
Expand Down Expand Up @@ -102,10 +83,6 @@ export type RethinkSchema = {
type: PushInvitation
index: 'userId'
}
ScheduledJob: {
type: ScheduledJobUnion
index: 'runAt' | 'type'
}
SlackAuth: {
type: SlackAuth
index: 'teamId' | 'userId'
Expand Down
35 changes: 30 additions & 5 deletions packages/server/dataloader/customLoaderMakers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import DataLoader from 'dataloader'
import tracer from 'dd-trace'
import {Selectable, SqlBool, sql} from 'kysely'
import {PARABOL_AI_USER_ID} from '../../client/utils/constants'
import getRethink, {RethinkSchema} from '../database/rethinkDriver'
import getRethink from '../database/rethinkDriver'
import {RDatum} from '../database/stricterR'
import MeetingSettingsTeamPrompt from '../database/types/MeetingSettingsTeamPrompt'
import MeetingTemplate from '../database/types/MeetingTemplate'
Expand All @@ -26,8 +26,8 @@ import getLatestTaskEstimates from '../postgres/queries/getLatestTaskEstimates'
import getMeetingTaskEstimates, {
MeetingTaskEstimatesResult
} from '../postgres/queries/getMeetingTaskEstimates'
import {selectTeams} from '../postgres/select'
import {OrganizationUser, Team} from '../postgres/types'
import {selectMeetingSettings, selectTeams} from '../postgres/select'
import {MeetingSettings, OrganizationUser, Team} from '../postgres/types'
import {AnyMeeting, MeetingTypeEnum} from '../postgres/types/Meeting'
import {Logger} from '../utils/Logger'
import getRedis from '../utils/getRedis'
Expand Down Expand Up @@ -288,7 +288,7 @@ export const githubDimensionFieldMaps = (parent: RootDataLoader) => {

export const meetingSettingsByType = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => {
dependsOn('meetingSettings')
return new DataLoader<MeetingSettingsKey, RethinkSchema['MeetingSettings']['type'], string>(
return new DataLoader<MeetingSettingsKey, MeetingSettings, string>(
async (keys) => {
const r = await getRethink()
const types = {} as Record<MeetingTypeEnum, string[]>
Expand All @@ -313,7 +313,7 @@ export const meetingSettingsByType = (parent: RootDataLoader, dependsOn: Registe
const {teamId, meetingType} = key
// until we decide the final shape of the team prompt settings, let's return a temporary hardcoded value
if (meetingType === 'teamPrompt') {
return new MeetingSettingsTeamPrompt({teamId})
return new MeetingSettingsTeamPrompt({teamId}) as any
}
return docs.find((doc) => doc.teamId === teamId && doc.meetingType === meetingType)!
})
Expand All @@ -325,6 +325,31 @@ export const meetingSettingsByType = (parent: RootDataLoader, dependsOn: Registe
)
}

export const _PGmeetingSettingsByType = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => {
dependsOn('meetingSettings')
return new DataLoader<MeetingSettingsKey, MeetingSettings, string>(
async (keys) => {
const res = await selectMeetingSettings()
.where(({eb, refTuple, tuple}) =>
eb(
refTuple('teamId', 'meetingType'),
'in',
keys.map((key) => tuple(key.teamId, key.meetingType))
)
)
.execute()
return keys.map(
(key) =>
res.find((doc) => doc.teamId === key.teamId && doc.meetingType === key.meetingType)!
)
},
{
...parent.dataLoaderOptions,
cacheKeyFn: (key) => `${key.teamId}:${key.meetingType}`
}
)
}

export const organizationApprovedDomainsByOrgId = (parent: RootDataLoader) => {
return new DataLoader<string, string[], string>(
async (orgIds) => {
Expand Down
5 changes: 5 additions & 0 deletions packages/server/dataloader/primaryKeyLoaderMakers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import getMeetingTemplatesByIds from '../postgres/queries/getMeetingTemplatesByI
import getTemplateRefsByIds from '../postgres/queries/getTemplateRefsByIds'
import {getUsersByIds} from '../postgres/queries/getUsersByIds'
import {
selectMeetingSettings,
selectOrganizations,
selectRetroReflections,
selectSuggestedAction,
Expand Down Expand Up @@ -85,3 +86,7 @@ export const templateDimensions = primaryKeyLoaderMaker((ids: readonly string[])
export const suggestedActions = primaryKeyLoaderMaker((ids: readonly string[]) => {
return selectSuggestedAction().where('id', 'in', ids).execute()
})

export const _PGmeetingSettings = primaryKeyLoaderMaker((ids: readonly string[]) => {
return selectMeetingSettings().where('id', 'in', ids).execute()
})
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import RethinkPrimaryKeyLoaderMaker from './RethinkPrimaryKeyLoaderMaker'
* all rethink dataloader types which also must exist in {@link rethinkDriver/RethinkSchema}
*/
export const agendaItems = new RethinkPrimaryKeyLoaderMaker('AgendaItem')
export const atlassianAuths = new RethinkPrimaryKeyLoaderMaker('AtlassianAuth')
export const comments = new RethinkPrimaryKeyLoaderMaker('Comment')
export const reflectPrompts = new RethinkPrimaryKeyLoaderMaker('ReflectPrompt')
export const massInvitations = new RethinkPrimaryKeyLoaderMaker('MassInvitation')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export default async function createTeamAndLeader(
const organization = await dataLoader.get('organizations').loadNonNull(orgId)
const {tier, trialStartDate} = organization
const verifiedTeam = new Team({...newTeam, createdBy: userId, tier, trialStartDate})

const meetingSettings = [
new MeetingSettingsRetrospective({teamId}),
new MeetingSettingsAction({teamId}),
Expand Down Expand Up @@ -77,6 +78,11 @@ export default async function createTeamAndLeader(
.values(suggestedAction)
.onConflict((oc) => oc.columns(['userId', 'type']).doNothing())
)
.with('MeetingSettingsInsert', (qc) =>
qc
.insertInto('MeetingSettings')
.values(meetingSettings.map((s) => ({...s, jiraSearchQueries: null})))
)
.insertInto('TimelineEvent')
.values(timelineEvent)
.execute(),
Expand Down
15 changes: 9 additions & 6 deletions packages/server/graphql/mutations/removePokerTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,9 @@ const removePokerTemplate = {
const {teamId} = template
const [templates, settings] = await Promise.all([
dataLoader.get('meetingTemplatesByType').load({meetingType: 'poker', teamId}),
r
.table('MeetingSettings')
.getAll(teamId, {index: 'teamId'})
.filter({meetingType: 'poker'})
.nth(0)
.run() as unknown as MeetingSettingsPoker
dataLoader
.get('meetingSettingsByType')
.load({meetingType: 'poker', teamId}) as any as MeetingSettingsPoker
])

// RESOLUTION
Expand All @@ -66,13 +63,19 @@ const removePokerTemplate = {
if (settings.selectedTemplateId === templateId) {
const nextTemplate = templates.find((template) => template.id !== templateId)
const nextTemplateId = nextTemplate?.id ?? SprintPokerDefaults.DEFAULT_TEMPLATE_ID
await getKysely()
.updateTable('MeetingSettings')
.set({selectedTemplateId: nextTemplateId})
.where('id', '=', settingsId)
.execute()
await r
.table('MeetingSettings')
.get(settingsId)
.update({
selectedTemplateId: nextTemplateId
})
.run()
dataLoader.clearAll('meetingSettings')
}

const data = {templateId, settingsId}
Expand Down
16 changes: 10 additions & 6 deletions packages/server/graphql/mutations/removeReflectTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql'
import {SubscriptionChannel} from 'parabol-client/types/constEnums'
import getRethink from '../../database/rethinkDriver'
import MeetingSettingsRetrospective from '../../database/types/MeetingSettingsRetrospective'
import getKysely from '../../postgres/getKysely'
import removeMeetingTemplate from '../../postgres/queries/removeMeetingTemplate'
import {getUserId, isTeamMember} from '../../utils/authorization'
import publish from '../../utils/publish'
Expand Down Expand Up @@ -41,12 +42,9 @@ const removeReflectTemplate = {
const {teamId} = template
const [templates, settings] = await Promise.all([
dataLoader.get('meetingTemplatesByType').load({meetingType: 'retrospective', teamId}),
r
.table('MeetingSettings')
.getAll(teamId, {index: 'teamId'})
.filter({meetingType: 'retrospective'})
.nth(0)
.run() as unknown as MeetingSettingsRetrospective
dataLoader
.get('meetingSettingsByType')
.load({meetingType: 'retrospective', teamId}) as any as MeetingSettingsRetrospective
])

// RESOLUTION
Expand All @@ -69,13 +67,19 @@ const removeReflectTemplate = {
if (settings.selectedTemplateId === templateId) {
const nextTemplate = templates.find((template) => template.id !== templateId)
const nextTemplateId = nextTemplate?.id ?? 'workingStuckTemplate'
await getKysely()
.updateTable('MeetingSettings')
.set({selectedTemplateId: nextTemplateId})
.where('id', '=', settingsId)
.execute()
await r
.table('MeetingSettings')
.get(settingsId)
.update({
selectedTemplateId: nextTemplateId
})
.run()
dataLoader.clearAll('meetingSettings')
}

const data = {templateId, settingsId}
Expand Down
9 changes: 8 additions & 1 deletion packages/server/graphql/mutations/selectTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql'
import {SubscriptionChannel} from 'parabol-client/types/constEnums'
import getRethink from '../../database/rethinkDriver'
import MeetingTemplate from '../../database/types/MeetingTemplate'
import getKysely from '../../postgres/getKysely'
import {Logger} from '../../utils/Logger'
import {getUserId, isTeamMember} from '../../utils/authorization'
import publish from '../../utils/publish'
Expand Down Expand Up @@ -67,7 +68,13 @@ const selectTemplate = {
)('changes')(0)('old_val')('id')
.default(null)
.run()

await getKysely()
.updateTable('MeetingSettings')
.set({selectedTemplateId})
.where('teamId', '=', teamId)
.where('meetingType', '=', template.type)
.returning('id')
.executeTakeFirst()
// No need to check if a non-null 'meetingSettingsId' was returned - the Activity Library client
// will always attempt to update the template, even if it's already selected, and we don't need
// to return a 'meetingSettingsId' if no updates took place.
Expand Down
10 changes: 10 additions & 0 deletions packages/server/graphql/mutations/updateRetroMaxVotes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import mode from 'parabol-client/utils/mode'
import getRethink from '../../database/rethinkDriver'
import {RValue} from '../../database/stricterR'
import MeetingRetrospective from '../../database/types/MeetingRetrospective'
import getKysely from '../../postgres/getKysely'
import {getUserId, isTeamMember} from '../../utils/authorization'
import publish from '../../utils/publish'
import standardError from '../../utils/standardError'
Expand Down Expand Up @@ -136,6 +137,15 @@ const updateRetroMaxVotes = {

// RESOLUTION
await Promise.all([
getKysely()
.updateTable('MeetingSettings')
.set({
totalVotes,
maxVotesPerGroup
})
.where('teamId', '=', teamId)
.where('meetingType', '=', 'retrospective')
.execute(),
r
.table('MeetingSettings')
.getAll(teamId, {index: 'teamId'})
Expand Down
49 changes: 43 additions & 6 deletions packages/server/graphql/public/mutations/setMeetingSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums'
import {isNotNull} from 'parabol-client/utils/predicates'
import getRethink from '../../../database/rethinkDriver'
import {RValue} from '../../../database/stricterR'
import {analytics, MeetingSettings} from '../../../utils/analytics/analytics'
import getKysely from '../../../postgres/getKysely'
import {MeetingSettings} from '../../../postgres/types'
import {
analytics,
MeetingSettings as MeetingSettingsAnalytics
} from '../../../utils/analytics/analytics'
import {getUserId} from '../../../utils/authorization'
import publish from '../../../utils/publish'
import standardError from '../../../utils/standardError'
import {MutationResolvers} from '../resolverTypes'
import {MutationResolvers, NewMeetingPhaseTypeEnum} from '../resolverTypes'

const setMeetingSettings: MutationResolvers['setMeetingSettings'] = async (
_source,
Expand All @@ -19,13 +24,13 @@ const setMeetingSettings: MutationResolvers['setMeetingSettings'] = async (

// AUTH
const viewerId = getUserId(authToken)
const settings = await r.table('MeetingSettings').get(settingsId).run()
const settings = (await dataLoader.get('meetingSettings').load(settingsId)) as MeetingSettings
if (!settings) {
return standardError(new Error('Settings not found'), {userId: viewerId})
}

// RESOLUTION
const {teamId, meetingType} = settings
const {teamId, meetingType, phaseTypes} = settings
const [team, viewer] = await Promise.all([
dataLoader.get('teams').loadNonNull(teamId),
dataLoader.get('users').loadNonNull(viewerId)
Expand All @@ -34,7 +39,27 @@ const setMeetingSettings: MutationResolvers['setMeetingSettings'] = async (
const {featureFlags} = organization
const hasTranscriptFlag = featureFlags?.includes('zoomTranscription')

const meetingSettings = {} as MeetingSettings
const meetingSettings = {} as MeetingSettingsAnalytics
const firstPhases: NewMeetingPhaseTypeEnum[] = []
if (checkinEnabled || (checkinEnabled !== false && phaseTypes.includes('checkin'))) {
firstPhases.push('checkin')
}
if (teamHealthEnabled || (teamHealthEnabled !== false && phaseTypes.includes('TEAM_HEALTH'))) {
firstPhases.push('TEAM_HEALTH')
}
const nextSettings = {
phaseTypes: [
...firstPhases,
...phaseTypes.filter((phase) => phase !== 'checkin' && phase !== 'TEAM_HEALTH')
],
disableAnonymity: isNotNull(disableAnonymity) ? disableAnonymity : settings.disableAnonymity,
videoMeetingURL: hasTranscriptFlag
? isNotNull(videoMeetingURL)
? videoMeetingURL
: settings.videoMeetingURL
: null
}

await r
.table('MeetingSettings')
.get(settingsId)
Expand Down Expand Up @@ -74,8 +99,20 @@ const setMeetingSettings: MutationResolvers['setMeetingSettings'] = async (
})
.run()

await getKysely()
.updateTable('MeetingSettings')
.set(nextSettings)
.where('id', '=', settings.id)
.execute()
dataLoader.clearAll('meetingSettings')

const data = {settingsId}
analytics.meetingSettingsChanged(viewer, teamId, meetingType, meetingSettings)
analytics.meetingSettingsChanged(viewer, teamId, meetingType, {
disableAnonymity: nextSettings.disableAnonymity,
videoMeetingURL: nextSettings.videoMeetingURL,
hasIcebreaker: nextSettings.phaseTypes.includes('checkin'),
hasTeamHealth: nextSettings.phaseTypes.includes('TEAM_HEALTH')
})
publish(SubscriptionChannel.TEAM, teamId, 'SetMeetingSettingsPayload', data, subOptions)
return data
}
Expand Down
Loading
Loading