diff --git a/codegen.json b/codegen.json index e6b199fe13a..beca57c4d23 100644 --- a/codegen.json +++ b/codegen.json @@ -21,7 +21,7 @@ "LoginsPayload": "./types/LoginsPayload#LoginsPayloadSource", "MeetingTemplate": "../../database/types/MeetingTemplate#default as IMeetingTemplate", "NewMeeting": "../../postgres/types/Meeting#AnyMeeting", - "Organization": "../public/types/Organization#OrganizationSource", + "Organization": "../../postgres/types/index#Organization as OrganizationDB", "PingableServices": "./types/PingableServices#PingableServicesSource", "ProcessRecurrenceSuccess": "./types/ProcessRecurrenceSuccess#ProcessRecurrenceSuccessSource", "RemoveAuthIdentitySuccess": "./types/RemoveAuthIdentitySuccess#RemoveAuthIdentitySuccessSource", @@ -30,7 +30,7 @@ "SignupsPayload": "./types/SignupsPayload#SignupsPayloadSource", "StartTrialSuccess": "./types/StartTrialSuccess#StartTrialSuccessSource", "StripeFailPaymentPayload": "./mutations/stripeFailPayment#StripeFailPaymentPayloadSource", - "Team": "../public/types/Team#TeamSource", + "Team": "../../postgres/types/index#Team as TeamDB", "UpdateOrgFeatureFlagSuccess": "./types/UpdateOrgFeatureFlagSuccess#UpdateOrgFeatureFlagSuccessSource", "UpgradeToTeamTierSuccess": "./mutations/upgradeToTeamTier#UpgradeToTeamTierSuccessSource", "User": "../../postgres/types/IUser#default as IUser", @@ -116,7 +116,7 @@ "NotifyResponseReplied": "../../database/types/NotifyResponseReplied#default as NotifyResponseRepliedDB", "NotifyTaskInvolves": "../../database/types/NotificationTaskInvolves#default", "NotifyTeamArchived": "../../database/types/NotificationTeamArchived#default", - "Organization": "./types/Organization#OrganizationSource", + "Organization": "../../postgres/types/index#Organization as OrganizationDB", "TemplateScaleValue": "./types/TemplateScaleValue#TemplateScaleValueSource as TemplateScaleValueSourceDB", "SuggestedAction": "../../postgres/types/index#SuggestedAction as SuggestedActionDB", "TemplateScale": "../../postgres/types/index#TemplateScale as TemplateScaleDB", @@ -137,7 +137,7 @@ "RemoveTeamMemberIntegrationAuthSuccess": "./types/RemoveTeamMemberIntegrationAuthPayload#RemoveTeamMemberIntegrationAuthSuccessSource", "RequestToJoinDomainSuccess": "./types/RequestToJoinDomainSuccess#RequestToJoinDomainSuccessSource", "ResetReflectionGroupsSuccess": "./types/ResetReflectionGroupsSuccess#ResetReflectionGroupsSuccessSource", - "RetroReflection": "./types/RetroReflection#RetroReflectionSource", + "RetroReflection": "../../postgres/types/index#RetroReflection as RetroReflectionDB", "RetroReflectionGroup": "./types/RetroReflectionGroup#RetroReflectionGroupSource", "RetrospectiveMeeting": "../../database/types/MeetingRetrospective#default", "RetrospectiveMeetingMember": "../../database/types/RetroMeetingMember#default", @@ -152,7 +152,7 @@ "StartTeamPromptSuccess": "./types/StartTeamPromptSuccess#StartTeamPromptSuccessSource", "StripeFailPaymentPayload": "./types/StripeFailPaymentPayload#StripeFailPaymentPayloadSource", "Task": "../../database/types/Task#default", - "Team": "./types/Team#TeamSource", + "Team": "../../postgres/types/index#Team as TeamDB", "TeamPromptMeetingSettings": "../../database/types/MeetingSettingsTeamPrompt#default as MeetingSettingsTeamPromptDB", "TeamHealthPhase": "./types/TeamHealthPhase#TeamHealthPhaseSource", "TeamHealthStage": "./types/TeamHealthStage#TeamHealthStageSource", @@ -164,7 +164,7 @@ "TeamMemberIntegrations": "./types/TeamMemberIntegrations#TeamMemberIntegrationsSource", "TeamPromptMeeting": "../../database/types/MeetingTeamPrompt#default as MeetingTeamPromptDB", "TeamPromptMeetingMember": "../../database/types/TeamPromptMeetingMember#default as TeamPromptMeetingMemberDB", - "TeamPromptResponse": "../../postgres/queries/getTeamPromptResponsesByIds#TeamPromptResponse", + "TeamPromptResponse": "../../postgres/types/index#TeamPromptResponse as TeamPromptResponseDB", "TemplateDimension": "../../postgres/types/index#TemplateDimension as TemplateDimensionDB", "TimelineEventTeamPromptComplete": "./types/TimelineEventTeamPromptComplete#TimelineEventTeamPromptCompleteSource", "ToggleFavoriteTemplateSuccess": "./types/ToggleFavoriteTemplateSuccess#ToggleFavoriteTemplateSuccessSource", diff --git a/packages/client/types/generics.ts b/packages/client/types/generics.ts index 4d8d089262b..755054dfa68 100644 --- a/packages/client/types/generics.ts +++ b/packages/client/types/generics.ts @@ -1,4 +1,3 @@ -import type {SelectQueryBuilder} from 'kysely' import type {MutableRefObject} from 'react' import type {RecordProxy} from 'relay-runtime' @@ -109,6 +108,3 @@ declare global { findLastIndex(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): number } } - -export type ExtractTypeFromQueryBuilderSelect any> = - ReturnType extends SelectQueryBuilder ? X : never diff --git a/packages/server/billing/helpers/handleEnterpriseOrgQuantityChanges.ts b/packages/server/billing/helpers/handleEnterpriseOrgQuantityChanges.ts index e9cb414b876..30b991ed905 100644 --- a/packages/server/billing/helpers/handleEnterpriseOrgQuantityChanges.ts +++ b/packages/server/billing/helpers/handleEnterpriseOrgQuantityChanges.ts @@ -1,10 +1,10 @@ import {DataLoaderWorker} from '../../graphql/graphql' -import {OrganizationSource} from '../../graphql/public/types/Organization' +import {Organization} from '../../postgres/types' import {analytics} from '../../utils/analytics/analytics' import {getStripeManager} from '../../utils/stripe' const sendEnterpriseOverageEvent = async ( - organization: OrganizationSource, + organization: Organization, dataLoader: DataLoaderWorker ) => { const manager = getStripeManager() @@ -31,7 +31,7 @@ const sendEnterpriseOverageEvent = async ( } const handleEnterpriseOrgQuantityChanges = async ( - paidOrgs: OrganizationSource[], + paidOrgs: Organization[], dataLoader: DataLoaderWorker ) => { const enterpriseOrgs = paidOrgs.filter((org) => org.tier === 'enterprise') diff --git a/packages/server/billing/helpers/handleTeamOrgQuantityChanges.ts b/packages/server/billing/helpers/handleTeamOrgQuantityChanges.ts index 2ae1f729a64..302a7f19a18 100644 --- a/packages/server/billing/helpers/handleTeamOrgQuantityChanges.ts +++ b/packages/server/billing/helpers/handleTeamOrgQuantityChanges.ts @@ -1,7 +1,7 @@ -import {OrganizationSource} from '../../graphql/public/types/Organization' +import {Organization} from '../../postgres/types' import updateSubscriptionQuantity from './updateSubscriptionQuantity' -const handleTeamOrgQuantityChanges = async (paidOrgs: OrganizationSource[]) => { +const handleTeamOrgQuantityChanges = async (paidOrgs: Organization[]) => { const teamOrgs = paidOrgs.filter((org) => org.tier === 'team') if (teamOrgs.length === 0) return diff --git a/packages/server/billing/helpers/teamLimitsCheck.ts b/packages/server/billing/helpers/teamLimitsCheck.ts index 849edb8420e..c9c59983cf1 100644 --- a/packages/server/billing/helpers/teamLimitsCheck.ts +++ b/packages/server/billing/helpers/teamLimitsCheck.ts @@ -8,12 +8,12 @@ import NotificationTeamsLimitExceeded from '../../database/types/NotificationTea import scheduleTeamLimitsJobs from '../../database/types/scheduleTeamLimitsJobs' import {DataLoaderWorker} from '../../graphql/graphql' import publishNotification from '../../graphql/public/mutations/helpers/publishNotification' -import {OrganizationSource} from '../../graphql/public/types/Organization' import getActiveTeamCountByTeamIds from '../../graphql/public/types/helpers/getActiveTeamCountByTeamIds' import {getFeatureTier} from '../../graphql/types/helpers/getFeatureTier' import {domainHasActiveDeals} from '../../hubSpot/hubSpotApi' import getKysely from '../../postgres/getKysely' import getTeamIdsByOrgIds from '../../postgres/queries/getTeamIdsByOrgIds' +import {Organization} from '../../postgres/types' import {getBillingLeadersByOrgId} from '../../utils/getBillingLeadersByOrgId' import sendToSentry from '../../utils/sendToSentry' import removeTeamsLimitObjects from './removeTeamsLimitObjects' @@ -36,7 +36,7 @@ const enableUsageStats = async (userIds: string[], orgId: string) => { } const sendWebsiteNotifications = async ( - organization: OrganizationSource, + organization: Organization, userIds: string[], dataLoader: DataLoaderWorker ) => { diff --git a/packages/server/database/types/Reactable.ts b/packages/server/database/types/Reactable.ts deleted file mode 100644 index 2cfd490359c..00000000000 --- a/packages/server/database/types/Reactable.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {RetroReflectionSource} from '../../graphql/public/types/RetroReflection' -import {TeamPromptResponse} from '../../postgres/queries/getTeamPromptResponsesByIds' -import Comment from './Comment' - -export type ReactableEnum = 'COMMENT' | 'REFLECTION' | 'RESPONSE' -export type Reactable = RetroReflectionSource | Comment | TeamPromptResponse diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index aca91715545..38e013297ef 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -6,11 +6,10 @@ import getRethink, {RethinkSchema} from '../database/rethinkDriver' import {RDatum} from '../database/stricterR' import MeetingSettingsTeamPrompt from '../database/types/MeetingSettingsTeamPrompt' import MeetingTemplate from '../database/types/MeetingTemplate' -import {Reactable, ReactableEnum} from '../database/types/Reactable' import Task, {TaskStatusEnum} from '../database/types/Task' import getFileStoreManager from '../fileStorage/getFileStoreManager' +import {ReactableEnum} from '../graphql/public/resolverTypes' import {SAMLSource} from '../graphql/public/types/SAML' -import {TeamSource} from '../graphql/public/types/Team' import getKysely from '../postgres/getKysely' import {TeamMeetingTemplate} from '../postgres/pg.d' import {IGetLatestTaskEstimatesQueryResult} from '../postgres/queries/generated/getLatestTaskEstimatesQuery' @@ -27,7 +26,8 @@ import getLatestTaskEstimates from '../postgres/queries/getLatestTaskEstimates' import getMeetingTaskEstimates, { MeetingTaskEstimatesResult } from '../postgres/queries/getMeetingTaskEstimates' -import {OrganizationUser} from '../postgres/types' +import {selectTeams} from '../postgres/select' +import {OrganizationUser, Team} from '../postgres/types' import {AnyMeeting, MeetingTypeEnum} from '../postgres/types/Meeting' import {Logger} from '../utils/Logger' import getRedis from '../utils/getRedis' @@ -36,7 +36,6 @@ import NullableDataLoader from './NullableDataLoader' import RootDataLoader, {RegisterDependsOn} from './RootDataLoader' import normalizeArrayResults from './normalizeArrayResults' import normalizeResults from './normalizeResults' -import {selectTeams} from './primaryKeyLoaderMakers' export interface MeetingSettingsKey { teamId: string @@ -49,7 +48,7 @@ export interface MeetingTemplateKey { } export interface ReactablesKey { - id: string + id: string | number type: ReactableEnum } @@ -64,12 +63,6 @@ export interface UserTasksKey { includeUnassigned?: boolean } -const reactableLoaders = [ - {type: 'COMMENT', loader: 'comments'}, - {type: 'REFLECTION', loader: 'retroReflections'}, - {type: 'RESPONSE', loader: 'teamPromptResponses'} -] as const - export const serializeUserTasksKey = (key: UserTasksKey) => { const {userIds, teamIds, first, after, archived, statusFilters, filterQuery} = key const parts = [ @@ -146,28 +139,6 @@ export const meetingTaskEstimates = (parent: RootDataLoader) => { ) } -export const reactables = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { - dependsOn(reactableLoaders.map((a) => a.loader)) - return new DataLoader( - async (keys) => { - const reactableResults = (await Promise.all( - reactableLoaders.map(async (val) => { - const ids = keys.filter((key) => key.type === val.type).map(({id}) => id) - return parent.get(val.loader).loadMany(ids) - }) - )) as Reactable[][] - const reactables = reactableResults.flat() - const keyIds = keys.map(({id}) => id) - const ret = normalizeResults(keyIds, reactables) - return ret - }, - { - ...parent.dataLoaderOptions, - cacheKeyFn: (key) => `${key.id}:${key.type}` - } - ) -} - export const userTasks = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { dependsOn('tasks') return new DataLoader( @@ -779,7 +750,7 @@ export const isOrgVerified = (parent: RootDataLoader, dependsOn: RegisterDepends export const autoJoinTeamsByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { dependsOn('teams') - return new DataLoader( + return new DataLoader( async (orgIds) => { const verificationResults = await parent.get('isOrgVerified').loadMany(orgIds) const verifiedOrgIds = orgIds.filter((_, index) => verificationResults[index]) @@ -873,7 +844,7 @@ export const fileStoreAsset = (parent: RootDataLoader) => { } export const meetingCount = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { - dependsOn('selectTeams') + dependsOn('newMeetings') return new DataLoader<{teamId: string; meetingType: MeetingTypeEnum}, number, string>( async (keys) => { const r = await getRethink() diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 32ada6428f3..98146663a5d 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -1,12 +1,15 @@ import getKysely from '../postgres/getKysely' +import {getTeamPromptResponsesByMeetingIds} from '../postgres/queries/getTeamPromptResponsesByMeetingIds' import { + selectOrganizations, + selectRetroReflections, selectSuggestedAction, + selectTeams, selectTemplateDimension, selectTemplateScale, selectTimelineEvent } from '../postgres/select' import {foreignKeyLoaderMaker} from './foreignKeyLoaderMaker' -import {selectOrganizations, selectRetroReflections, selectTeams} from './primaryKeyLoaderMakers' export const teamsByOrgIds = foreignKeyLoaderMaker('teams', 'orgId', (orgIds) => selectTeams().where('orgId', 'in', orgIds).where('isArchived', '=', false).execute() @@ -161,3 +164,9 @@ export const suggestedActionsByUserId = foreignKeyLoaderMaker( return selectSuggestedAction().where('userId', 'in', userIds).execute() } ) + +export const teamPromptResponsesByMeetingId = foreignKeyLoaderMaker( + 'teamPromptResponses', + 'meetingId', + getTeamPromptResponsesByMeetingIds +) diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 66389f348a5..155e7f34489 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -3,11 +3,14 @@ import {getDiscussionsByIds} from '../postgres/queries/getDiscussionsByIds' import {getDomainJoinRequestsByIds} from '../postgres/queries/getDomainJoinRequestsByIds' import getMeetingSeriesByIds from '../postgres/queries/getMeetingSeriesByIds' import getMeetingTemplatesByIds from '../postgres/queries/getMeetingTemplatesByIds' -import {getTeamPromptResponsesByIds} from '../postgres/queries/getTeamPromptResponsesByIds' import getTemplateRefsByIds from '../postgres/queries/getTemplateRefsByIds' import {getUsersByIds} from '../postgres/queries/getUsersByIds' import { + selectOrganizations, + selectRetroReflections, selectSuggestedAction, + selectTeamPromptResponses, + selectTeams, selectTemplateDimension, selectTemplateScale, selectTemplateScaleRef, @@ -17,44 +20,6 @@ import {primaryKeyLoaderMaker} from './primaryKeyLoaderMaker' export const users = primaryKeyLoaderMaker(getUsersByIds) -export const selectTeams = () => - getKysely() - .selectFrom('Team') - .select([ - 'autoJoin', - 'createdAt', - 'createdBy', - 'id', - 'insightsUpdatedAt', - 'isArchived', - 'isOnboardTeam', - 'isPaid', - 'kudosEmojiUnicode', - 'lastMeetingType', - 'lockMessageHTML', - 'meetingEngagement', - 'mostUsedEmojis', - 'name', - 'orgId', - 'qualAIMeetingsCount', - 'tier', - 'topRetroTemplates', - 'trialStartDate', - 'updatedAt' - ]) - .select(({fn}) => [ - fn< - { - dimensionName: string - cloudId: string - projectKey: string - issueKey: string - fieldName: string - fieldType: string - fieldId: string - }[] - >('to_json', ['jiraDimensionFields']).as('jiraDimensionFields') - ]) export const teams = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectTeams().where('id', 'in', ids).execute() }) @@ -63,7 +28,9 @@ export const templateRefs = primaryKeyLoaderMaker(getTemplateRefsByIds) export const templateScaleRefs = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectTemplateScaleRef().where('id', 'in', ids).execute() }) -export const teamPromptResponses = primaryKeyLoaderMaker(getTeamPromptResponsesByIds) +export const teamPromptResponses = primaryKeyLoaderMaker(async (ids: readonly number[]) => { + return selectTeamPromptResponses().where('id', 'in', ids).execute() +}) export const meetingSeries = primaryKeyLoaderMaker(getMeetingSeriesByIds) export const meetingTemplates = primaryKeyLoaderMaker(getMeetingTemplatesByIds) export const domainJoinRequests = primaryKeyLoaderMaker(getDomainJoinRequestsByIds) @@ -76,28 +43,6 @@ export const retroReflectionGroups = primaryKeyLoaderMaker((ids: readonly string return getKysely().selectFrom('RetroReflectionGroup').selectAll().where('id', 'in', ids).execute() }) -export const selectRetroReflections = () => - getKysely() - .selectFrom('RetroReflection') - .select([ - 'id', - 'content', - 'createdAt', - 'creatorId', - 'isActive', - 'meetingId', - 'plaintextContent', - 'promptId', - 'reflectionGroupId', - 'sentimentScore', - 'sortOrder', - 'updatedAt' - ]) - .select(({fn}) => [ - fn<{lemma: string; salience: number; name: string}[]>('to_json', ['entities']).as('entities'), - fn<{id: string; userId: string}[]>('to_json', ['reactjis']).as('reactjis') - ]) - export const retroReflections = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectRetroReflections().where('id', 'in', ids).execute() }) @@ -106,36 +51,6 @@ export const timelineEvents = primaryKeyLoaderMaker((ids: readonly string[]) => return selectTimelineEvent().where('id', 'in', ids).execute() }) -export const selectOrganizations = () => - getKysely() - .selectFrom('Organization') - .select([ - 'id', - 'activeDomain', - 'isActiveDomainTouched', - 'createdAt', - 'name', - 'payLaterClickCount', - 'periodEnd', - 'periodStart', - 'picture', - 'showConversionModal', - 'stripeId', - 'stripeSubscriptionId', - 'upcomingInvoiceEmailSentAt', - 'tier', - 'tierLimitExceededAt', - 'trialStartDate', - 'scheduledLockAt', - 'lockedAt', - 'updatedAt', - 'featureFlags' - ]) - .select(({fn}) => [ - fn<{brand: string; expiry: string; last4: number} | null>('to_json', ['creditCard']).as( - 'creditCard' - ) - ]) export const organizations = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectOrganizations().where('id', 'in', ids).execute() }) diff --git a/packages/server/graphql/mutations/helpers/calculateEngagement.ts b/packages/server/graphql/mutations/helpers/calculateEngagement.ts index 3b79f6dcb51..e4faf3d4d25 100644 --- a/packages/server/graphql/mutations/helpers/calculateEngagement.ts +++ b/packages/server/graphql/mutations/helpers/calculateEngagement.ts @@ -1,7 +1,6 @@ import TeamMemberId from '../../../../client/shared/gqlIds/TeamMemberId' import EstimatePhase from '../../../database/types/EstimatePhase' import Meeting from '../../../database/types/Meeting' -import {getTeamPromptResponsesByMeetingId} from '../../../postgres/queries/getTeamPromptResponsesByMeetingIds' import getPhase from '../../../utils/getPhase' import {DataLoaderWorker} from '../../graphql' import isValid from '../../isValid' @@ -57,7 +56,7 @@ const calculateEngagement = async (meeting: Meeting, dataLoader: DataLoaderWorke // Team prompt responses if (getPhase(phases, 'RESPONSES')) { - const responses = await getTeamPromptResponsesByMeetingId(meetingId) + const responses = await dataLoader.get('teamPromptResponsesByMeetingId').load(meetingId) responses.forEach(({userId, reactjis}) => { passiveMembers.delete(userId) reactjis.forEach(({userId}) => { diff --git a/packages/server/graphql/mutations/helpers/canAccessAISummary.ts b/packages/server/graphql/mutations/helpers/canAccessAISummary.ts index 4ba56c793fc..e5a36e0cc1f 100644 --- a/packages/server/graphql/mutations/helpers/canAccessAISummary.ts +++ b/packages/server/graphql/mutations/helpers/canAccessAISummary.ts @@ -1,10 +1,10 @@ import {Threshold} from 'parabol-client/types/constEnums' +import {Team} from '../../../postgres/types' import {DataLoaderWorker} from '../../graphql' -import {TeamSource} from '../../public/types/Team' import {getFeatureTier} from '../../types/helpers/getFeatureTier' const canAccessAISummary = async ( - team: TeamSource, + team: Team, featureFlags: string[], dataLoader: DataLoaderWorker, meetingType: 'standup' | 'retrospective' diff --git a/packages/server/graphql/mutations/helpers/generateGroups.ts b/packages/server/graphql/mutations/helpers/generateGroups.ts index d08a1922f8f..a37536bb97c 100644 --- a/packages/server/graphql/mutations/helpers/generateGroups.ts +++ b/packages/server/graphql/mutations/helpers/generateGroups.ts @@ -1,15 +1,15 @@ import {SubscriptionChannel} from '../../../../client/types/constEnums' import getRethink from '../../../database/rethinkDriver' import {AutogroupReflectionGroupType} from '../../../database/types/MeetingRetrospective' +import {RetroReflection} from '../../../postgres/types' import {Logger} from '../../../utils/Logger' import OpenAIServerManager from '../../../utils/OpenAIServerManager' import {analytics} from '../../../utils/analytics/analytics' import publish from '../../../utils/publish' import {DataLoaderWorker} from '../../graphql' -import {RetroReflectionSource} from '../../public/types/RetroReflection' const generateGroups = async ( - reflections: RetroReflectionSource[], + reflections: RetroReflection[], teamId: string, dataLoader: DataLoaderWorker ) => { diff --git a/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts index 441a6801c24..4e0701bfc45 100644 --- a/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts @@ -6,13 +6,13 @@ import appOrigin from '../../../../appOrigin' import Meeting from '../../../../database/types/Meeting' import {SlackNotificationEventEnum as EventEnum} from '../../../../database/types/SlackNotification' import {IntegrationProviderMSTeams as IIntegrationProviderMSTeams} from '../../../../postgres/queries/getIntegrationProvidersByIds' +import {Team} from '../../../../postgres/types' import IUser from '../../../../postgres/types/IUser' import {MeetingTypeEnum} from '../../../../postgres/types/Meeting' import MSTeamsServerManager from '../../../../utils/MSTeamsServerManager' import {analytics} from '../../../../utils/analytics/analytics' import sendToSentry from '../../../../utils/sendToSentry' import {DataLoaderWorker} from '../../../graphql' -import {TeamSource} from '../../../public/types/Team' import {NotificationIntegrationHelper} from './NotificationIntegrationHelper' import {createNotifier} from './Notifier' import getSummaryText from './getSummaryText' @@ -361,7 +361,7 @@ function GenerateACMeetingTitle(meetingTitle: string) { return titleTextBlock } -function GenerateACMeetingAndTeamsDetails(team: TeamSource, meeting: Meeting) { +function GenerateACMeetingAndTeamsDetails(team: Team, meeting: Meeting) { const meetingDetailColumnSet = new AdaptiveCards.ColumnSet() const teamDetailColumn = new AdaptiveCards.Column() teamDetailColumn.width = 'stretch' diff --git a/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts index 5bbe302896d..61688c3160a 100644 --- a/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts @@ -7,6 +7,7 @@ import appOrigin from '../../../../appOrigin' import Meeting from '../../../../database/types/Meeting' import {SlackNotificationEventEnum as EventEnum} from '../../../../database/types/SlackNotification' import {IntegrationProviderMattermost as IIntegrationProviderMattermost} from '../../../../postgres/queries/getIntegrationProvidersByIds' +import {Team} from '../../../../postgres/types' import IUser from '../../../../postgres/types/IUser' import {MeetingTypeEnum} from '../../../../postgres/types/Meeting' import MattermostServerManager from '../../../../utils/MattermostServerManager' @@ -14,7 +15,6 @@ import {analytics} from '../../../../utils/analytics/analytics' import {toEpochSeconds} from '../../../../utils/epochTime' import sendToSentry from '../../../../utils/sendToSentry' import {DataLoaderWorker} from '../../../graphql' -import {TeamSource} from '../../../public/types/Team' import {NotificationIntegrationHelper} from './NotificationIntegrationHelper' import {createNotifier} from './Notifier' import getSummaryText from './getSummaryText' @@ -94,7 +94,7 @@ const makeEndMeetingButtons = (meeting: Meeting) => { type MattermostNotificationAuth = IntegrationProviderMattermost & {userId: string} const makeTeamPromptStartMeetingNotification = ( - team: TeamSource, + team: Team, meeting: Meeting, meetingUrl: string ) => { @@ -120,11 +120,7 @@ const makeTeamPromptStartMeetingNotification = ( ] } -const makeGenericStartMeetingNotification = ( - team: TeamSource, - meeting: Meeting, - meetingUrl: string -) => { +const makeGenericStartMeetingNotification = (team: Team, meeting: Meeting, meetingUrl: string) => { return [ makeFieldsAttachment( [ @@ -154,11 +150,7 @@ const makeGenericStartMeetingNotification = ( const makeStartMeetingNotificationLookup: Record< MeetingTypeEnum, - ( - team: TeamSource, - meeting: Meeting, - meetingUrl: string - ) => ReturnType[] + (team: Team, meeting: Meeting, meetingUrl: string) => ReturnType[] > = { teamPrompt: makeTeamPromptStartMeetingNotification, action: makeGenericStartMeetingNotification, diff --git a/packages/server/graphql/mutations/helpers/notifications/NotificationIntegrationHelper.ts b/packages/server/graphql/mutations/helpers/notifications/NotificationIntegrationHelper.ts index 7dd24c4c78c..e1bd2c9287c 100644 --- a/packages/server/graphql/mutations/helpers/notifications/NotificationIntegrationHelper.ts +++ b/packages/server/graphql/mutations/helpers/notifications/NotificationIntegrationHelper.ts @@ -1,8 +1,6 @@ import Meeting from '../../../../database/types/Meeting' -import {TeamPromptResponse} from '../../../../postgres/queries/getTeamPromptResponsesByIds' +import {Team, TeamPromptResponse} from '../../../../postgres/types' import User from '../../../../postgres/types/IUser' -import {TeamSource} from '../../../public/types/Team' - export type NotifyResponse = | 'success' | { @@ -12,25 +10,25 @@ export type NotifyResponse = } export type NotificationIntegration = { - startMeeting(meeting: Meeting, team: TeamSource, user: User): Promise - updateMeeting?(meeting: Meeting, team: TeamSource, user: User): Promise + startMeeting(meeting: Meeting, team: Team, user: User): Promise + updateMeeting?(meeting: Meeting, team: Team, user: User): Promise endMeeting( meeting: Meeting, - team: TeamSource, + team: Team, user: User, standupResponses: {user: User; response: TeamPromptResponse}[] | null ): Promise startTimeLimit( scheduledEndTime: Date, meeting: Meeting, - team: TeamSource, + team: Team, user: User ): Promise - endTimeLimit(meeting: Meeting, team: TeamSource, user: User): Promise + endTimeLimit(meeting: Meeting, team: Team, user: User): Promise integrationUpdated(user: User): Promise standupResponseSubmitted( meeting: Meeting, - team: TeamSource, + team: Team, user: User, response: TeamPromptResponse ): Promise diff --git a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts index 37ff8e356cb..e2e37ad240c 100644 --- a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts @@ -3,6 +3,7 @@ import formatWeekday from 'parabol-client/utils/date/formatWeekday' import makeAppURL from 'parabol-client/utils/makeAppURL' import findStageById from 'parabol-client/utils/meetings/findStageById' import {phaseLabelLookup} from 'parabol-client/utils/meetings/lookups' +import TeamPromptResponseId from '../../../../../client/shared/gqlIds/TeamPromptResponseId' import {ErrorResponse, PostMessageResponse} from '../../../../../client/utils/SlackManager' import appOrigin from '../../../../appOrigin' import getRethink, {RethinkSchema} from '../../../../database/rethinkDriver' @@ -10,8 +11,8 @@ import Meeting from '../../../../database/types/Meeting' import SlackAuth from '../../../../database/types/SlackAuth' import {SlackNotificationEvent} from '../../../../database/types/SlackNotification' import {SlackNotificationAuth} from '../../../../dataloader/integrationAuthLoaders' -import {TeamPromptResponse} from '../../../../postgres/queries/getTeamPromptResponsesByIds' import {getTeamPromptResponsesByMeetingId} from '../../../../postgres/queries/getTeamPromptResponsesByMeetingIds' +import {Team, TeamPromptResponse} from '../../../../postgres/types' import User from '../../../../postgres/types/IUser' import {AnyMeeting, MeetingTypeEnum} from '../../../../postgres/types/Meeting' import SlackServerManager from '../../../../utils/SlackServerManager' @@ -20,7 +21,6 @@ import {toEpochSeconds} from '../../../../utils/epochTime' import sendToSentry from '../../../../utils/sendToSentry' import {convertToMarkdown} from '../../../../utils/tiptap/convertToMarkdown' import {DataLoaderWorker} from '../../../graphql' -import {TeamSource} from '../../../public/types/Team' import {NotificationIntegrationHelper} from './NotificationIntegrationHelper' import {createNotifier} from './Notifier' import getSummaryText from './getSummaryText' @@ -137,12 +137,12 @@ const makeEndMeetingButtons = (meeting: Meeting) => { } } -const createTeamSectionContent = (team: TeamSource) => `*Team:*\n${team.name}` +const createTeamSectionContent = (team: Team) => `*Team:*\n${team.name}` const createMeetingSectionContent = (meeting: Meeting) => `*Meeting:*\n${meeting.name}` const makeTeamPromptStartMeetingNotification = ( - team: TeamSource, + team: Team, meeting: Meeting, meetingUrl: string ): SlackNotification => { @@ -157,7 +157,7 @@ const makeTeamPromptStartMeetingNotification = ( } const makeGenericStartMeetingNotification = ( - team: TeamSource, + team: Team, meeting: Meeting, meetingUrl: string ): SlackNotification => { @@ -173,7 +173,7 @@ const makeGenericStartMeetingNotification = ( const makeStartMeetingNotificationLookup: Record< MeetingTypeEnum, - (team: TeamSource, meeting: Meeting, meetingUrl: string) => SlackNotification + (team: Team, meeting: Meeting, meetingUrl: string) => SlackNotification > = { teamPrompt: makeTeamPromptStartMeetingNotification, action: makeGenericStartMeetingNotification, @@ -184,7 +184,7 @@ const makeStartMeetingNotificationLookup: Record< const addStandupResponsesToThread = async ( res: PostMessageResponse, standupResponses: Array<{user: User; response: TeamPromptResponse}> | null, - team: TeamSource, + team: Team, user: User, meeting: Meeting, notificationChannel: NotificationChannel @@ -213,7 +213,7 @@ const addStandupResponsesToThread = async ( utm_source: 'slack standup summary', utm_medium: 'product', utm_campaign: 'after-meeting', - responseId: response.id + responseId: TeamPromptResponseId.join(response.id) } } const responseUrl = makeAppURL(appOrigin, `meet/${meeting.id}/responses`, options) @@ -269,7 +269,7 @@ const getSlackMessageForNotification = async ( utm_source: 'slack standup notification', utm_medium: 'product', utm_campaign: 'notifications', - responseId + responseId: TeamPromptResponseId.join(responseId) } } @@ -281,7 +281,7 @@ const getSlackMessageForNotification = async ( buttonText: 'See the discussion' } } else if (notification.type === 'RESPONSE_MENTIONED') { - const responseId = notification.responseId + const responseId = TeamPromptResponseId.split(notification.responseId) const response = await dataLoader.get('teamPromptResponses').loadNonNull(responseId) const author = await dataLoader.get('users').loadNonNull(response.userId) const title = `*${author.preferredName}* mentioned you in their response in *${meeting.name}*` @@ -291,7 +291,7 @@ const getSlackMessageForNotification = async ( utm_source: 'slack standup notification', utm_medium: 'product', utm_campaign: 'notifications', - responseId + responseId: notification.responseId } } @@ -511,7 +511,7 @@ export const SlackSingleChannelNotifier: NotificationIntegrationHelper { diff --git a/packages/server/graphql/public/mutations/addReactjiToReactable.ts b/packages/server/graphql/public/mutations/addReactjiToReactable.ts index 598aefef047..b60543c50ad 100644 --- a/packages/server/graphql/public/mutations/addReactjiToReactable.ts +++ b/packages/server/graphql/public/mutations/addReactjiToReactable.ts @@ -6,6 +6,7 @@ import {ValueOf} from '../../../../client/types/generics' import getRethink from '../../../database/rethinkDriver' import {RDatum} from '../../../database/stricterR' import Comment from '../../../database/types/Comment' +import {DataLoaderInstance} from '../../../dataloader/RootDataLoader' import getKysely from '../../../postgres/getKysely' import {analytics} from '../../../utils/analytics/analytics' import {getUserId} from '../../../utils/authorization' @@ -13,14 +14,22 @@ import emojiIds from '../../../utils/emojiIds' import getGroupedReactjis from '../../../utils/getGroupedReactjis' import publish from '../../../utils/publish' import {GQLContext} from '../../graphql' -import getReactableType from '../../types/getReactableType' -import {MutationResolvers} from '../resolverTypes' +import {MutationResolvers, ReactableEnum} from '../resolverTypes' +import {getReactableType} from '../types/Reactable' -const dataLoaderLookup = { - RESPONSE: 'teamPromptResponses', - COMMENT: 'comments', - REFLECTION: 'retroReflections' -} as const +export const getReactable = ( + reactableDBId: string | number, + reactableType: ReactableEnum, + dataLoader: DataLoaderInstance +) => { + if (reactableType === 'RESPONSE') { + return dataLoader.get('teamPromptResponses').load(reactableDBId as number) + } + if (reactableType === 'COMMENT') { + return dataLoader.get('comments').load(reactableDBId as string) + } + return dataLoader.get('retroReflections').load(reactableDBId as string) +} const tableLookup = { RESPONSE: 'TeamPromptResponse', @@ -41,9 +50,10 @@ const addReactjiToReactable: MutationResolvers['addReactjiToReactable'] = async const subOptions = {mutatorId, operationId} //AUTH - const loaderName = dataLoaderLookup[reactableType] - const reactable = await dataLoader.get(loaderName).load(reactableId) - dataLoader.get(loaderName).clear(reactableId) + const reactableDBId = + reactableType === 'RESPONSE' ? TeamPromptResponseId.split(reactableId) : reactableId + + const reactable = await getReactable(reactableDBId, reactableType, dataLoader) if (!reactable) { return {error: {message: `Item does not exist`}} @@ -133,16 +143,17 @@ const addReactjiToReactable: MutationResolvers['addReactjiToReactable'] = async updatePG(tableName), updateRethink(tableName) ]) + dataLoader.clearAll(['comments', 'teamPromptResponses', 'retroReflections']) const {meetingType} = meeting - const data = {reactableId, reactableType} + const data = {reactableId: reactableDBId as any, reactableType} analytics.reactjiInteracted( viewer, meetingId, meetingType, - reactable, + {...reactable, id: reactableId}, reactableType, reactji, !!isRemove diff --git a/packages/server/graphql/public/mutations/helpers/publishTeamPromptMentions.ts b/packages/server/graphql/public/mutations/helpers/publishTeamPromptMentions.ts index e9c9805d0d8..6ca6ece3237 100644 --- a/packages/server/graphql/public/mutations/helpers/publishTeamPromptMentions.ts +++ b/packages/server/graphql/public/mutations/helpers/publishTeamPromptMentions.ts @@ -1,7 +1,8 @@ import {JSONContent} from '@tiptap/core' +import TeamPromptResponseId from '../../../../../client/shared/gqlIds/TeamPromptResponseId' import getRethink from '../../../../database/rethinkDriver' import NotificationResponseMentioned from '../../../../database/types/NotificationResponseMentioned' -import {TeamPromptResponse} from '../../../../postgres/queries/getTeamPromptResponsesByIds' +import {TeamPromptResponse} from '../../../../postgres/types' const getMentionedUserIdsFromContent = (content: JSONContent): string[] => { if (content.type === 'mention' && content.attrs?.id) { @@ -39,7 +40,8 @@ const createTeamPromptMentionNotifications = async ( const notificationsToAdd = addedMentions.map((mention) => { return new NotificationResponseMentioned({ userId: mention, - responseId: newResponse.id, + // hack to turn the DB id into the GQL ID. The GDL ID should only be used in GQL resolvers, but i didn't catch this before it got built + responseId: TeamPromptResponseId.join(newResponse.id), meetingId: newResponse.meetingId }) }) diff --git a/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts b/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts index e59e43c53fe..ec07653dffd 100644 --- a/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts +++ b/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts @@ -2,8 +2,8 @@ import {generateText, JSONContent} from '@tiptap/core' import {createEditorExtensions} from 'parabol-client/components/promptResponse/tiptapConfig' import TeamPromptResponseId from 'parabol-client/shared/gqlIds/TeamPromptResponseId' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import {TeamPromptResponse} from '../../../postgres/queries/getTeamPromptResponsesByIds' import {upsertTeamPromptResponse as upsertTeamPromptResponseQuery} from '../../../postgres/queries/upsertTeamPromptResponses' +import {TeamPromptResponse} from '../../../postgres/types' import {analytics} from '../../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -28,7 +28,7 @@ const upsertTeamPromptResponse: MutationResolvers['upsertTeamPromptResponse'] = if (inputTeamPromptResponseId) { oldTeamPromptResponse = await dataLoader .get('teamPromptResponses') - .load(inputTeamPromptResponseId) + .load(TeamPromptResponseId.split(inputTeamPromptResponseId)) if (!oldTeamPromptResponse) { return standardError(new Error('TeamPromptResponse not found'), {userId: viewerId}) } @@ -68,15 +68,13 @@ const upsertTeamPromptResponse: MutationResolvers['upsertTeamPromptResponse'] = return standardError(new Error('Invalid editor format'), {userId: viewerId}) } - const teamPromptResponseId = TeamPromptResponseId.join( - await upsertTeamPromptResponseQuery({ - meetingId, - userId: viewerId, - sortOrder: 0, //TODO: placeholder as currently it's defined as non-null. Might decide to remove the column entirely later. - content, - plaintextContent - }) - ) + const teamPromptResponseId = await upsertTeamPromptResponseQuery({ + meetingId, + userId: viewerId, + sortOrder: 0, //TODO: placeholder as currently it's defined as non-null. Might decide to remove the column entirely later. + content, + plaintextContent + }) dataLoader.get('teamPromptResponses').clear(teamPromptResponseId) diff --git a/packages/server/graphql/public/types/AddReactjiToReactableSuccess.ts b/packages/server/graphql/public/types/AddReactjiToReactableSuccess.ts index c4e13cef3bb..7a05df39d5e 100644 --- a/packages/server/graphql/public/types/AddReactjiToReactableSuccess.ts +++ b/packages/server/graphql/public/types/AddReactjiToReactableSuccess.ts @@ -1,12 +1,14 @@ import {AddReactjiToReactableSuccessResolvers, ReactableEnum} from '../../public/resolverTypes' +import {getReactable} from '../mutations/addReactjiToReactable' export type AddReactjiToReactableSuccessSource = { - reactableId: string + reactableId: string | number reactableType: ReactableEnum } + const AddReactjiToReactableSuccess: AddReactjiToReactableSuccessResolvers = { reactable: async ({reactableId, reactableType}, _args: unknown, {dataLoader}) => { - return await dataLoader.get('reactables').load({id: reactableId, type: reactableType}) + return getReactable(reactableId, reactableType, dataLoader) } } diff --git a/packages/server/graphql/public/types/NotifyResponseMentioned.ts b/packages/server/graphql/public/types/NotifyResponseMentioned.ts index c36e42f8f5b..0a8b86906a5 100644 --- a/packages/server/graphql/public/types/NotifyResponseMentioned.ts +++ b/packages/server/graphql/public/types/NotifyResponseMentioned.ts @@ -1,3 +1,4 @@ +import TeamPromptResponseId from '../../../../client/shared/gqlIds/TeamPromptResponseId' import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {NotifyResponseMentionedResolvers} from '../resolverTypes' @@ -7,8 +8,10 @@ const NotifyResponseMentioned: NotifyResponseMentionedResolvers = { const meeting = await dataLoader.get('newMeetings').load(meetingId) return meeting as MeetingTeamPrompt }, - response: ({responseId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('teamPromptResponses').loadNonNull(responseId) + response: ({responseId}, _args, {dataLoader}) => { + // Hack, in a perfect world, this notification would have the numeric DB ID saved on it + const dbId = TeamPromptResponseId.split(responseId) + return dataLoader.get('teamPromptResponses').loadNonNull(dbId) } } diff --git a/packages/server/graphql/public/types/Organization.ts b/packages/server/graphql/public/types/Organization.ts index 5610172e74b..1991fa4790b 100644 --- a/packages/server/graphql/public/types/Organization.ts +++ b/packages/server/graphql/public/types/Organization.ts @@ -1,5 +1,3 @@ -import {ExtractTypeFromQueryBuilderSelect} from '../../../../client/types/generics' -import {selectOrganizations} from '../../../dataloader/primaryKeyLoaderMakers' import { getUserId, isSuperUser, @@ -11,9 +9,6 @@ import {getFeatureTier} from '../../types/helpers/getFeatureTier' import {OrganizationResolvers} from '../resolverTypes' import getActiveTeamCountByOrgIds from './helpers/getActiveTeamCountByOrgIds' -export interface OrganizationSource - extends ExtractTypeFromQueryBuilderSelect {} - const Organization: OrganizationResolvers = { approvedDomains: async ({id: orgId}, _args, {dataLoader}) => { return dataLoader.get('organizationApprovedDomainsByOrgId').load(orgId) diff --git a/packages/server/graphql/public/types/Reactable.ts b/packages/server/graphql/public/types/Reactable.ts index cba19140497..8c7a49e407f 100644 --- a/packages/server/graphql/public/types/Reactable.ts +++ b/packages/server/graphql/public/types/Reactable.ts @@ -1,6 +1,15 @@ import resolveReactjis from '../../resolvers/resolveReactjis' -import getReactableType from '../../types/getReactableType' -import {ReactableResolvers} from '../resolverTypes' +import {ReactableEnum, ReactableResolvers} from '../resolverTypes' + +export const getReactableType = (reactable: any): ReactableEnum => { + if (reactable.reflectionGroupId) { + return 'REFLECTION' + } else if (reactable.discussionId) { + return 'COMMENT' + } else { + return 'RESPONSE' + } +} const Reactable: ReactableResolvers = { __resolveType: (type) => { diff --git a/packages/server/graphql/public/types/RetroReflection.ts b/packages/server/graphql/public/types/RetroReflection.ts index 116444d224d..07d6c6a0b04 100644 --- a/packages/server/graphql/public/types/RetroReflection.ts +++ b/packages/server/graphql/public/types/RetroReflection.ts @@ -1,13 +1,8 @@ -import {ExtractTypeFromQueryBuilderSelect} from '../../../../client/types/generics' import MeetingRetrospective from '../../../database/types/MeetingRetrospective' -import {selectRetroReflections} from '../../../dataloader/primaryKeyLoaderMakers' import {getUserId, isSuperUser} from '../../../utils/authorization' import getGroupedReactjis from '../../../utils/getGroupedReactjis' import {RetroReflectionResolvers} from '../resolverTypes' -export interface RetroReflectionSource - extends ExtractTypeFromQueryBuilderSelect {} - const RetroReflection: RetroReflectionResolvers = { creatorId: async ({creatorId, meetingId}, _args, {authToken, dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) diff --git a/packages/server/graphql/public/types/Team.ts b/packages/server/graphql/public/types/Team.ts index d28e7a23ae5..c55f46565dd 100644 --- a/packages/server/graphql/public/types/Team.ts +++ b/packages/server/graphql/public/types/Team.ts @@ -1,13 +1,9 @@ import TeamInsightsId from 'parabol-client/shared/gqlIds/TeamInsightsId' -import {ExtractTypeFromQueryBuilderSelect} from '../../../../client/types/generics' import toTeamMemberId from '../../../../client/utils/relay/toTeamMemberId' -import {selectTeams} from '../../../dataloader/primaryKeyLoaderMakers' import {getUserId, isTeamMember} from '../../../utils/authorization' import {getFeatureTier} from '../../types/helpers/getFeatureTier' import {TeamResolvers} from '../resolverTypes' -export interface TeamSource extends ExtractTypeFromQueryBuilderSelect {} - const Team: TeamResolvers = { insights: async ( {id, orgId, mostUsedEmojis, meetingEngagement, topRetroTemplates}, diff --git a/packages/server/graphql/public/types/TeamPromptResponse.ts b/packages/server/graphql/public/types/TeamPromptResponse.ts index c480a34cc28..693d7232eeb 100644 --- a/packages/server/graphql/public/types/TeamPromptResponse.ts +++ b/packages/server/graphql/public/types/TeamPromptResponse.ts @@ -1,6 +1,10 @@ +import TeamPromptResponseId from '../../../../client/shared/gqlIds/TeamPromptResponseId' import {TeamPromptResponseResolvers} from '../resolverTypes' const TeamPromptResponse: TeamPromptResponseResolvers = { + id: ({id}) => { + return TeamPromptResponseId.join(id) + }, user: ({userId}, _args, {dataLoader}) => { return dataLoader.get('users').loadNonNull(userId) }, diff --git a/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts b/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts index cf51634ce03..d4a3042d4ee 100644 --- a/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts +++ b/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts @@ -1,7 +1,7 @@ import {UpsertTeamPromptResponseSuccessResolvers} from '../resolverTypes' export type UpsertTeamPromptResponseSuccessSource = { - teamPromptResponseId: string + teamPromptResponseId: number meetingId: string } diff --git a/packages/server/graphql/queries/helpers/makeUpcomingInvoice.ts b/packages/server/graphql/queries/helpers/makeUpcomingInvoice.ts index 0ad3a1755f7..891abebfa89 100644 --- a/packages/server/graphql/queries/helpers/makeUpcomingInvoice.ts +++ b/packages/server/graphql/queries/helpers/makeUpcomingInvoice.ts @@ -1,14 +1,14 @@ import dayjs from 'dayjs' import Stripe from 'stripe' import Invoice from '../../../database/types/Invoice' +import {Organization} from '../../../postgres/types' import {fromEpochSeconds} from '../../../utils/epochTime' import getUpcomingInvoiceId from '../../../utils/getUpcomingInvoiceId' import {getStripeManager} from '../../../utils/stripe' import StripeManager from '../../../utils/stripe/StripeManager' -import {OrganizationSource} from '../../public/types/Organization' export default async function makeUpcomingInvoice( - org: OrganizationSource, + org: Organization, quantity: number, stripeId?: string | null ): Promise { diff --git a/packages/server/graphql/resolvers.ts b/packages/server/graphql/resolvers.ts index 1dac9a9f6fc..b02819208d0 100644 --- a/packages/server/graphql/resolvers.ts +++ b/packages/server/graphql/resolvers.ts @@ -8,12 +8,11 @@ import Organization from '../database/types/Organization' import Task from '../database/types/Task' import User from '../database/types/User' import {Loaders} from '../dataloader/RootDataLoader' -import {TeamMember} from '../postgres/types' +import {Team, TeamMember} from '../postgres/types' import {AnyMeeting} from '../postgres/types/Meeting' import {getUserId, isSuperUser, isUserBillingLeader} from '../utils/authorization' import {GQLContext} from './graphql' import isValid from './isValid' -import {TeamSource} from './public/types/Team' export const resolveNewMeeting = ( { @@ -76,7 +75,7 @@ export const resolveTasks = async ( } export const resolveTeam = ( - {team, teamId}: {teamId?: string; team?: TeamSource}, + {team, teamId}: {teamId?: string; team?: Team}, _args: unknown, {dataLoader}: GQLContext ) => { @@ -88,7 +87,7 @@ export const resolveTeam = ( } export const resolveTeams = ( - {teamIds, teams}: {teamIds: string; teams: TeamSource[]}, + {teamIds, teams}: {teamIds: string; teams: Team[]}, _args: unknown, {dataLoader}: GQLContext ) => { diff --git a/packages/server/graphql/types/getReactableType.ts b/packages/server/graphql/types/getReactableType.ts deleted file mode 100644 index 629da1f1b4a..00000000000 --- a/packages/server/graphql/types/getReactableType.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {ReactableEnum} from '../../database/types/Reactable' - -const getReactableType = (reactable: any): ReactableEnum => { - if (reactable.reflectionGroupId) { - return 'REFLECTION' - } else if (reactable.discussionId) { - return 'COMMENT' - } else { - return 'RESPONSE' - } -} - -export default getReactableType diff --git a/packages/server/postgres/queries/getTeamPromptResponsesByIds.ts b/packages/server/postgres/queries/getTeamPromptResponsesByIds.ts deleted file mode 100644 index 118de592e96..00000000000 --- a/packages/server/postgres/queries/getTeamPromptResponsesByIds.ts +++ /dev/null @@ -1,37 +0,0 @@ -import {JSONContent} from '@tiptap/core' -import TeamPromptResponseId from 'parabol-client/shared/gqlIds/TeamPromptResponseId' -import {MaybeReadonly} from 'parabol-client/types/generics' -import Reactji from '../../database/types/Reactji' -import getPg from '../getPg' -import { - getTeamPromptResponsesByIdsQuery, - IGetTeamPromptResponsesByIdsQueryResult -} from './generated/getTeamPromptResponsesByIdsQuery' - -export interface TeamPromptResponse - extends Omit { - id: string - reactjis: Reactji[] - content: JSONContent -} - -export const mapToTeamPromptResponse = ( - results: IGetTeamPromptResponsesByIdsQueryResult[] -): TeamPromptResponse[] => { - return results.map((teamPromptResponse: any) => { - return { - ...teamPromptResponse, - id: TeamPromptResponseId.join(teamPromptResponse.id) - } as TeamPromptResponse - }) -} - -export const getTeamPromptResponsesByIds = async ( - teamPromptResponseIds: MaybeReadonly -): Promise => { - const teamPromptResponses = await getTeamPromptResponsesByIdsQuery.run( - {ids: teamPromptResponseIds.map((id) => TeamPromptResponseId.split(id))}, - getPg() - ) - return mapToTeamPromptResponse(teamPromptResponses) -} diff --git a/packages/server/postgres/queries/getTeamPromptResponsesByMeetingIds.ts b/packages/server/postgres/queries/getTeamPromptResponsesByMeetingIds.ts index c5d3f18b20e..06e38c88585 100644 --- a/packages/server/postgres/queries/getTeamPromptResponsesByMeetingIds.ts +++ b/packages/server/postgres/queries/getTeamPromptResponsesByMeetingIds.ts @@ -1,16 +1,9 @@ -import getPg from '../getPg' -import {getTeamPromptResponsesByMeetingIdQuery} from './generated/getTeamPromptResponsesByMeetingIdQuery' -import {mapToTeamPromptResponse, TeamPromptResponse} from './getTeamPromptResponsesByIds' +import {selectTeamPromptResponses} from '../select' -export const getTeamPromptResponsesByMeetingIds = async ( - meetingIds: readonly string[] -): Promise => { - const responses = await getTeamPromptResponsesByMeetingIdQuery.run({meetingIds}, getPg()) - return mapToTeamPromptResponse(responses) +export const getTeamPromptResponsesByMeetingIds = async (meetingIds: readonly string[]) => { + return selectTeamPromptResponses().where('meetingId', 'in', meetingIds).execute() } -export const getTeamPromptResponsesByMeetingId = async ( - meetingId: string -): Promise => { +export const getTeamPromptResponsesByMeetingId = async (meetingId: string) => { return getTeamPromptResponsesByMeetingIds([meetingId]) } diff --git a/packages/server/postgres/queries/src/getTeamPromptResponsesByIdsQuery.sql b/packages/server/postgres/queries/src/getTeamPromptResponsesByIdsQuery.sql deleted file mode 100644 index be33810c322..00000000000 --- a/packages/server/postgres/queries/src/getTeamPromptResponsesByIdsQuery.sql +++ /dev/null @@ -1,6 +0,0 @@ -/* - @name getTeamPromptResponsesByIdsQuery - @param ids -> (...) -*/ -SELECT "id", "createdAt", "updatedAt", "meetingId", "userId", "sortOrder", "content", "plaintextContent", to_json("reactjis") as "reactjis" FROM "TeamPromptResponse" -WHERE id in :ids; diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index 7e0e712333a..53a2052c047 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -1,3 +1,4 @@ +import type {JSONContent} from '@tiptap/core' import {NotNull, sql} from 'kysely' import getKysely from './getKysely' @@ -58,3 +59,113 @@ export const selectSuggestedAction = () => { } >() } + +// Can revert to using .selectAll() when https://github.com/kysely-org/kysely/pull/1102 is merged +export const selectTeams = () => + getKysely() + .selectFrom('Team') + .select([ + 'autoJoin', + 'createdAt', + 'createdBy', + 'id', + 'insightsUpdatedAt', + 'isArchived', + 'isOnboardTeam', + 'isPaid', + 'kudosEmojiUnicode', + 'lastMeetingType', + 'lockMessageHTML', + 'meetingEngagement', + 'mostUsedEmojis', + 'name', + 'orgId', + 'qualAIMeetingsCount', + 'tier', + 'topRetroTemplates', + 'trialStartDate', + 'updatedAt' + ]) + .select(({fn}) => [ + fn< + { + dimensionName: string + cloudId: string + projectKey: string + issueKey: string + fieldName: string + fieldType: string + fieldId: string + }[] + >('to_json', ['jiraDimensionFields']).as('jiraDimensionFields') + ]) + +export type ReactjiDB = {id: string; userId: string} +export const selectRetroReflections = () => + getKysely() + .selectFrom('RetroReflection') + .select([ + 'id', + 'content', + 'createdAt', + 'creatorId', + 'isActive', + 'meetingId', + 'plaintextContent', + 'promptId', + 'reflectionGroupId', + 'sentimentScore', + 'sortOrder', + 'updatedAt' + ]) + .select(({fn}) => [ + fn<{lemma: string; salience: number; name: string}[]>('to_json', ['entities']).as('entities'), + fn('to_json', ['reactjis']).as('reactjis') + ]) + +export const selectOrganizations = () => + getKysely() + .selectFrom('Organization') + .select([ + 'id', + 'activeDomain', + 'isActiveDomainTouched', + 'createdAt', + 'name', + 'payLaterClickCount', + 'periodEnd', + 'periodStart', + 'picture', + 'showConversionModal', + 'stripeId', + 'stripeSubscriptionId', + 'upcomingInvoiceEmailSentAt', + 'tier', + 'tierLimitExceededAt', + 'trialStartDate', + 'scheduledLockAt', + 'lockedAt', + 'updatedAt', + 'featureFlags' + ]) + .select(({fn}) => [ + fn<{brand: string; expiry: string; last4: number} | null>('to_json', ['creditCard']).as( + 'creditCard' + ) + ]) + +export const selectTeamPromptResponses = () => + getKysely() + .selectFrom('TeamPromptResponse') + .select([ + 'id', + 'createdAt', + 'updatedAt', + 'meetingId', + 'userId', + 'sortOrder', + 'content', + 'plaintextContent' + ]) + .$narrowType<{content: JSONContent}>() + .select(({fn}) => [fn('to_json', ['reactjis']).as('reactjis')]) diff --git a/packages/server/postgres/types/index.d.ts b/packages/server/postgres/types/index.d.ts index 44db14c7827..af90f4583a8 100644 --- a/packages/server/postgres/types/index.d.ts +++ b/packages/server/postgres/types/index.d.ts @@ -1,21 +1,40 @@ import {SelectQueryBuilder, Selectable} from 'kysely' +import type Comment from '../../database/types/Comment' import { Discussion as DiscussionPG, OrganizationUser as OrganizationUserPG, TeamMember as TeamMemberPG } from '../pg.d' -import {selectSuggestedAction, selectTemplateScale, selectTemplateScaleRef} from '../select' +import { + selectOrganizations, + selectRetroReflections, + selectSuggestedAction, + selectTeamPromptResponses, + selectTeams, + selectTemplateScale, + selectTemplateScaleRef +} from '../select' type ExtractTypeFromQueryBuilderSelect any> = - ReturnType extends SelectQueryBuilder ? X : never + ReturnType extends SelectQueryBuilder<_, _, infer X> ? X : never export type Discussion = Selectable + +export interface Organization + extends ExtractTypeFromQueryBuilderSelect {} export type OrganizationUser = Selectable +export type Reactable = RetroReflection | TeamPromptResponse | Comment +export interface RetroReflection + extends ExtractTypeFromQueryBuilderSelect {} + export type SuggestedAction = ExtractTypeFromQueryBuilderSelect +export interface Team extends ExtractTypeFromQueryBuilderSelect {} + export type TeamMember = Selectable +export type TeamPromptResponse = ExtractTypeFromQueryBuilderSelect export type TemplateScale = ExtractTypeFromQueryBuilderSelect // TODO refactor getTemplateScaleRefsByIds to kysely diff --git a/packages/server/safeMutations/acceptTeamInvitation.ts b/packages/server/safeMutations/acceptTeamInvitation.ts index 54f8ea58cd2..80f0dc24baa 100644 --- a/packages/server/safeMutations/acceptTeamInvitation.ts +++ b/packages/server/safeMutations/acceptTeamInvitation.ts @@ -6,13 +6,13 @@ import getRethink from '../database/rethinkDriver' import {DataLoaderInstance} from '../dataloader/RootDataLoader' import generateUID from '../generateUID' import {DataLoaderWorker} from '../graphql/graphql' -import {TeamSource} from '../graphql/public/types/Team' import getKysely from '../postgres/getKysely' +import {Team} from '../postgres/types' import {Logger} from '../utils/Logger' import setUserTierForUserIds from '../utils/setUserTierForUserIds' const handleFirstAcceptedInvitation = async ( - team: TeamSource, + team: Team, dataLoader: DataLoaderInstance ): Promise => { const now = new Date() @@ -56,11 +56,7 @@ const handleFirstAcceptedInvitation = async ( return userId } -const acceptTeamInvitation = async ( - team: TeamSource, - userId: string, - dataLoader: DataLoaderWorker -) => { +const acceptTeamInvitation = async (team: Team, userId: string, dataLoader: DataLoaderWorker) => { const r = await getRethink() const pg = getKysely() const now = new Date() diff --git a/packages/server/utils/OpenAIServerManager.ts b/packages/server/utils/OpenAIServerManager.ts index 25ae8526109..b54ce4ab7aa 100644 --- a/packages/server/utils/OpenAIServerManager.ts +++ b/packages/server/utils/OpenAIServerManager.ts @@ -1,7 +1,7 @@ import JSON5 from 'json5' import OpenAI from 'openai' import {ModifyType} from '../graphql/public/resolverTypes' -import {RetroReflectionSource} from '../graphql/public/types/RetroReflection' +import {RetroReflection} from '../postgres/types' import {Logger} from './Logger' import sendToSentry from './sendToSentry' @@ -107,7 +107,7 @@ class OpenAIServerManager { } } - async getDiscussionPromptQuestion(topic: string, reflections: RetroReflectionSource[]) { + async getDiscussionPromptQuestion(topic: string, reflections: RetroReflection[]) { if (!this.openAIApi) return null const prompt = `As the meeting facilitator, your task is to steer the discussion in a productive direction. I will provide you with a topic and comments made by the participants around that topic. Your job is to generate a thought-provoking question based on these inputs. Here's how to do it step by step: diff --git a/packages/server/utils/analytics/analytics.ts b/packages/server/utils/analytics/analytics.ts index 1fd9bca190a..6da67df7976 100644 --- a/packages/server/utils/analytics/analytics.ts +++ b/packages/server/utils/analytics/analytics.ts @@ -1,19 +1,18 @@ import {ReasonToDowngradeEnum} from '../../../client/__generated__/DowngradeToStarterMutation.graphql' import type {UpgradeCTALocationEnumType} from '../../../client/shared/UpgradeCTALocationEnumType' +import TeamPromptResponseId from '../../../client/shared/gqlIds/TeamPromptResponseId' import {PARABOL_AI_USER_ID} from '../../../client/utils/constants' import {TeamLimitsEmailType} from '../../billing/helpers/sendTeamsLimitEmail' import Meeting from '../../database/types/Meeting' import MeetingMember from '../../database/types/MeetingMember' import MeetingRetrospective from '../../database/types/MeetingRetrospective' import MeetingTemplate from '../../database/types/MeetingTemplate' -import {Reactable, ReactableEnum} from '../../database/types/Reactable' import {SlackNotificationEventEnum} from '../../database/types/SlackNotification' import {TaskServiceEnum} from '../../database/types/Task' import {DataLoaderWorker} from '../../graphql/graphql' -import {ModifyType} from '../../graphql/public/resolverTypes' +import {ModifyType, ReactableEnum} from '../../graphql/public/resolverTypes' import {IntegrationProviderServiceEnumType} from '../../graphql/types/IntegrationProviderServiceEnum' -import {TeamPromptResponse} from '../../postgres/queries/getTeamPromptResponsesByIds' -import {TemplateScale} from '../../postgres/types' +import {TeamPromptResponse, TemplateScale} from '../../postgres/types' import {MeetingTypeEnum} from '../../postgres/types/Meeting' import {MeetingSeries} from '../../postgres/types/MeetingSeries' import {AmplitudeAnalytics} from './amplitude/AmplitudeAnalytics' @@ -346,12 +345,12 @@ class Analytics { responseAdded = ( user: AnalyticsUser, meetingId: string, - teamPromptResponseId: string, + teamPromptResponseId: number, isUpdate: boolean ) => { this.track(user, 'Response Added', { meetingId, - teamPromptResponseId, + teamPromptResponseId: TeamPromptResponseId.join(teamPromptResponseId), isUpdate }) } @@ -360,7 +359,7 @@ class Analytics { user: AnalyticsUser, meetingId: string, meetingType: MeetingTypeEnum, - reactable: Reactable, + reactable: {createdBy?: string; id: string}, reactableType: ReactableEnum, reactji: string, isRemove: boolean