From 6273411f5c5e7e03dc569b3359a49902b88dc11c Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Fri, 27 Sep 2024 12:03:37 -0700 Subject: [PATCH] chore(rethinkdb): NewMeeting: Phase 1a (#10216) Signed-off-by: Matt Krick --- codegen.json | 10 +- .../modules/demo/ClientGraphQLServer.ts | 19 +- packages/client/modules/demo/initDB.ts | 2 +- packages/client/types/generics.ts | 4 + packages/client/utils/meetings/lookups.ts | 2 +- .../indexing/retrospectiveDiscussionTopic.ts | 3 +- .../helpers/publishSimilarRetroTopics.ts | 3 +- .../__tests__/processRecurrence.test.ts | 6 +- .../database/types/GenericMeetingStage.ts | 14 +- packages/server/database/types/Meeting.ts | 35 +-- .../server/database/types/MeetingAction.ts | 10 +- .../server/database/types/MeetingPoker.ts | 11 +- .../database/types/MeetingRetrospective.ts | 45 ++-- .../database/types/MeetingTeamPrompt.ts | 9 +- .../email/newMeetingSummaryEmailCreator.tsx | 4 +- .../server/graphql/meetingTypePredicates.ts | 13 -- .../server/graphql/mutations/endCheckIn.ts | 22 +- .../graphql/mutations/endRetrospective.ts | 10 +- .../graphql/mutations/endSprintPoker.ts | 18 +- .../addAgendaItemToActiveActionMeeting.ts | 3 +- .../mutations/helpers/addDiscussionTopics.ts | 4 +- .../mutations/helpers/calculateEngagement.ts | 7 +- .../mutations/helpers/collectReactjis.ts | 7 +- .../helpers/createNewMeetingPhases.ts | 5 +- .../endMeeting/sendNewMeetingSummary.ts | 4 +- .../mutations/helpers/gatherInsights.ts | 4 +- .../helpers/generateDiscussionSummary.ts | 6 +- .../mutations/helpers/generateGroups.ts | 3 +- .../helpers/generateStandupMeetingSummary.ts | 6 +- .../mutations/helpers/handleCompletedStage.ts | 9 +- .../helpers/notifications/MSTeamsNotifier.ts | 5 +- .../notifications/MattermostNotifier.ts | 15 +- .../NotificationIntegrationHelper.ts | 14 +- .../helpers/notifications/Notifier.ts | 12 +- .../helpers/notifications/SlackNotifier.ts | 29 ++- .../helpers/notifications/getSummaryText.ts | 31 +-- .../mutations/helpers/pushEstimateToGitHub.ts | 6 +- .../helpers/removeEmptyReflections.ts | 4 +- .../helpers/removeStagesFromMeetings.ts | 2 +- .../helpers/safeCreateRetrospective.ts | 7 +- .../mutations/helpers/safeCreateTeamPrompt.ts | 4 +- .../mutations/helpers/safeEndRetrospective.ts | 20 +- .../mutations/helpers/safeEndTeamPrompt.ts | 14 +- .../helpers/sendPokerMeetingRevoteEvent.ts | 4 +- .../removeReflectionFromGroup.ts | 13 +- .../server/graphql/mutations/joinMeeting.ts | 7 +- .../mutations/pokerAnnounceDeckHover.ts | 6 +- .../graphql/mutations/pokerResetDimension.ts | 6 +- .../graphql/mutations/pokerRevealVotes.ts | 6 +- .../resetRetroMeetingToGroupStage.ts | 6 +- .../graphql/mutations/setTaskEstimate.ts | 5 +- .../graphql/mutations/startSprintPoker.ts | 7 +- .../updateAzureDevOpsDimensionField.ts | 6 +- .../mutations/updateGitHubDimensionField.ts | 6 +- .../graphql/mutations/updatePokerScope.ts | 13 +- .../graphql/mutations/updateRetroMaxVotes.ts | 6 +- .../graphql/mutations/voteForPokerStory.ts | 11 +- .../mutations/voteForReflectionGroup.ts | 6 +- .../mutations/generateMeetingSummary.ts | 9 +- .../private/mutations/processRecurrence.ts | 13 +- .../private/mutations/runScheduledJobs.ts | 4 +- .../types/GenerateMeetingSummarySuccess.ts | 9 +- .../public/mutations/addTranscriptionBot.ts | 6 +- .../graphql/public/mutations/endTeamPrompt.ts | 6 +- .../public/mutations/helpers/getSummaries.ts | 5 +- .../public/mutations/helpers/getTopics.ts | 12 +- .../public/mutations/resetReflectionGroups.ts | 2 +- .../public/mutations/setTeamHealthVote.ts | 32 +++ .../graphql/public/mutations/startCheckIn.ts | 7 +- .../mutations/updateGitLabDimensionField.ts | 6 +- .../mutations/updateJiraDimensionField.ts | 6 +- .../updateJiraServerDimensionField.ts | 6 +- .../public/mutations/updateMeetingTemplate.ts | 4 +- .../mutations/updateRecurrenceSettings.ts | 6 +- .../types/AddTranscriptionBotSuccess.ts | 5 +- .../graphql/public/types/AutogroupSuccess.ts | 4 +- .../server/graphql/public/types/Discussion.ts | 8 +- .../public/types/EndTeamPromptSuccess.ts | 5 +- .../graphql/public/types/EstimateStage.ts | 10 +- .../public/types/GenerateGroupsSuccess.ts | 5 +- .../public/types/GenerateInsightSuccess.ts | 8 +- .../server/graphql/public/types/NewMeeting.ts | 2 +- .../public/types/NotifyResponseMentioned.ts | 4 +- .../public/types/NotifyResponseReplied.ts | 4 +- .../graphql/public/types/ReflectPhase.ts | 4 +- .../types/ResetReflectionGroupsSuccess.ts | 4 +- .../graphql/public/types/RetroDiscussStage.ts | 4 +- .../graphql/public/types/RetroReflection.ts | 4 +- .../public/types/RetroReflectionGroup.ts | 4 +- .../public/types/StartCheckInSuccess.ts | 7 +- .../public/types/StartRetrospectiveSuccess.ts | 7 +- .../public/types/StartTeamPromptSuccess.ts | 5 +- .../graphql/public/types/TeamPromptMeeting.ts | 6 +- .../types/UpdateDimensionFieldSuccess.ts | 4 +- .../types/UpdateMeetingPromptSuccess.ts | 5 +- .../types/UpdateRecurrenceSettingsSuccess.ts | 5 +- packages/server/graphql/resolvers.ts | 3 +- .../graphql/types/SetPhaseFocusPayload.ts | 4 +- .../1726174453131_NewMeeting-phase1.ts | 99 +++++++++ packages/server/postgres/select.ts | 54 ++++- packages/server/postgres/types/Meeting.d.ts | 95 +++++++- .../postgres/types/NewMeetingPhase.d.ts | 204 ++++++++++++++++++ packages/server/postgres/types/index.d.ts | 14 ++ .../server/utils/RecallAIServerManager.ts | 2 +- packages/server/utils/analytics/analytics.ts | 20 +- packages/server/utils/analytics/helpers.ts | 9 +- packages/server/utils/getPhase.ts | 31 +-- 107 files changed, 905 insertions(+), 446 deletions(-) create mode 100644 packages/server/postgres/migrations/1726174453131_NewMeeting-phase1.ts create mode 100644 packages/server/postgres/types/NewMeetingPhase.d.ts diff --git a/codegen.json b/codegen.json index 39b03e6f474..14b74d7dc35 100644 --- a/codegen.json +++ b/codegen.json @@ -26,7 +26,7 @@ "PingableServices": "./types/PingableServices#PingableServicesSource", "ProcessRecurrenceSuccess": "./types/ProcessRecurrenceSuccess#ProcessRecurrenceSuccessSource", "RemoveAuthIdentitySuccess": "./types/RemoveAuthIdentitySuccess#RemoveAuthIdentitySuccessSource", - "RetrospectiveMeeting": "../../database/types/MeetingRetrospective#default", + "RetrospectiveMeeting": "../../postgres/types/Meeting#RetrospectiveMeeting", "SAML": "./types/SAML#SAMLSource", "SetIsFreeMeetingTemplateSuccess": "./types/SetIsFreeMeetingTemplateSuccess#SetIsFreeMeetingTemplateSuccessSource", "SignupsPayload": "./types/SignupsPayload#SignupsPayloadSource", @@ -73,7 +73,7 @@ "EndTeamPromptSuccess": "./types/EndTeamPromptSuccess#EndTeamPromptSuccessSource", "AcceptRequestToJoinDomainSuccess": "./types/AcceptRequestToJoinDomainSuccess#AcceptRequestToJoinDomainSuccessSource", "AcceptTeamInvitationPayload": "./types/AcceptTeamInvitationPayload#AcceptTeamInvitationPayloadSource", - "ActionMeeting": "../../database/types/MeetingAction#default", + "ActionMeeting": "../../postgres/types/Meeting#CheckInMeeting", "ActionMeetingMember": "../../database/types/ActionMeetingMember#default as ActionMeetingMemberDB", "AddApprovedOrganizationDomainsSuccess": "./types/AddApprovedOrganizationDomainsSuccess#AddApprovedOrganizationDomainsSuccessSource", "AddPokerTemplateSuccess": "./types/AddPokerTemplateSuccess#AddPokerTemplateSuccessSource", @@ -143,7 +143,7 @@ "Threadable": "./types/Threadable#ThreadableSource", "OrgIntegrationProviders": "./types/OrgIntegrationProviders#OrgIntegrationProvidersSource", "OrganizationUser": "../../postgres/types/index#OrganizationUser as OrganizationUserDB", - "PokerMeeting": "../../database/types/MeetingPoker#default as MeetingPoker", + "PokerMeeting": "../../postgres/types/Meeting#PokerMeeting", "PokerMeetingMember": "../../database/types/MeetingPokerMeetingMember#default as PokerMeetingMemberDB", "PokerTemplate": "../../database/types/PokerTemplate#default as PokerTemplateDB", "RRule": "rrule-rust#RRuleSet", @@ -159,7 +159,7 @@ "ResetReflectionGroupsSuccess": "./types/ResetReflectionGroupsSuccess#ResetReflectionGroupsSuccessSource", "RetroReflection": "../../postgres/types/index#RetroReflection as RetroReflectionDB", "RetroReflectionGroup": "./types/RetroReflectionGroup#RetroReflectionGroupSource", - "RetrospectiveMeeting": "../../database/types/MeetingRetrospective#default", + "RetrospectiveMeeting": "../../postgres/types/Meeting#RetrospectiveMeeting", "RetrospectiveMeetingMember": "../../database/types/RetroMeetingMember#default", "SAML": "./types/SAML#SAMLSource", "SetMeetingSettingsPayload": "../types/SetMeetingSettingsPayload#SetMeetingSettingsPayloadSource", @@ -182,7 +182,7 @@ "TeamMemberIntegrationAuthOAuth1": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth", "TeamMemberIntegrationAuthOAuth2": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth", "TeamMemberIntegrations": "./types/TeamMemberIntegrations#TeamMemberIntegrationsSource", - "TeamPromptMeeting": "../../database/types/MeetingTeamPrompt#default as MeetingTeamPromptDB", + "TeamPromptMeeting": "../../postgres/types/Meeting#TeamPromptMeeting", "TeamPromptMeetingMember": "../../database/types/TeamPromptMeetingMember#default as TeamPromptMeetingMemberDB", "TeamPromptResponse": "../../postgres/types/index#TeamPromptResponse as TeamPromptResponseDB", "TemplateDimension": "../../postgres/types/index#TemplateDimension as TemplateDimensionDB", diff --git a/packages/client/modules/demo/ClientGraphQLServer.ts b/packages/client/modules/demo/ClientGraphQLServer.ts index e7ea44c8170..4be77ecd626 100644 --- a/packages/client/modules/demo/ClientGraphQLServer.ts +++ b/packages/client/modules/demo/ClientGraphQLServer.ts @@ -8,13 +8,15 @@ import stringSimilarity from 'string-similarity' import {ReactableEnum} from '~/__generated__/AddReactjiToReactableMutation.graphql' import {DragReflectionDropTargetTypeEnum} from '~/__generated__/EndDraggingReflectionMutation.graphql' import {PALETTE} from '~/styles/paletteV3' -import DiscussPhase from '../../../server/database/types/DiscussPhase' -import DiscussStage from '../../../server/database/types/DiscussStage' -import NewMeetingPhase from '../../../server/database/types/GenericMeetingPhase' -import NewMeetingStage from '../../../server/database/types/GenericMeetingStage' import GoogleAnalyzedEntity from '../../../server/database/types/GoogleAnalyzedEntity' import ReflectPhase from '../../../server/database/types/ReflectPhase' import ITask from '../../../server/database/types/Task' +import {NewMeetingStage} from '../../../server/graphql/private/resolverTypes' +import { + DiscussPhase, + DiscussStage, + NewMeetingPhase +} from '../../../server/postgres/types/NewMeetingPhase' import { ExternalLinks, MeetingSettingsThreshold, @@ -100,11 +102,7 @@ export type DemoReflectionGroup = { voterIds: string[] } -export type IDiscussPhase = Omit & { - readyToAdvance: any - startAt: string | Date - endAt: string | Date -} +export type IDiscussPhase = DiscussPhase export type IReflectPhase = Omit & { startAt: string | Date @@ -1048,7 +1046,6 @@ class ClientGraphQLServer extends (EventEmitter as GQLDemoEmitter) { reflectionGroupId: newReflectionGroupId, updatedAt: now }) - this.db.newMeeting.nextAutoGroupThreshold = null const nextTitle = getGroupSmartTitle([reflection as DemoReflection]) newReflectionGroup.smartTitle = nextTitle newReflectionGroup.title = nextTitle @@ -1523,7 +1520,7 @@ class ClientGraphQLServer extends (EventEmitter as GQLDemoEmitter) { }, EndRetrospectiveMutation: ({meetingId}: {meetingId: string}, userId: string) => { const phases = this.db.newMeeting.phases as INewMeetingPhase[] - const lastPhase = phases[phases.length - 1] as IDiscussPhase + const lastPhase = phases[phases.length - 1]! const currentStage = lastPhase.stages.find( (stage) => stage.startAt && !stage.endAt ) as IDiscussStage diff --git a/packages/client/modules/demo/initDB.ts b/packages/client/modules/demo/initDB.ts index aa9dfc85899..23e400d75f8 100644 --- a/packages/client/modules/demo/initDB.ts +++ b/packages/client/modules/demo/initDB.ts @@ -1,7 +1,7 @@ import {SlackNotificationEventEnum} from '~/__generated__/SlackNotificationList_viewer.graphql' import {PALETTE} from '~/styles/paletteV3' -import RetrospectiveMeeting from '../../../server/database/types/MeetingRetrospective' import ITask from '../../../server/database/types/Task' +import {RetrospectiveMeeting} from '../../../server/postgres/types/Meeting' import JiraProjectId from '../../shared/gqlIds/JiraProjectId' import demoUserAvatar from '../../styles/theme/images/avatar-user.svg' import {ExternalLinks, MeetingSettingsThreshold, RetroDemo} from '../../types/constEnums' diff --git a/packages/client/types/generics.ts b/packages/client/types/generics.ts index 755054dfa68..0148c9b35be 100644 --- a/packages/client/types/generics.ts +++ b/packages/client/types/generics.ts @@ -32,6 +32,10 @@ type DeepNonNullableObject = { [P in keyof T]-?: DeepNonNullable> } +export type NonNullableProps = { + [K in keyof T]: NonNullable +} + // export type DeepNullableObject = { // [P in keyof T]: T[P] extends Array // ? Array> | null diff --git a/packages/client/utils/meetings/lookups.ts b/packages/client/utils/meetings/lookups.ts index aa327d72d5b..b0a59f46588 100644 --- a/packages/client/utils/meetings/lookups.ts +++ b/packages/client/utils/meetings/lookups.ts @@ -1,6 +1,6 @@ import React from 'react' -import {MeetingTypeEnum} from '~/../server/postgres/types/Meeting' import {NewMeetingPhaseTypeEnum} from '~/__generated__/ActionMeetingSidebar_meeting.graphql' +import {MeetingTypeEnum} from '../../__generated__/SummarySheet_meeting.graphql' import CardsSVG from '../../components/CardsSVG' import {ACTION, POKER, RETROSPECTIVE, TEAM_PROMPT} from '../constants' diff --git a/packages/embedder/indexing/retrospectiveDiscussionTopic.ts b/packages/embedder/indexing/retrospectiveDiscussionTopic.ts index 36eca506754..c962d3bcbdf 100644 --- a/packages/embedder/indexing/retrospectiveDiscussionTopic.ts +++ b/packages/embedder/indexing/retrospectiveDiscussionTopic.ts @@ -1,4 +1,3 @@ -import {isMeetingRetrospective} from 'parabol-server/database/types/MeetingRetrospective' import {DataLoaderInstance} from 'parabol-server/dataloader/RootDataLoader' import prettier from 'prettier' import {Comment} from '../../server/postgres/types' @@ -73,7 +72,7 @@ export const createTextFromRetrospectiveDiscussionTopic = async ( dataLoader.get('retroReflectionGroups').load(reflectionGroupId), dataLoader.get('retroReflectionsByGroupId').load(reflectionGroupId) ]) - if (!isMeetingRetrospective(newMeeting)) throw new Error('Meeting is not a retro') + if (newMeeting.meetingType !== 'retrospective') throw new Error('Meeting is not a retro') // It should never be undefined, but our data integrity in RethinkDB is bad const templateId = newMeeting?.templateId ?? '' diff --git a/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts b/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts index 217ee63073b..9e43696e9f3 100644 --- a/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts +++ b/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts @@ -2,7 +2,6 @@ import {SubscriptionChannel} from '../../../client/types/constEnums' import makeAppURL from '../../../client/utils/makeAppURL' import appOrigin from '../../../server/appOrigin' import {DataLoaderInstance} from '../../../server/dataloader/RootDataLoader' -import {isRetroMeeting} from '../../../server/graphql/meetingTypePredicates' import { buildCommentContentBlock, createAIComment @@ -25,7 +24,7 @@ const makeSimilarDiscussionLink = async ( dataLoader.get('retroReflectionGroups').loadNonNull(reflectionGroupId) ]) - if (!meeting || !isRetroMeeting(meeting)) throw new Error('invalid meeting type') + if (!meeting || meeting.meetingType !== 'retrospective') throw new Error('invalid meeting type') const {phases, name: meetingName} = meeting const {title: topic} = reflectionGroup const discussPhase = getPhase(phases, 'discuss') diff --git a/packages/server/__tests__/processRecurrence.test.ts b/packages/server/__tests__/processRecurrence.test.ts index bcbf2ab7fbd..86edc1b87fb 100644 --- a/packages/server/__tests__/processRecurrence.test.ts +++ b/packages/server/__tests__/processRecurrence.test.ts @@ -10,6 +10,7 @@ import ReflectPhase from '../database/types/ReflectPhase' import TeamPromptResponsesPhase from '../database/types/TeamPromptResponsesPhase' import generateUID from '../generateUID' import {insertMeetingSeries as insertMeetingSeriesQuery} from '../postgres/queries/insertMeetingSeries' +import {RetroMeetingPhase} from '../postgres/types/NewMeetingPhase' import {getUserTeams, sendIntranet, signUp} from './common' const PROCESS_RECURRENCE = ` @@ -273,7 +274,10 @@ RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` id: meetingId, teamId, meetingCount: 0, - phases: [new ReflectPhase(teamId, []), new DiscussPhase(undefined)], + phases: [ + new ReflectPhase(teamId, []) as RetroMeetingPhase, + new DiscussPhase(undefined) as RetroMeetingPhase + ], facilitatorUserId: userId, scheduledEndTime: new Date(Date.now() - ms('5m')), meetingSeriesId, diff --git a/packages/server/database/types/GenericMeetingStage.ts b/packages/server/database/types/GenericMeetingStage.ts index 016e37d6b93..bfe7e2ae23d 100644 --- a/packages/server/database/types/GenericMeetingStage.ts +++ b/packages/server/database/types/GenericMeetingStage.ts @@ -36,17 +36,17 @@ export interface GenericMeetingStageInput { export default class GenericMeetingStage { id: string - isAsync: boolean | undefined | null + isAsync?: boolean | undefined | null isComplete = false isNavigable: boolean isNavigableByFacilitator: boolean - startAt: Date | undefined - endAt: Date | undefined = undefined - scheduledEndTime: Date | undefined | null - suggestedEndTime: Date | undefined - suggestedTimeLimit: number | undefined + startAt?: Date | undefined + endAt?: Date | undefined = undefined + scheduledEndTime?: Date | undefined | null + suggestedEndTime?: Date | undefined + suggestedTimeLimit?: number | undefined viewCount: number - readyToAdvance: string[] | undefined = [] + readyToAdvance?: string[] | undefined = [] phaseType: string constructor(input: GenericMeetingStageInput) { const {durations, phaseType, id, isNavigable, isNavigableByFacilitator, startAt, viewCount} = diff --git a/packages/server/database/types/Meeting.ts b/packages/server/database/types/Meeting.ts index c9ed6cbf778..503b945c153 100644 --- a/packages/server/database/types/Meeting.ts +++ b/packages/server/database/types/Meeting.ts @@ -1,21 +1,22 @@ import generateUID from '../../generateUID' import {MeetingTypeEnum} from '../../postgres/types/Meeting' +import {NewMeetingPhase} from '../../postgres/types/NewMeetingPhase' import GenericMeetingPhase from './GenericMeetingPhase' interface Input { - id?: string + id?: string | null teamId: string meetingType: MeetingTypeEnum meetingCount: number - name?: string + name?: string | null // Every meeting has at least one phase - phases: [GenericMeetingPhase, ...GenericMeetingPhase[]] + phases: [NewMeetingPhase, ...NewMeetingPhase[]] facilitatorUserId: string - showConversionModal?: boolean - meetingSeriesId?: number + showConversionModal?: boolean | null + meetingSeriesId?: number | null scheduledEndTime?: Date | null - summary?: string - sentimentScore?: number + summary?: string | null + sentimentScore?: number | null } const namePrefix = { @@ -29,23 +30,23 @@ export default abstract class Meeting { updatedAt = new Date() createdBy: string | null endedAt: Date | undefined | null = undefined - facilitatorStageId: string | undefined - facilitatorUserId: string + facilitatorStageId: string + facilitatorUserId: string | null meetingCount: number meetingNumber: number name: string - summarySentAt: Date | undefined = undefined + summarySentAt: Date | undefined | null = undefined teamId: string meetingType: MeetingTypeEnum phases: GenericMeetingPhase[] - showConversionModal?: boolean - meetingSeriesId?: number + showConversionModal?: boolean | null + meetingSeriesId?: number | null scheduledEndTime?: Date | null - summary?: string - sentimentScore?: number - usedReactjis?: Record - slackTs?: string - engagement?: number + summary?: string | null + sentimentScore?: number | null + usedReactjis?: Record | null + slackTs?: string | number | null + engagement?: number | null constructor(input: Input) { const { diff --git a/packages/server/database/types/MeetingAction.ts b/packages/server/database/types/MeetingAction.ts index fee9b580b8d..745df2cb0b3 100644 --- a/packages/server/database/types/MeetingAction.ts +++ b/packages/server/database/types/MeetingAction.ts @@ -1,10 +1,6 @@ -import AgendaItemsPhase from './AgendaItemsPhase' -import CheckInPhase from './CheckInPhase' -import GenericMeetingPhase from './GenericMeetingPhase' +import {CheckInMeetingPhase} from '../../postgres/types/NewMeetingPhase' import Meeting from './Meeting' -import UpdatesPhase from './UpdatesPhase' -type CheckInMeetingPhase = CheckInPhase | UpdatesPhase | GenericMeetingPhase | AgendaItemsPhase interface Input { id?: string teamId: string @@ -14,10 +10,6 @@ interface Input { facilitatorUserId: string } -export function isMeetingAction(meeting: Meeting): meeting is MeetingAction { - return meeting.meetingType === 'action' -} - export default class MeetingAction extends Meeting { meetingType!: 'action' taskCount?: number diff --git a/packages/server/database/types/MeetingPoker.ts b/packages/server/database/types/MeetingPoker.ts index 5883fd6d8ac..f8c4a19cea6 100644 --- a/packages/server/database/types/MeetingPoker.ts +++ b/packages/server/database/types/MeetingPoker.ts @@ -1,24 +1,17 @@ -import CheckInPhase from './CheckInPhase' -import EstimatePhase from './EstimatePhase' -import GenericMeetingPhase from './GenericMeetingPhase' +import {PokerMeetingPhase} from '../../postgres/types/NewMeetingPhase' import Meeting from './Meeting' -type PokerPhase = CheckInPhase | EstimatePhase | GenericMeetingPhase interface Input { id: string teamId: string meetingCount: number name: string - phases: [PokerPhase, ...PokerPhase[]] + phases: [PokerMeetingPhase, ...PokerMeetingPhase[]] facilitatorUserId: string templateId: string templateRefId: string } -export function isMeetingPoker(meeting: Meeting): meeting is MeetingPoker { - return meeting.meetingType === 'poker' -} - export default class MeetingPoker extends Meeting { meetingType!: 'poker' templateId: string diff --git a/packages/server/database/types/MeetingRetrospective.ts b/packages/server/database/types/MeetingRetrospective.ts index d727149bb78..4ee8d4e376d 100644 --- a/packages/server/database/types/MeetingRetrospective.ts +++ b/packages/server/database/types/MeetingRetrospective.ts @@ -1,44 +1,31 @@ -import GenericMeetingPhase from './GenericMeetingPhase' +import {AutogroupReflectionGroupType, TranscriptBlock} from '../../postgres/types' +import {RetroMeetingPhase} from '../../postgres/types/NewMeetingPhase' import Meeting from './Meeting' -export type AutogroupReflectionGroupType = { - groupTitle: string - reflectionIds: string[] -} - -export type TranscriptBlock = { - speaker: string - words: string -} - interface Input { - id?: string + id?: string | null teamId: string meetingCount: number name: string - phases: [GenericMeetingPhase, ...GenericMeetingPhase[]] + phases: [RetroMeetingPhase, ...RetroMeetingPhase[]] facilitatorUserId: string - showConversionModal?: boolean + showConversionModal?: boolean | null templateId: string totalVotes: number maxVotesPerGroup: number disableAnonymity: boolean - transcription?: TranscriptBlock[] - autogroupReflectionGroups?: AutogroupReflectionGroupType[] - resetReflectionGroups?: AutogroupReflectionGroupType[] + transcription?: TranscriptBlock[] | null + autogroupReflectionGroups?: AutogroupReflectionGroupType[] | null + resetReflectionGroups?: AutogroupReflectionGroupType[] | null recallBotId?: string - videoMeetingURL?: string - meetingSeriesId?: number + videoMeetingURL?: string | null + meetingSeriesId?: number | null scheduledEndTime?: Date | null } -export function isMeetingRetrospective(meeting: Meeting): meeting is MeetingRetrospective { - return meeting.meetingType === 'retrospective' -} - export default class MeetingRetrospective extends Meeting { meetingType!: 'retrospective' - showConversionModal?: boolean + showConversionModal?: boolean | null autoGroupThreshold?: number | null nextAutoGroupThreshold?: number | null totalVotes: number @@ -50,11 +37,11 @@ export default class MeetingRetrospective extends Meeting { templateId: string topicCount?: number reflectionCount?: number - transcription?: TranscriptBlock[] - recallBotId?: string - videoMeetingURL?: string - autogroupReflectionGroups?: AutogroupReflectionGroupType[] - resetReflectionGroups?: AutogroupReflectionGroupType[] + transcription?: TranscriptBlock[] | null + recallBotId?: string | null + videoMeetingURL?: string | null + autogroupReflectionGroups?: AutogroupReflectionGroupType[] | null + resetReflectionGroups?: AutogroupReflectionGroupType[] | null constructor(input: Input) { const { diff --git a/packages/server/database/types/MeetingTeamPrompt.ts b/packages/server/database/types/MeetingTeamPrompt.ts index bf57eb4ebaf..b781fbb2f55 100644 --- a/packages/server/database/types/MeetingTeamPrompt.ts +++ b/packages/server/database/types/MeetingTeamPrompt.ts @@ -1,8 +1,5 @@ -import GenericMeetingPhase from './GenericMeetingPhase' +import {TeamPromptPhase} from '../../postgres/types/NewMeetingPhase' import Meeting from './Meeting' -import TeamPromptResponsesPhase from './TeamPromptResponsesPhase' - -type TeamPromptPhase = TeamPromptResponsesPhase | GenericMeetingPhase interface Input { id?: string @@ -16,10 +13,6 @@ interface Input { scheduledEndTime?: Date } -export function isMeetingTeamPrompt(meeting: Meeting): meeting is MeetingTeamPrompt { - return meeting.meetingType === 'teamPrompt' -} - export default class MeetingTeamPrompt extends Meeting { meetingType!: 'teamPrompt' meetingPrompt: string diff --git a/packages/server/email/newMeetingSummaryEmailCreator.tsx b/packages/server/email/newMeetingSummaryEmailCreator.tsx index 8387534b811..f0e065a2f33 100644 --- a/packages/server/email/newMeetingSummaryEmailCreator.tsx +++ b/packages/server/email/newMeetingSummaryEmailCreator.tsx @@ -19,9 +19,9 @@ const newMeetingSummaryEmailCreator = async (props: Props) => { const dataLoaderId = dataLoader.share() const newMeeting = await dataLoader.get('newMeetings').load(meetingId) - const facilitator = await dataLoader.get('users').loadNonNull(newMeeting.facilitatorUserId) + const facilitator = await dataLoader.get('users').loadNonNull(newMeeting.facilitatorUserId!) const {tms} = facilitator - const authToken = new AuthToken({sub: newMeeting.facilitatorUserId, tms, rol: 'impersonate'}) + const authToken = new AuthToken({sub: newMeeting.facilitatorUserId!, tms, rol: 'impersonate'}) const environment = new ServerEnvironment(authToken, dataLoaderId) // this depends on types, and those types are generated by created the schema, which must crawl the endMeeting file diff --git a/packages/server/graphql/meetingTypePredicates.ts b/packages/server/graphql/meetingTypePredicates.ts index 332acd71a87..bb4ed35985d 100644 --- a/packages/server/graphql/meetingTypePredicates.ts +++ b/packages/server/graphql/meetingTypePredicates.ts @@ -5,20 +5,7 @@ import EstimatePhase from '../database/types/EstimatePhase' import EstimateStage from '../database/types/EstimateStage' import GenericMeetingPhase from '../database/types/GenericMeetingPhase' import GenericMeetingStage from '../database/types/GenericMeetingStage' -import MeetingAction from '../database/types/MeetingAction' -import MeetingPoker from '../database/types/MeetingPoker' -import MeetingRetrospective from '../database/types/MeetingRetrospective' import TeamPromptResponsesPhase from '../database/types/TeamPromptResponsesPhase' -import {AnyMeeting} from '../postgres/types/Meeting' - -export const isRetroMeeting = (meeting: AnyMeeting): meeting is MeetingRetrospective => - meeting.meetingType === 'retrospective' - -export const isPokerMeeting = (meeting: AnyMeeting): meeting is MeetingPoker => - meeting.meetingType === 'poker' - -export const isActionMeeting = (meeting: AnyMeeting): meeting is MeetingAction => - meeting.meetingType === 'action' export const isEstimateStage = (stage: GenericMeetingStage): stage is EstimateStage => stage.phaseType === 'ESTIMATE' diff --git a/packages/server/graphql/mutations/endCheckIn.ts b/packages/server/graphql/mutations/endCheckIn.ts index 4ad865c40b0..c3ea3419772 100644 --- a/packages/server/graphql/mutations/endCheckIn.ts +++ b/packages/server/graphql/mutations/endCheckIn.ts @@ -7,13 +7,13 @@ import {positionAfter} from '../../../client/shared/sortOrder' import {checkTeamsLimit} from '../../billing/helpers/teamLimitsCheck' import getRethink from '../../database/rethinkDriver' import {RDatum} from '../../database/stricterR' -import MeetingAction from '../../database/types/MeetingAction' import Task from '../../database/types/Task' import TimelineEventCheckinComplete from '../../database/types/TimelineEventCheckinComplete' import {DataLoaderInstance} from '../../dataloader/RootDataLoader' import generateUID from '../../generateUID' import getKysely from '../../postgres/getKysely' import {AgendaItem} from '../../postgres/types' +import {CheckInMeeting} from '../../postgres/types/Meeting' import archiveTasksForDB from '../../safeMutations/archiveTasksForDB' import removeSuggestedAction from '../../safeMutations/removeSuggestedAction' import {Logger} from '../../utils/Logger' @@ -101,7 +101,7 @@ const clonePinnedAgendaItems = async ( dataLoader.clearAll('agendaItems') } -const summarizeCheckInMeeting = async (meeting: MeetingAction, dataLoader: DataLoaderWorker) => { +const summarizeCheckInMeeting = async (meeting: CheckInMeeting, dataLoader: DataLoaderWorker) => { /* If isKill, no agenda items were processed so clear none of them. * Similarly, don't clone pins. the original ones will show up again. */ @@ -177,12 +177,11 @@ export default { const viewerId = getUserId(authToken) // AUTH - const meeting = (await r - .table('NewMeeting') - .get(meetingId) - .default(null) - .run()) as MeetingAction | null + const meeting = await r.table('NewMeeting').get(meetingId).default(null).run() if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) + if (meeting.meetingType !== 'action') { + return standardError(new Error('Not a check-in meeting'), {userId: viewerId}) + } const {endedAt, facilitatorStageId, phases, teamId} = meeting // VALIDATION @@ -201,7 +200,7 @@ export default { const phase = getMeetingPhase(phases) const insights = await gatherInsights(meeting, dataLoader) - const completedCheckIn = (await r + const completedCheckIn = await r .table('NewMeeting') .get(meetingId) .update( @@ -213,7 +212,7 @@ export default { {returnChanges: true} )('changes')(0)('new_val') .default(null) - .run()) as unknown as MeetingAction + .run() if (!completedCheckIn) { return standardError(new Error('Completed check-in meeting does not exist'), { @@ -221,6 +220,11 @@ export default { }) } + if (completedCheckIn.meetingType !== 'action') { + return standardError(new Error('Completed check-in meeting is not an action'), { + userId: viewerId + }) + } // remove any empty tasks const [meetingMembers, team, teamMembers, removedTaskIds] = await Promise.all([ dataLoader.get('meetingMembersByMeetingId').load(meetingId), diff --git a/packages/server/graphql/mutations/endRetrospective.ts b/packages/server/graphql/mutations/endRetrospective.ts index 82136fa8f9d..322a6ee1c67 100644 --- a/packages/server/graphql/mutations/endRetrospective.ts +++ b/packages/server/graphql/mutations/endRetrospective.ts @@ -1,6 +1,5 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import getRethink from '../../database/rethinkDriver' -import MeetingRetrospective from '../../database/types/MeetingRetrospective' import {getUserId, isTeamMember} from '../../utils/authorization' import standardError from '../../utils/standardError' import {GQLContext} from '../graphql' @@ -23,12 +22,11 @@ export default { const viewerId = getUserId(authToken) // AUTH - const meeting = (await r - .table('NewMeeting') - .get(meetingId) - .default(null) - .run()) as MeetingRetrospective | null + const meeting = await r.table('NewMeeting').get(meetingId).default(null).run() if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) + if (meeting.meetingType !== 'retrospective') { + return standardError(new Error('Meeting not found'), {userId: viewerId}) + } const {endedAt, teamId} = meeting // VALIDATION diff --git a/packages/server/graphql/mutations/endSprintPoker.ts b/packages/server/graphql/mutations/endSprintPoker.ts index 00db314a2ab..5927fcd34f4 100644 --- a/packages/server/graphql/mutations/endSprintPoker.ts +++ b/packages/server/graphql/mutations/endSprintPoker.ts @@ -4,8 +4,6 @@ import getMeetingPhase from 'parabol-client/utils/getMeetingPhase' import findStageById from 'parabol-client/utils/meetings/findStageById' import {checkTeamsLimit} from '../../billing/helpers/teamLimitsCheck' import getRethink from '../../database/rethinkDriver' -import Meeting from '../../database/types/Meeting' -import MeetingPoker from '../../database/types/MeetingPoker' import TimelineEventPokerComplete from '../../database/types/TimelineEventPokerComplete' import getKysely from '../../postgres/getKysely' import {Logger} from '../../utils/Logger' @@ -42,12 +40,11 @@ export default { const viewerId = getUserId(authToken) // AUTH - const meeting = (await r - .table('NewMeeting') - .get(meetingId) - .default(null) - .run()) as Meeting | null + const meeting = await r.table('NewMeeting').get(meetingId).default(null).run() if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) + if (meeting.meetingType !== 'poker') { + return standardError(new Error('Meeting is not a poker meeting'), {userId: viewerId}) + } const {endedAt, facilitatorStageId, phases, teamId} = meeting // VALIDATION @@ -82,7 +79,7 @@ export default { await dataLoader.get('commentCountByDiscussionId').loadMany(discussionIds) ).filter(isValid) const commentCount = commentCounts.reduce((cumSum, count) => cumSum + count, 0) - const completedMeeting = (await r + const completedMeeting = await r .table('NewMeeting') .get(meetingId) .update( @@ -96,12 +93,15 @@ export default { {returnChanges: true, nonAtomic: true} )('changes')(0)('new_val') .default(null) - .run()) as unknown as MeetingPoker + .run() if (!completedMeeting) { return standardError(new Error('Completed poker meeting does not exist'), { userId: viewerId }) } + if (completedMeeting.meetingType !== 'poker') { + return standardError(new Error('Meeting is not a poker meeting'), {userId: viewerId}) + } const {templateId} = completedMeeting const [meetingMembers, team, teamMembers, removedTaskIds, template] = await Promise.all([ dataLoader.get('meetingMembersByMeetingId').load(meetingId), diff --git a/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts b/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts index 735ba4be831..69c0e8bca7e 100644 --- a/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts +++ b/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts @@ -1,6 +1,5 @@ import getRethink from '../../../database/rethinkDriver' import AgendaItemsStage from '../../../database/types/AgendaItemsStage' -import MeetingAction from '../../../database/types/MeetingAction' import getKysely from '../../../postgres/getKysely' import getPhase from '../../../utils/getPhase' import {DataLoaderWorker} from '../../graphql' @@ -18,7 +17,7 @@ const addAgendaItemToActiveActionMeeting = async ( const activeMeetings = await dataLoader.get('activeMeetingsByTeamId').load(teamId) const actionMeeting = activeMeetings.find( (activeMeeting) => activeMeeting.meetingType === 'action' - ) as MeetingAction | undefined + ) if (!actionMeeting) return undefined const {id: meetingId, phases} = actionMeeting const agendaItemPhase = getPhase(phases, 'agendaitems') diff --git a/packages/server/graphql/mutations/helpers/addDiscussionTopics.ts b/packages/server/graphql/mutations/helpers/addDiscussionTopics.ts index ef05b73ae6d..a703837a169 100644 --- a/packages/server/graphql/mutations/helpers/addDiscussionTopics.ts +++ b/packages/server/graphql/mutations/helpers/addDiscussionTopics.ts @@ -1,11 +1,11 @@ import mapGroupsToStages from 'parabol-client/utils/makeGroupsToStages' import DiscussStage from '../../../database/types/DiscussStage' -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import generateUID from '../../../generateUID' +import {RetrospectiveMeeting} from '../../../postgres/types/Meeting' import getPhase from '../../../utils/getPhase' import {DataLoaderWorker} from '../../graphql' -const addDiscussionTopics = async (meeting: MeetingRetrospective, dataLoader: DataLoaderWorker) => { +const addDiscussionTopics = async (meeting: RetrospectiveMeeting, dataLoader: DataLoaderWorker) => { const {id: meetingId, phases} = meeting const discussPhase = getPhase(phases, 'discuss') if (!discussPhase) return {discussPhaseStages: [], meetingId} diff --git a/packages/server/graphql/mutations/helpers/calculateEngagement.ts b/packages/server/graphql/mutations/helpers/calculateEngagement.ts index bed551a8130..78ebd67ddfc 100644 --- a/packages/server/graphql/mutations/helpers/calculateEngagement.ts +++ b/packages/server/graphql/mutations/helpers/calculateEngagement.ts @@ -1,6 +1,7 @@ import TeamMemberId from '../../../../client/shared/gqlIds/TeamMemberId' import EstimatePhase from '../../../database/types/EstimatePhase' -import Meeting from '../../../database/types/Meeting' +import {AnyMeeting} from '../../../postgres/types/Meeting' +import {NewMeetingStages} from '../../../postgres/types/NewMeetingPhase' import getPhase from '../../../utils/getPhase' import {DataLoaderWorker} from '../../graphql' import isValid from '../../isValid' @@ -13,7 +14,7 @@ import isValid from '../../isValid' * **sprint poker**: meeting members facilitated, voted discussed or reacted / total meeting members * **standup**: replied, commented or reacted / all members */ -const calculateEngagement = async (meeting: Meeting, dataLoader: DataLoaderWorker) => { +const calculateEngagement = async (meeting: AnyMeeting, dataLoader: DataLoaderWorker) => { const {id: meetingId, phases, meetingType, facilitatorUserId} = meeting if (meetingType === 'action') return undefined @@ -78,7 +79,7 @@ const calculateEngagement = async (meeting: Meeting, dataLoader: DataLoaderWorke } // Discussions can happen in many different stage types: discuss, ESTIMATE, reflect, RESPONSES - const stages = phases.flatMap(({stages}) => stages) + const stages = phases.flatMap(({stages}) => stages as NewMeetingStages[]) const discussionIds = stages .map((stage) => 'discussionId' in stage && stage.discussionId) .filter(isValid) as string[] diff --git a/packages/server/graphql/mutations/helpers/collectReactjis.ts b/packages/server/graphql/mutations/helpers/collectReactjis.ts index 99c3206b63f..5e51b14d1a4 100644 --- a/packages/server/graphql/mutations/helpers/collectReactjis.ts +++ b/packages/server/graphql/mutations/helpers/collectReactjis.ts @@ -1,15 +1,16 @@ -import Meeting from '../../../database/types/Meeting' import {getTeamPromptResponsesByMeetingId} from '../../../postgres/queries/getTeamPromptResponsesByMeetingIds' +import {AnyMeeting} from '../../../postgres/types/Meeting' +import {NewMeetingStages} from '../../../postgres/types/NewMeetingPhase' import {DataLoaderWorker} from '../../graphql' import isValid from '../../isValid' -const collectReactjis = async (meeting: Meeting, dataLoader: DataLoaderWorker) => { +const collectReactjis = async (meeting: AnyMeeting, dataLoader: DataLoaderWorker) => { const {id: meetingId, phases} = meeting const usedReactjis: Record = {} // Discussions can happen in many different stage types: discuss, ESTIMATE, reflect, RESPONSES - const stages = phases.flatMap(({stages}) => stages) + const stages = phases.flatMap(({stages}) => stages as NewMeetingStages[]) const discussionIds = stages .map((stage) => 'discussionId' in stage && stage.discussionId) .filter(isValid) as string[] diff --git a/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts b/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts index 098392fc6a6..44846ed791f 100644 --- a/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts +++ b/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts @@ -25,6 +25,7 @@ import UpdatesPhase from '../../../database/types/UpdatesPhase' import UpdatesStage from '../../../database/types/UpdatesStage' import getKysely from '../../../postgres/getKysely' import {MeetingTypeEnum} from '../../../postgres/types/Meeting' +import {NewMeetingPhase} from '../../../postgres/types/NewMeetingPhase' import isPhaseAvailable from '../../../utils/isPhaseAvailable' import {DataLoaderWorker} from '../../graphql' import {getFeatureTier} from '../../types/helpers/getFeatureTier' @@ -69,7 +70,7 @@ const getPastStageDurations = async (teamId: string) => { ) } -const createNewMeetingPhases = async ( +const createNewMeetingPhases = async ( facilitatorUserId: string, teamId: string, meetingId: string, @@ -162,7 +163,7 @@ const createNewMeetingPhases = async ( throw new Error(`Unhandled phaseType: ${phaseType}`) } }) - )) as [GenericMeetingPhase, ...GenericMeetingPhase[]] + )) as [T, ...T[]] primePhases(phases) await Promise.all(asyncSideEffects) return phases diff --git a/packages/server/graphql/mutations/helpers/endMeeting/sendNewMeetingSummary.ts b/packages/server/graphql/mutations/helpers/endMeeting/sendNewMeetingSummary.ts index 1b22cefe1b4..570f456e361 100644 --- a/packages/server/graphql/mutations/helpers/endMeeting/sendNewMeetingSummary.ts +++ b/packages/server/graphql/mutations/helpers/endMeeting/sendNewMeetingSummary.ts @@ -1,12 +1,12 @@ import getRethink from '../../../../database/rethinkDriver' -import Meeting from '../../../../database/types/Meeting' import getMailManager from '../../../../email/getMailManager' import newMeetingSummaryEmailCreator from '../../../../email/newMeetingSummaryEmailCreator' +import {AnyMeeting} from '../../../../postgres/types/Meeting' import {GQLContext} from '../../../graphql' import isValid from '../../../isValid' export default async function sendNewMeetingSummary( - newMeeting: Meeting, + newMeeting: AnyMeeting, context: Pick ) { const {id: meetingId, teamId, summarySentAt} = newMeeting diff --git a/packages/server/graphql/mutations/helpers/gatherInsights.ts b/packages/server/graphql/mutations/helpers/gatherInsights.ts index 159a7f75533..fed38a5f008 100644 --- a/packages/server/graphql/mutations/helpers/gatherInsights.ts +++ b/packages/server/graphql/mutations/helpers/gatherInsights.ts @@ -1,9 +1,9 @@ -import Meeting from '../../../database/types/Meeting' +import {AnyMeeting} from '../../../postgres/types/Meeting' import {DataLoaderWorker} from '../../graphql' import calculateEngagement from './calculateEngagement' import collectReactjis from './collectReactjis' -const gatherInsights = async (meeting: Meeting, dataLoader: DataLoaderWorker) => { +const gatherInsights = async (meeting: AnyMeeting, dataLoader: DataLoaderWorker) => { const [usedReactjis, engagement] = await Promise.all([ collectReactjis(meeting, dataLoader), calculateEngagement(meeting, dataLoader) diff --git a/packages/server/graphql/mutations/helpers/generateDiscussionSummary.ts b/packages/server/graphql/mutations/helpers/generateDiscussionSummary.ts index fe88bac549e..0c4943e6a7e 100644 --- a/packages/server/graphql/mutations/helpers/generateDiscussionSummary.ts +++ b/packages/server/graphql/mutations/helpers/generateDiscussionSummary.ts @@ -1,7 +1,7 @@ import {SubscriptionChannel} from '../../../../client/types/constEnums' import {PARABOL_AI_USER_ID} from '../../../../client/utils/constants' -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import updateDiscussions from '../../../postgres/queries/updateDiscussions' +import {RetrospectiveMeeting} from '../../../postgres/types/Meeting' import OpenAIServerManager from '../../../utils/OpenAIServerManager' import publish from '../../../utils/publish' import {DataLoaderWorker} from '../../graphql' @@ -9,12 +9,12 @@ import canAccessAISummary from './canAccessAISummary' const generateDiscussionSummary = async ( discussionId: string, - meeting: MeetingRetrospective, + meeting: RetrospectiveMeeting, dataLoader: DataLoaderWorker ) => { const {id: meetingId, endedAt, facilitatorUserId, teamId} = meeting const [facilitator, team] = await Promise.all([ - dataLoader.get('users').loadNonNull(facilitatorUserId), + dataLoader.get('users').loadNonNull(facilitatorUserId!), dataLoader.get('teams').loadNonNull(teamId) ]) const isAISummaryAccessible = await canAccessAISummary( diff --git a/packages/server/graphql/mutations/helpers/generateGroups.ts b/packages/server/graphql/mutations/helpers/generateGroups.ts index a37536bb97c..068e3b176d0 100644 --- a/packages/server/graphql/mutations/helpers/generateGroups.ts +++ b/packages/server/graphql/mutations/helpers/generateGroups.ts @@ -1,7 +1,6 @@ import {SubscriptionChannel} from '../../../../client/types/constEnums' import getRethink from '../../../database/rethinkDriver' -import {AutogroupReflectionGroupType} from '../../../database/types/MeetingRetrospective' -import {RetroReflection} from '../../../postgres/types' +import {AutogroupReflectionGroupType, RetroReflection} from '../../../postgres/types' import {Logger} from '../../../utils/Logger' import OpenAIServerManager from '../../../utils/OpenAIServerManager' import {analytics} from '../../../utils/analytics/analytics' diff --git a/packages/server/graphql/mutations/helpers/generateStandupMeetingSummary.ts b/packages/server/graphql/mutations/helpers/generateStandupMeetingSummary.ts index ac20e9b106c..7b84ec54f00 100644 --- a/packages/server/graphql/mutations/helpers/generateStandupMeetingSummary.ts +++ b/packages/server/graphql/mutations/helpers/generateStandupMeetingSummary.ts @@ -1,15 +1,15 @@ -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {getTeamPromptResponsesByMeetingId} from '../../../postgres/queries/getTeamPromptResponsesByMeetingIds' +import {TeamPromptMeeting} from '../../../postgres/types/Meeting' import OpenAIServerManager from '../../../utils/OpenAIServerManager' import {DataLoaderWorker} from '../../graphql' import canAccessAISummary from './canAccessAISummary' const generateStandupMeetingSummary = async ( - meeting: MeetingTeamPrompt, + meeting: TeamPromptMeeting, dataLoader: DataLoaderWorker ) => { const [facilitator, team] = await Promise.all([ - dataLoader.get('users').loadNonNull(meeting.facilitatorUserId), + dataLoader.get('users').loadNonNull(meeting.facilitatorUserId!), dataLoader.get('teams').loadNonNull(meeting.teamId) ]) const isAISummaryAccessible = await canAccessAISummary( diff --git a/packages/server/graphql/mutations/helpers/handleCompletedStage.ts b/packages/server/graphql/mutations/helpers/handleCompletedStage.ts index a6aa7267f88..0fec6c43d1d 100644 --- a/packages/server/graphql/mutations/helpers/handleCompletedStage.ts +++ b/packages/server/graphql/mutations/helpers/handleCompletedStage.ts @@ -4,9 +4,8 @@ import {r} from 'rethinkdb-ts' import groupReflections from '../../../../client/utils/smartGroup/groupReflections' import DiscussStage from '../../../database/types/DiscussStage' import GenericMeetingStage from '../../../database/types/GenericMeetingStage' -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import getKysely from '../../../postgres/getKysely' -import {AnyMeeting} from '../../../postgres/types/Meeting' +import {AnyMeeting, RetrospectiveMeeting} from '../../../postgres/types/Meeting' import {DataLoaderWorker} from '../../graphql' import addAIGeneratedContentToThreads from './addAIGeneratedContentToThreads' import addDiscussionTopics from './addDiscussionTopics' @@ -24,7 +23,7 @@ import removeEmptyReflections from './removeEmptyReflections' */ const handleCompletedRetrospectiveStage = async ( stage: GenericMeetingStage, - meeting: MeetingRetrospective, + meeting: RetrospectiveMeeting, dataLoader: DataLoaderWorker ) => { const pg = getKysely() @@ -72,7 +71,7 @@ const handleCompletedRetrospectiveStage = async ( .run() data.meeting = meeting // dont await for the OpenAI API response - generateDiscussionPrompt(meeting.id, teamId, dataLoader, facilitatorUserId) + generateDiscussionPrompt(meeting.id, teamId, dataLoader, facilitatorUserId!) } return {[stage.phaseType]: data} @@ -116,7 +115,7 @@ const handleCompletedStage = async ( dataLoader: DataLoaderWorker ) => { if (meeting.meetingType === 'retrospective') { - return handleCompletedRetrospectiveStage(stage, meeting as MeetingRetrospective, dataLoader) + return handleCompletedRetrospectiveStage(stage, meeting, dataLoader) } return {} } diff --git a/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts index b96aec374fc..45459007256 100644 --- a/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts @@ -3,11 +3,10 @@ import makeAppURL from 'parabol-client/utils/makeAppURL' import findStageById from 'parabol-client/utils/meetings/findStageById' import {phaseLabelLookup} from 'parabol-client/utils/meetings/lookups' import appOrigin from '../../../../appOrigin' -import Meeting from '../../../../database/types/Meeting' import {IntegrationProviderMSTeams as IIntegrationProviderMSTeams} from '../../../../postgres/queries/getIntegrationProvidersByIds' import {SlackNotification, Team} from '../../../../postgres/types' import IUser from '../../../../postgres/types/IUser' -import {MeetingTypeEnum} from '../../../../postgres/types/Meeting' +import {AnyMeeting, MeetingTypeEnum} from '../../../../postgres/types/Meeting' import MSTeamsServerManager from '../../../../utils/MSTeamsServerManager' import {analytics} from '../../../../utils/analytics/analytics' import sendToSentry from '../../../../utils/sendToSentry' @@ -360,7 +359,7 @@ function GenerateACMeetingTitle(meetingTitle: string) { return titleTextBlock } -function GenerateACMeetingAndTeamsDetails(team: Team, meeting: Meeting) { +function GenerateACMeetingAndTeamsDetails(team: Team, meeting: AnyMeeting) { 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 62eb866722c..857316161ff 100644 --- a/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts @@ -4,11 +4,10 @@ import makeAppURL from 'parabol-client/utils/makeAppURL' import findStageById from 'parabol-client/utils/meetings/findStageById' import {phaseLabelLookup} from 'parabol-client/utils/meetings/lookups' import appOrigin from '../../../../appOrigin' -import Meeting from '../../../../database/types/Meeting' import {IntegrationProviderMattermost as IIntegrationProviderMattermost} from '../../../../postgres/queries/getIntegrationProvidersByIds' import {SlackNotification, Team} from '../../../../postgres/types' import IUser from '../../../../postgres/types/IUser' -import {MeetingTypeEnum} from '../../../../postgres/types/Meeting' +import {AnyMeeting, MeetingTypeEnum} from '../../../../postgres/types/Meeting' import MattermostServerManager from '../../../../utils/MattermostServerManager' import {analytics} from '../../../../utils/analytics/analytics' import {toEpochSeconds} from '../../../../utils/epochTime' @@ -47,7 +46,7 @@ const notifyMattermost = async ( return 'success' } -const makeEndMeetingButtons = (meeting: Meeting) => { +const makeEndMeetingButtons = (meeting: AnyMeeting) => { const {id: meetingId} = meeting const searchParams = { utm_source: 'mattermost summary', @@ -94,7 +93,7 @@ type MattermostNotificationAuth = IntegrationProviderMattermost & {userId: strin const makeTeamPromptStartMeetingNotification = ( team: Team, - meeting: Meeting, + meeting: AnyMeeting, meetingUrl: string ) => { return [ @@ -119,7 +118,11 @@ const makeTeamPromptStartMeetingNotification = ( ] } -const makeGenericStartMeetingNotification = (team: Team, meeting: Meeting, meetingUrl: string) => { +const makeGenericStartMeetingNotification = ( + team: Team, + meeting: AnyMeeting, + meetingUrl: string +) => { return [ makeFieldsAttachment( [ @@ -149,7 +152,7 @@ const makeGenericStartMeetingNotification = (team: Team, meeting: Meeting, meeti const makeStartMeetingNotificationLookup: Record< MeetingTypeEnum, - (team: Team, meeting: Meeting, meetingUrl: string) => ReturnType[] + (team: Team, meeting: AnyMeeting, 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 e1bd2c9287c..6223b769ba8 100644 --- a/packages/server/graphql/mutations/helpers/notifications/NotificationIntegrationHelper.ts +++ b/packages/server/graphql/mutations/helpers/notifications/NotificationIntegrationHelper.ts @@ -1,6 +1,6 @@ -import Meeting from '../../../../database/types/Meeting' import {Team, TeamPromptResponse} from '../../../../postgres/types' import User from '../../../../postgres/types/IUser' +import {AnyMeeting} from '../../../../postgres/types/Meeting' export type NotifyResponse = | 'success' | { @@ -10,24 +10,24 @@ export type NotifyResponse = } export type NotificationIntegration = { - startMeeting(meeting: Meeting, team: Team, user: User): Promise - updateMeeting?(meeting: Meeting, team: Team, user: User): Promise + startMeeting(meeting: AnyMeeting, team: Team, user: User): Promise + updateMeeting?(meeting: AnyMeeting, team: Team, user: User): Promise endMeeting( - meeting: Meeting, + meeting: AnyMeeting, team: Team, user: User, standupResponses: {user: User; response: TeamPromptResponse}[] | null ): Promise startTimeLimit( scheduledEndTime: Date, - meeting: Meeting, + meeting: AnyMeeting, team: Team, user: User ): Promise - endTimeLimit(meeting: Meeting, team: Team, user: User): Promise + endTimeLimit(meeting: AnyMeeting, team: Team, user: User): Promise integrationUpdated(user: User): Promise standupResponseSubmitted( - meeting: Meeting, + meeting: AnyMeeting, team: Team, user: User, response: TeamPromptResponse diff --git a/packages/server/graphql/mutations/helpers/notifications/Notifier.ts b/packages/server/graphql/mutations/helpers/notifications/Notifier.ts index 6ba69226bed..e01c3e88709 100644 --- a/packages/server/graphql/mutations/helpers/notifications/Notifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/Notifier.ts @@ -61,14 +61,14 @@ export const createNotifier = (loader: NotificationIntegrationLoader): Notifier async startMeeting(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { const {meeting, team, user} = await loadMeetingTeam(dataLoader, meetingId, teamId) if (!meeting || !team || !user) return - const notifiers = await loader(dataLoader, team.id, meeting.facilitatorUserId, 'meetingStart') + const notifiers = await loader(dataLoader, team.id, meeting.facilitatorUserId!, 'meetingStart') notifiers.forEach((notifier) => notifier.startMeeting(meeting, team, user)) }, async updateMeeting(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { const {meeting, team, user} = await loadMeetingTeam(dataLoader, meetingId, teamId) if (!meeting || !team || !user) return - const notifiers = await loader(dataLoader, team.id, meeting.facilitatorUserId, 'meetingStart') + const notifiers = await loader(dataLoader, team.id, meeting.facilitatorUserId!, 'meetingStart') notifiers.forEach((notifier) => notifier.updateMeeting?.(meeting, team, user)) }, @@ -85,7 +85,7 @@ export const createNotifier = (loader: NotificationIntegrationLoader): Notifier } }) ) - const notifiers = await loader(dataLoader, team.id, meeting.facilitatorUserId, 'meetingEnd') + const notifiers = await loader(dataLoader, team.id, meeting.facilitatorUserId!, 'meetingEnd') notifiers.forEach((notifier) => notifier.endMeeting(meeting, team, user, standupResponses)) }, @@ -100,7 +100,7 @@ export const createNotifier = (loader: NotificationIntegrationLoader): Notifier const notifiers = await loader( dataLoader, team.id, - meeting.facilitatorUserId, + meeting.facilitatorUserId!, 'MEETING_STAGE_TIME_LIMIT_START' ) notifiers.forEach((notifier) => notifier.startTimeLimit(scheduledEndTime, meeting, team, user)) @@ -112,7 +112,7 @@ export const createNotifier = (loader: NotificationIntegrationLoader): Notifier const notifiers = await loader( dataLoader, team.id, - meeting.facilitatorUserId, + meeting.facilitatorUserId!, 'MEETING_STAGE_TIME_LIMIT_END' ) notifiers.forEach((notifier) => notifier.endTimeLimit(meeting, team, user)) @@ -141,7 +141,7 @@ export const createNotifier = (loader: NotificationIntegrationLoader): Notifier const notifiers = await loader( dataLoader, team.id, - meeting.facilitatorUserId, + meeting.facilitatorUserId!, 'STANDUP_RESPONSE_SUBMITTED' ) notifiers.forEach((notifier) => diff --git a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts index 457220ef98a..5a10f159e0e 100644 --- a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts @@ -7,7 +7,6 @@ import TeamPromptResponseId from '../../../../../client/shared/gqlIds/TeamPrompt import {ErrorResponse, PostMessageResponse} from '../../../../../client/utils/SlackManager' import appOrigin from '../../../../appOrigin' import getRethink, {RethinkSchema} from '../../../../database/rethinkDriver' -import Meeting from '../../../../database/types/Meeting' import SlackAuth from '../../../../database/types/SlackAuth' import {SlackNotificationAuth} from '../../../../dataloader/integrationAuthLoaders' import getKysely from '../../../../postgres/getKysely' @@ -91,7 +90,7 @@ const notifySlack = async ( return res } -const makeEndMeetingButtons = (meeting: Meeting) => { +const makeEndMeetingButtons = (meeting: AnyMeeting) => { const {id: meetingId} = meeting const searchParams = { utm_source: 'slack summary', @@ -136,11 +135,11 @@ const makeEndMeetingButtons = (meeting: Meeting) => { const createTeamSectionContent = (team: Team) => `*Team:*\n${team.name}` -const createMeetingSectionContent = (meeting: Meeting) => `*Meeting:*\n${meeting.name}` +const createMeetingSectionContent = (meeting: AnyMeeting) => `*Meeting:*\n${meeting.name}` const makeTeamPromptStartMeetingNotification = ( team: Team, - meeting: Meeting, + meeting: AnyMeeting, meetingUrl: string ): SlackNotificationMessage => { const title = `*${meeting.name}* is open :speech_balloon: ` @@ -155,7 +154,7 @@ const makeTeamPromptStartMeetingNotification = ( const makeGenericStartMeetingNotification = ( team: Team, - meeting: Meeting, + meeting: AnyMeeting, meetingUrl: string ): SlackNotificationMessage => { const title = 'Meeting started :wave: ' @@ -170,7 +169,7 @@ const makeGenericStartMeetingNotification = ( const makeStartMeetingNotificationLookup: Record< MeetingTypeEnum, - (team: Team, meeting: Meeting, meetingUrl: string) => SlackNotificationMessage + (team: Team, meeting: AnyMeeting, meetingUrl: string) => SlackNotificationMessage > = { teamPrompt: makeTeamPromptStartMeetingNotification, action: makeGenericStartMeetingNotification, @@ -183,7 +182,7 @@ const addStandupResponsesToThread = async ( standupResponses: Array<{user: User; response: TeamPromptResponse}> | null, team: Team, user: User, - meeting: Meeting, + meeting: AnyMeeting, notificationChannel: NotificationChannel ) => { if (!standupResponses || standupResponses.length === 0) { @@ -361,7 +360,11 @@ export const SlackSingleChannelNotifier: NotificationIntegrationHelper { // Order of slack auth is: diff --git a/packages/server/graphql/mutations/helpers/notifications/getSummaryText.ts b/packages/server/graphql/mutations/helpers/notifications/getSummaryText.ts index d3522fce222..49ea47b1c85 100644 --- a/packages/server/graphql/mutations/helpers/notifications/getSummaryText.ts +++ b/packages/server/graphql/mutations/helpers/notifications/getSummaryText.ts @@ -1,16 +1,15 @@ import relativeDate from 'parabol-client/utils/date/relativeDate' import plural from 'parabol-client/utils/plural' -import Meeting from '../../../../database/types/Meeting' -import {isMeetingAction} from '../../../../database/types/MeetingAction' -import {isMeetingPoker} from '../../../../database/types/MeetingPoker' -import {isMeetingRetrospective} from '../../../../database/types/MeetingRetrospective' -import {isMeetingTeamPrompt} from '../../../../database/types/MeetingTeamPrompt' import {getTeamPromptResponsesByMeetingId} from '../../../../postgres/queries/getTeamPromptResponsesByMeetingIds' +import {AnyMeeting} from '../../../../postgres/types/Meeting' import sendToSentry from '../../../../utils/sendToSentry' -const getSummaryText = async (meeting: Meeting) => { - if (isMeetingRetrospective(meeting)) { - const {commentCount = 0, reflectionCount = 0, topicCount = 0, taskCount = 0} = meeting +const getSummaryText = async (meeting: AnyMeeting) => { + if (meeting.meetingType === 'retrospective') { + const commentCount = meeting.commentCount || 0 + const reflectionCount = meeting.reflectionCount || 0 + const topicCount = meeting.topicCount || 0 + const taskCount = meeting.taskCount || 0 const hasNonZeroStat = commentCount || reflectionCount || topicCount || taskCount if (!hasNonZeroStat && meeting.summary) { sendToSentry(new Error('No stats found for meeting'), { @@ -24,8 +23,11 @@ const getSummaryText = async (meeting: Meeting) => { commentCount, 'comment' )} and created ${taskCount} ${plural(taskCount, 'task')}.` - } else if (isMeetingAction(meeting)) { - const {createdAt, endedAt, agendaItemCount = 0, commentCount = 0, taskCount = 0} = meeting + } else if (meeting.meetingType === 'action') { + const agendaItemCount = meeting.agendaItemCount || 0 + const commentCount = meeting.commentCount || 0 + const taskCount = meeting.taskCount || 0 + const {createdAt, endedAt} = meeting const meetingDuration = relativeDate(createdAt, { now: endedAt, max: 2, @@ -39,21 +41,22 @@ const getSummaryText = async (meeting: Meeting) => { commentCount, 'comment' )}.` - } else if (isMeetingTeamPrompt(meeting)) { + } else if (meeting.meetingType === 'teamPrompt') { const responseCount = (await getTeamPromptResponsesByMeetingId(meeting.id)).filter( (response) => !!response.plaintextContent ).length // :TODO: (jmtaber129): Add additional stats here. return `Your team shared ${responseCount} ${plural(responseCount, 'response', 'responses')}.` - } else if (isMeetingPoker(meeting)) { - const {storyCount = 0, commentCount = 0} = meeting + } else if (meeting.meetingType === 'poker') { + const storyCount = meeting.storyCount || 0 + const commentCount = meeting.commentCount || 0 return `You voted on ${storyCount} ${plural( storyCount, 'story', 'stories' )} and added ${commentCount} ${plural(commentCount, 'comment')}.` } else { - throw new Error(`Meeting type not supported ${meeting.meetingType}`) + throw new Error(`Meeting type not supported ${(meeting as any).meetingType}`) } } diff --git a/packages/server/graphql/mutations/helpers/pushEstimateToGitHub.ts b/packages/server/graphql/mutations/helpers/pushEstimateToGitHub.ts index 039c216bd81..891895d7a71 100644 --- a/packages/server/graphql/mutations/helpers/pushEstimateToGitHub.ts +++ b/packages/server/graphql/mutations/helpers/pushEstimateToGitHub.ts @@ -6,7 +6,6 @@ import {SprintPokerDefaults} from 'parabol-client/types/constEnums' import makeAppURL from 'parabol-client/utils/makeAppURL' import {isNotNull} from 'parabol-client/utils/predicates' import appOrigin from '../../../appOrigin' -import MeetingPoker from '../../../database/types/MeetingPoker' import { AddCommentMutation, AddCommentMutationVariables, @@ -50,6 +49,9 @@ const pushEstimateToGitHub = async ( return new Error('Meeting does not exist') } + if (meeting.meetingType !== 'poker') { + return new Error('Not a poker meeting') + } const githubIntegration = task.integration as Extract< typeof task.integration, {service: 'github'} @@ -150,7 +152,7 @@ const pushEstimateToGitHub = async ( if (!matchingLabel) { let color = PALETTE.GRAPE_500.slice(1) if (meeting) { - const {templateRefId} = meeting as MeetingPoker + const {templateRefId} = meeting const templateRef = await dataLoader.get('templateRefs').loadNonNull(templateRefId) const {dimensions} = templateRef const dimensionRef = dimensions.find((dimension) => dimension.name === dimensionName) diff --git a/packages/server/graphql/mutations/helpers/removeEmptyReflections.ts b/packages/server/graphql/mutations/helpers/removeEmptyReflections.ts index e11080a9bb6..cdccfb40f76 100644 --- a/packages/server/graphql/mutations/helpers/removeEmptyReflections.ts +++ b/packages/server/graphql/mutations/helpers/removeEmptyReflections.ts @@ -1,9 +1,9 @@ import extractTextFromDraftString from 'parabol-client/utils/draftjs/extractTextFromDraftString' -import Meeting from '../../../database/types/Meeting' import {DataLoaderInstance} from '../../../dataloader/RootDataLoader' import getKysely from '../../../postgres/getKysely' +import {AnyMeeting} from '../../../postgres/types/Meeting' -const removeEmptyReflections = async (meeting: Meeting, dataLoader: DataLoaderInstance) => { +const removeEmptyReflections = async (meeting: AnyMeeting, dataLoader: DataLoaderInstance) => { const pg = getKysely() const {id: meetingId} = meeting const reflections = await dataLoader.get('retroReflectionsByMeetingId').load(meetingId) diff --git a/packages/server/graphql/mutations/helpers/removeStagesFromMeetings.ts b/packages/server/graphql/mutations/helpers/removeStagesFromMeetings.ts index 64f1dda66a0..39c5546998e 100644 --- a/packages/server/graphql/mutations/helpers/removeStagesFromMeetings.ts +++ b/packages/server/graphql/mutations/helpers/removeStagesFromMeetings.ts @@ -40,7 +40,7 @@ const removeStagesFromMeetings = async ( nextStage.viewCount = nextStage.viewCount ? nextStage.viewCount + 1 : 1 nextStage.isNavigable = true } - const stageIdx = stages.indexOf(stage) + const stageIdx = (stages as any).indexOf(stage) stages.splice(stageIdx, 1) } } diff --git a/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts b/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts index fa01659f21f..8a9f2e8ef0e 100644 --- a/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts @@ -1,6 +1,7 @@ import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import generateUID from '../../../generateUID' -import {MeetingTypeEnum} from '../../../postgres/types/Meeting' +import {MeetingTypeEnum, RetrospectiveMeeting} from '../../../postgres/types/Meeting' +import {RetroMeetingPhase} from '../../../postgres/types/NewMeetingPhase' import {DataLoaderWorker} from '../../graphql' import createNewMeetingPhases from './createNewMeetingPhases' @@ -30,7 +31,7 @@ const safeCreateRetrospective = async ( const {showConversionModal} = organization const meetingId = generateUID() - const phases = await createNewMeetingPhases( + const phases = await createNewMeetingPhases( facilitatorUserId, teamId, meetingId, @@ -46,7 +47,7 @@ const safeCreateRetrospective = async ( showConversionModal, ...meetingSettings, name - }) + }) as RetrospectiveMeeting } export default safeCreateRetrospective diff --git a/packages/server/graphql/mutations/helpers/safeCreateTeamPrompt.ts b/packages/server/graphql/mutations/helpers/safeCreateTeamPrompt.ts index 7416d9d78dc..7c702494396 100644 --- a/packages/server/graphql/mutations/helpers/safeCreateTeamPrompt.ts +++ b/packages/server/graphql/mutations/helpers/safeCreateTeamPrompt.ts @@ -3,7 +3,7 @@ import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import TeamPromptResponsesPhase from '../../../database/types/TeamPromptResponsesPhase' import generateUID from '../../../generateUID' import getKysely from '../../../postgres/getKysely' -import {MeetingTypeEnum} from '../../../postgres/types/Meeting' +import {MeetingTypeEnum, TeamPromptMeeting} from '../../../postgres/types/Meeting' import {DataLoaderWorker} from '../../graphql' import {primePhases} from './createNewMeetingPhases' @@ -52,7 +52,7 @@ const safeCreateTeamPrompt = async ( facilitatorUserId: facilitatorId, meetingPrompt: DEFAULT_PROMPT, // :TODO: (jmtaber129): Get this from meeting settings. ...meetingOverrideProps - }) + }) as TeamPromptMeeting } export default safeCreateTeamPrompt diff --git a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts index 1af261de253..58ca5c03b34 100644 --- a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts @@ -4,9 +4,9 @@ import getMeetingPhase from 'parabol-client/utils/getMeetingPhase' import findStageById from 'parabol-client/utils/meetings/findStageById' import {checkTeamsLimit} from '../../../billing/helpers/teamLimitsCheck' import getRethink from '../../../database/rethinkDriver' -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import TimelineEventRetroComplete from '../../../database/types/TimelineEventRetroComplete' import getKysely from '../../../postgres/getKysely' +import {RetrospectiveMeeting} from '../../../postgres/types/Meeting' import removeSuggestedAction from '../../../safeMutations/removeSuggestedAction' import {Logger} from '../../../utils/Logger' import RecallAIServerManager from '../../../utils/RecallAIServerManager' @@ -33,14 +33,14 @@ const getTranscription = async (recallBotId?: string | null) => { return await manager.getBotTranscript(recallBotId) } -const summarizeRetroMeeting = async (meeting: MeetingRetrospective, context: InternalContext) => { +const summarizeRetroMeeting = async (meeting: RetrospectiveMeeting, context: InternalContext) => { const {dataLoader} = context const {id: meetingId, phases, facilitatorUserId, teamId, recallBotId} = meeting const r = await getRethink() const [reflectionGroups, reflections, sentimentScore] = await Promise.all([ dataLoader.get('retroReflectionGroupsByMeetingId').load(meetingId), dataLoader.get('retroReflectionsByMeetingId').load(meetingId), - generateWholeMeetingSentimentScore(meetingId, facilitatorUserId, dataLoader) + generateWholeMeetingSentimentScore(meetingId, facilitatorUserId!, dataLoader) ]) const discussPhase = getPhase(phases, 'discuss') const {stages} = discussPhase @@ -48,7 +48,7 @@ const summarizeRetroMeeting = async (meeting: MeetingRetrospective, context: Int const reflectionGroupIds = reflectionGroups.map(({id}) => id) const [summary, transcription] = await Promise.all([ - generateWholeMeetingSummary(discussionIds, meetingId, teamId, facilitatorUserId, dataLoader), + generateWholeMeetingSummary(discussionIds, meetingId, teamId, facilitatorUserId!, dataLoader), getTranscription(recallBotId) ]) const commentCounts = ( @@ -93,7 +93,7 @@ const safeEndRetrospective = async ({ context, now }: { - meeting: MeetingRetrospective + meeting: RetrospectiveMeeting context: InternalContext now: Date }) => { @@ -115,7 +115,7 @@ const safeEndRetrospective = async ({ const phase = getMeetingPhase(phases) const insights = await gatherInsights(meeting, dataLoader) - const completedRetrospective = (await r + const completedRetrospective = await r .table('NewMeeting') .get(meetingId) .update( @@ -127,14 +127,18 @@ const safeEndRetrospective = async ({ {returnChanges: true} )('changes')(0)('new_val') .default(null) - .run()) as unknown as MeetingRetrospective + .run() if (!completedRetrospective) { return standardError(new Error('Completed retrospective meeting does not exist'), { userId: viewerId }) } - + if (completedRetrospective.meetingType !== 'retrospective') { + return standardError(new Error('Meeting type is not retrospective'), { + userId: viewerId + }) + } // remove any empty tasks const {templateId} = completedRetrospective const [meetingMembers, team, teamMembers, removedTaskIds, template] = await Promise.all([ diff --git a/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts b/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts index e88ed41daf1..e44d2ee81d2 100644 --- a/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts +++ b/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts @@ -1,10 +1,10 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {checkTeamsLimit} from '../../../billing/helpers/teamLimitsCheck' import getRethink, {ParabolR} from '../../../database/rethinkDriver' -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import TimelineEventTeamPromptComplete from '../../../database/types/TimelineEventTeamPromptComplete' import getKysely from '../../../postgres/getKysely' import {getTeamPromptResponsesByMeetingId} from '../../../postgres/queries/getTeamPromptResponsesByMeetingIds' +import {TeamPromptMeeting} from '../../../postgres/types/Meeting' import {Logger} from '../../../utils/Logger' import {analytics} from '../../../utils/analytics/analytics' import publish, {SubOptions} from '../../../utils/publish' @@ -17,7 +17,7 @@ import {IntegrationNotifier} from './notifications/IntegrationNotifier' import updateQualAIMeetingsCount from './updateQualAIMeetingsCount' import updateTeamInsights from './updateTeamInsights' -const summarizeTeamPrompt = async (meeting: MeetingTeamPrompt, context: InternalContext) => { +const summarizeTeamPrompt = async (meeting: TeamPromptMeeting, context: InternalContext) => { const {dataLoader} = context const r = await getRethink() @@ -51,7 +51,7 @@ const safeEndTeamPrompt = async ({ context, subOptions }: { - meeting: MeetingTeamPrompt + meeting: TeamPromptMeeting now: Date viewerId?: string r: ParabolR @@ -66,7 +66,7 @@ const safeEndTeamPrompt = async ({ // RESOLUTION const insights = await gatherInsights(meeting, dataLoader) - const completedTeamPrompt = (await r + const completedTeamPrompt = await r .table('NewMeeting') .get(meetingId) .update( @@ -77,7 +77,7 @@ const safeEndTeamPrompt = async ({ {returnChanges: true} )('changes')(0)('new_val') .default(null) - .run()) as unknown as MeetingTeamPrompt + .run() if (!completedTeamPrompt) { return standardError(new Error('Completed team prompt meeting does not exist'), { @@ -85,6 +85,10 @@ const safeEndTeamPrompt = async ({ }) } + if (completedTeamPrompt.meetingType !== 'teamPrompt') { + return standardError(new Error('Meeting is not a team prompt'), {userId: viewerId}) + } + const [meetingMembers, team, teamMembers, responses] = await Promise.all([ dataLoader.get('meetingMembersByMeetingId').load(meetingId), dataLoader.get('teams').loadNonNull(teamId), diff --git a/packages/server/graphql/mutations/helpers/sendPokerMeetingRevoteEvent.ts b/packages/server/graphql/mutations/helpers/sendPokerMeetingRevoteEvent.ts index 9db1d61ab85..88c74280788 100644 --- a/packages/server/graphql/mutations/helpers/sendPokerMeetingRevoteEvent.ts +++ b/packages/server/graphql/mutations/helpers/sendPokerMeetingRevoteEvent.ts @@ -1,11 +1,11 @@ -import Meeting from '../../../database/types/Meeting' import MeetingMember from '../../../database/types/MeetingMember' import {TeamMember} from '../../../postgres/types' +import {AnyMeeting} from '../../../postgres/types/Meeting' import {analytics} from '../../../utils/analytics/analytics' import {DataLoaderWorker} from '../../graphql' const sendPokerMeetingRevoteEvent = async ( - meeting: Meeting, + meeting: AnyMeeting, teamMembers: TeamMember[], meetingMembers: MeetingMember[], dataLoader: DataLoaderWorker diff --git a/packages/server/graphql/mutations/helpers/updateReflectionLocation/removeReflectionFromGroup.ts b/packages/server/graphql/mutations/helpers/updateReflectionLocation/removeReflectionFromGroup.ts index 389ea399d3d..d82fb1c99f0 100644 --- a/packages/server/graphql/mutations/helpers/updateReflectionLocation/removeReflectionFromGroup.ts +++ b/packages/server/graphql/mutations/helpers/updateReflectionLocation/removeReflectionFromGroup.ts @@ -1,21 +1,17 @@ import getGroupSmartTitle from 'parabol-client/utils/smartGroup/getGroupSmartTitle' import dndNoise from '../../../../../client/utils/dndNoise' -import getRethink from '../../../../database/rethinkDriver' -import MeetingRetrospective from '../../../../database/types/MeetingRetrospective' import ReflectionGroup from '../../../../database/types/ReflectionGroup' import getKysely from '../../../../postgres/getKysely' import {GQLContext} from '../../../graphql' import updateSmartGroupTitle from './updateSmartGroupTitle' const removeReflectionFromGroup = async (reflectionId: string, {dataLoader}: GQLContext) => { - const r = await getRethink() const pg = getKysely() const reflection = await dataLoader.get('retroReflections').load(reflectionId) if (!reflection) throw new Error('Reflection not found') const {reflectionGroupId: oldReflectionGroupId, meetingId, promptId} = reflection - const [meetingReflectionGroups, meeting] = await Promise.all([ - dataLoader.get('retroReflectionGroupsByMeetingId').load(meetingId), - dataLoader.get('newMeetings').load(meetingId) + const [meetingReflectionGroups] = await Promise.all([ + dataLoader.get('retroReflectionGroupsByMeetingId').load(meetingId) ]) dataLoader.get('retroReflectionGroupsByMeetingId').clear(meetingId) dataLoader.get('retroReflectionGroups').clearAll() @@ -52,14 +48,11 @@ const removeReflectionFromGroup = async (reflectionId: string, {dataLoader}: GQL reflectionGroupId }) .where('id', '=', reflectionId) - .execute(), - r.table('NewMeeting').get(meetingId).update({nextAutoGroupThreshold: null}).run() + .execute() ]) // mutates the dataloader response reflection.sortOrder = 0 reflection.reflectionGroupId = reflectionGroupId - const retroMeeting = meeting as MeetingRetrospective - retroMeeting.nextAutoGroupThreshold = null const oldReflections = await dataLoader .get('retroReflectionsByGroupId') .load(oldReflectionGroupId) diff --git a/packages/server/graphql/mutations/joinMeeting.ts b/packages/server/graphql/mutations/joinMeeting.ts index c95582aeea1..99f2e3a61ab 100644 --- a/packages/server/graphql/mutations/joinMeeting.ts +++ b/packages/server/graphql/mutations/joinMeeting.ts @@ -6,8 +6,6 @@ import getRethink from '../../database/rethinkDriver' import ActionMeetingMember from '../../database/types/ActionMeetingMember' import CheckInStage from '../../database/types/CheckInStage' import {NewMeetingPhaseTypeEnum} from '../../database/types/GenericMeetingPhase' -import Meeting from '../../database/types/Meeting' -import MeetingRetrospective from '../../database/types/MeetingRetrospective' import PokerMeetingMember from '../../database/types/PokerMeetingMember' import RetroMeetingMember from '../../database/types/RetroMeetingMember' import TeamPromptMeetingMember from '../../database/types/TeamPromptMeetingMember' @@ -15,6 +13,7 @@ import TeamPromptResponseStage from '../../database/types/TeamPromptResponseStag import UpdatesStage from '../../database/types/UpdatesStage' import getKysely from '../../postgres/getKysely' import {TeamMember} from '../../postgres/types' +import {AnyMeeting} from '../../postgres/types/Meeting' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' @@ -22,13 +21,13 @@ import publish from '../../utils/publish' import {GQLContext} from '../graphql' import JoinMeetingPayload from '../types/JoinMeetingPayload' -const createMeetingMember = (meeting: Meeting, teamMember: TeamMember) => { +const createMeetingMember = (meeting: AnyMeeting, teamMember: TeamMember) => { const {userId, teamId, isSpectatingPoker} = teamMember switch (meeting.meetingType) { case 'action': return new ActionMeetingMember({teamId, userId, meetingId: meeting.id}) case 'retrospective': - const {id: meetingId, totalVotes} = meeting as MeetingRetrospective + const {id: meetingId, totalVotes} = meeting return new RetroMeetingMember({ teamId, userId, diff --git a/packages/server/graphql/mutations/pokerAnnounceDeckHover.ts b/packages/server/graphql/mutations/pokerAnnounceDeckHover.ts index d4f09a698a6..e0b4dcbc794 100644 --- a/packages/server/graphql/mutations/pokerAnnounceDeckHover.ts +++ b/packages/server/graphql/mutations/pokerAnnounceDeckHover.ts @@ -2,7 +2,6 @@ import {GraphQLBoolean, GraphQLID, GraphQLNonNull} from 'graphql' import ms from 'ms' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' -import MeetingPoker from '../../database/types/MeetingPoker' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' import getRedis, {RedisPipelineResponse} from '../../utils/getRedis' @@ -35,10 +34,13 @@ const pokerAnnounceDeckHover = { const subOptions = {mutatorId, operationId} // AUTH - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingPoker + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) { return {error: {message: 'Meeting not found'}} } + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } const {endedAt, phases, meetingType, teamId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on the team'}} diff --git a/packages/server/graphql/mutations/pokerResetDimension.ts b/packages/server/graphql/mutations/pokerResetDimension.ts index d728eaa4b2f..c25a460db2c 100644 --- a/packages/server/graphql/mutations/pokerResetDimension.ts +++ b/packages/server/graphql/mutations/pokerResetDimension.ts @@ -2,7 +2,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' import {RValue} from '../../database/stricterR' -import MeetingPoker from '../../database/types/MeetingPoker' import updateStage from '../../database/updateStage' import removeMeetingTaskEstimates from '../../postgres/queries/removeMeetingTaskEstimates' import {getUserId, isTeamMember} from '../../utils/authorization' @@ -33,10 +32,13 @@ const pokerResetDimension = { const subOptions = {mutatorId, operationId} //AUTH - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingPoker + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) { return {error: {message: 'Meeting not found'}} } + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } const {endedAt, phases, meetingType, teamId, createdBy, facilitatorUserId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on the team'}} diff --git a/packages/server/graphql/mutations/pokerRevealVotes.ts b/packages/server/graphql/mutations/pokerRevealVotes.ts index c640ae4964b..50c9620df76 100644 --- a/packages/server/graphql/mutations/pokerRevealVotes.ts +++ b/packages/server/graphql/mutations/pokerRevealVotes.ts @@ -2,7 +2,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {PokerCards, SubscriptionChannel} from 'parabol-client/types/constEnums' import {RValue} from '../../database/stricterR' import EstimateUserScore from '../../database/types/EstimateUserScore' -import MeetingPoker from '../../database/types/MeetingPoker' import PokerMeetingMember from '../../database/types/PokerMeetingMember' import updateStage from '../../database/updateStage' import {getUserId, isTeamMember} from '../../utils/authorization' @@ -36,12 +35,15 @@ const pokerRevealVotes = { // fetch meetingMembers up here to reduce chance of race condition that a vote gets cast in between now & when we update the scores const [meetingMembers, meeting] = await Promise.all([ dataLoader.get('meetingMembersByMeetingId').load(meetingId), - dataLoader.get('newMeetings').load(meetingId) as Promise + dataLoader.get('newMeetings').load(meetingId) ]) if (!meeting) { return {error: {message: 'Meeting not found'}} } + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } const {endedAt, phases, meetingType, teamId, createdBy, facilitatorUserId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on the team'}} diff --git a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts index 259a838413a..f9c202d981d 100644 --- a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts +++ b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts @@ -4,8 +4,8 @@ import {CHECKIN, DISCUSS, GROUP, REFLECT, VOTE} from '../../../client/utils/cons import getRethink from '../../database/rethinkDriver' import DiscussPhase from '../../database/types/DiscussPhase' import GenericMeetingPhase from '../../database/types/GenericMeetingPhase' -import MeetingRetrospective from '../../database/types/MeetingRetrospective' import getKysely from '../../postgres/getKysely' +import {RetroMeetingPhase} from '../../postgres/types/NewMeetingPhase' import {getUserId} from '../../utils/authorization' import getPhase from '../../utils/getPhase' import publish from '../../utils/publish' @@ -34,7 +34,7 @@ const resetRetroMeetingToGroupStage = { // AUTH const viewerId = getUserId(authToken) - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingRetrospective + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) const {createdBy, facilitatorUserId, phases, meetingType} = meeting if (meetingType !== 'retrospective') { @@ -91,7 +91,7 @@ const resetRetroMeetingToGroupStage = { default: throw new Error(`Unhandled phaseType: ${phase.phaseType}`) } - }) + }) as RetroMeetingPhase[] primePhases(newPhases, resetToPhaseIndex) meeting.phases = newPhases diff --git a/packages/server/graphql/mutations/setTaskEstimate.ts b/packages/server/graphql/mutations/setTaskEstimate.ts index 08c2fa49895..1f2bdaea1fa 100644 --- a/packages/server/graphql/mutations/setTaskEstimate.ts +++ b/packages/server/graphql/mutations/setTaskEstimate.ts @@ -3,7 +3,6 @@ import {SprintPokerDefaults, SubscriptionChannel, Threshold} from 'parabol-clien import makeAppURL from 'parabol-client/utils/makeAppURL' import JiraProjectKeyId from '../../../client/shared/gqlIds/JiraProjectKeyId' import appOrigin from '../../appOrigin' -import MeetingPoker from '../../database/types/MeetingPoker' import TaskIntegrationJiraServer from '../../database/types/TaskIntegrationJiraServer' import JiraServerRestManager from '../../integrations/jiraServer/JiraServerRestManager' import {IntegrationProviderJiraServer} from '../../postgres/queries/getIntegrationProvidersByIds' @@ -69,10 +68,10 @@ const setTaskEstimate = { return {error: {message: 'Invalid dimension name'}} } - const {phases, meetingType, templateRefId, name: meetingName} = meeting as MeetingPoker - if (meetingType !== 'poker') { + if (meeting.meetingType !== 'poker') { return {error: {message: 'Invalid poker meeting'}} } + const {phases, templateRefId, name: meetingName} = meeting const templateRef = await dataLoader.get('templateRefs').loadNonNull(templateRefId) const {dimensions} = templateRef const dimensionRefIdx = dimensions.findIndex((dimension) => dimension.name === dimensionName) diff --git a/packages/server/graphql/mutations/startSprintPoker.ts b/packages/server/graphql/mutations/startSprintPoker.ts index 0fd4ecd04f9..ece86c98986 100644 --- a/packages/server/graphql/mutations/startSprintPoker.ts +++ b/packages/server/graphql/mutations/startSprintPoker.ts @@ -8,7 +8,8 @@ import generateUID from '../../generateUID' import getKysely from '../../postgres/getKysely' import updateMeetingTemplateLastUsedAt from '../../postgres/queries/updateMeetingTemplateLastUsedAt' import updateTeamByTeamId from '../../postgres/queries/updateTeamByTeamId' -import {MeetingTypeEnum} from '../../postgres/types/Meeting' +import {MeetingTypeEnum, PokerMeeting} from '../../postgres/types/Meeting' +import {PokerMeetingPhase} from '../../postgres/types/NewMeetingPhase' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' import getHashAndJSON from '../../utils/getHashAndJSON' @@ -123,7 +124,7 @@ export default { .default(0) .run() - const phases = await createNewMeetingPhases( + const phases = await createNewMeetingPhases( viewerId, teamId, meetingId, @@ -149,7 +150,7 @@ export default { facilitatorUserId: viewerId, templateId: selectedTemplateId, templateRefId - }) + }) as PokerMeeting const template = await dataLoader.get('meetingTemplates').load(selectedTemplateId) await Promise.all([ diff --git a/packages/server/graphql/mutations/updateAzureDevOpsDimensionField.ts b/packages/server/graphql/mutations/updateAzureDevOpsDimensionField.ts index b8a5fd2245c..fda78fffe3c 100644 --- a/packages/server/graphql/mutations/updateAzureDevOpsDimensionField.ts +++ b/packages/server/graphql/mutations/updateAzureDevOpsDimensionField.ts @@ -1,6 +1,5 @@ import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import MeetingPoker from '../../database/types/MeetingPoker' import upsertAzureDevOpsDimensionFieldMap, { AzureDevOpsFieldMapProps } from '../../postgres/queries/upsertAzureDevOpsDimensionFieldMap' @@ -67,7 +66,10 @@ const updateAzureDevOpsDimensionField = { if (!meeting) { return {error: {message: 'Invalid meetingId'}} } - const {teamId, templateRefId} = meeting as MeetingPoker + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } + const {teamId, templateRefId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on team'}} } diff --git a/packages/server/graphql/mutations/updateGitHubDimensionField.ts b/packages/server/graphql/mutations/updateGitHubDimensionField.ts index ded249edf3f..f6c18e70f53 100644 --- a/packages/server/graphql/mutations/updateGitHubDimensionField.ts +++ b/packages/server/graphql/mutations/updateGitHubDimensionField.ts @@ -1,6 +1,5 @@ import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import MeetingPoker from '../../database/types/MeetingPoker' import upsertGitHubDimensionFieldMap from '../../postgres/queries/upsertGitHubDimensionFieldMap' import {Logger} from '../../utils/Logger' import {isTeamMember} from '../../utils/authorization' @@ -49,7 +48,10 @@ const updateGitHubDimensionField = { if (!meeting) { return {error: {message: 'Invalid meetingId'}} } - const {teamId, templateRefId} = meeting as MeetingPoker + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } + const {teamId, templateRefId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on team'}} } diff --git a/packages/server/graphql/mutations/updatePokerScope.ts b/packages/server/graphql/mutations/updatePokerScope.ts index 823a6381383..0e59ab72161 100644 --- a/packages/server/graphql/mutations/updatePokerScope.ts +++ b/packages/server/graphql/mutations/updatePokerScope.ts @@ -4,7 +4,6 @@ import {SubscriptionChannel, Threshold} from 'parabol-client/types/constEnums' import {ESTIMATE_TASK_SORT_ORDER} from '../../../client/utils/constants' import getRethink from '../../database/rethinkDriver' import EstimateStage from '../../database/types/EstimateStage' -import MeetingPoker from '../../database/types/MeetingPoker' import {TaskServiceEnum} from '../../database/types/Task' import getKysely from '../../postgres/getKysely' import {Discussion} from '../../postgres/pg' @@ -56,12 +55,14 @@ const updatePokerScope = { // Wrap everything in try catch to ensure the lock is released try { //AUTH - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingPoker + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) { return {error: {message: `Meeting not found`}} } - - const {endedAt, teamId, phases, meetingType, templateRefId, facilitatorStageId} = meeting + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } + const {endedAt, teamId, phases, templateRefId, facilitatorStageId} = meeting if (!isTeamMember(authToken, teamId)) { // bad actors could be naughty & just lock meetings that they don't own. Limit bad actors to team members return {error: {message: `Not on team`}} @@ -70,10 +71,6 @@ const updatePokerScope = { return {error: {message: `Meeting already ended`}} } - if (meetingType !== 'poker') { - return {error: {message: 'Not a poker meeting'}} - } - // RESOLUTION const estimatePhase = getPhase(phases, 'ESTIMATE') diff --git a/packages/server/graphql/mutations/updateRetroMaxVotes.ts b/packages/server/graphql/mutations/updateRetroMaxVotes.ts index db5c0beca24..be2c7749cb6 100644 --- a/packages/server/graphql/mutations/updateRetroMaxVotes.ts +++ b/packages/server/graphql/mutations/updateRetroMaxVotes.ts @@ -4,7 +4,6 @@ import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' 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' @@ -44,12 +43,15 @@ const updateRetroMaxVotes = { const subOptions = {mutatorId, operationId} //AUTH - const meeting = (await r.table('NewMeeting').get(meetingId).run()) as MeetingRetrospective + const meeting = await r.table('NewMeeting').get(meetingId).run() if (!meeting) { return {error: {message: 'Meeting not found'}} } + if (meeting.meetingType !== 'retrospective') { + return {error: {message: `Meeting not retrospective`}} + } const { endedAt, meetingType, diff --git a/packages/server/graphql/mutations/voteForPokerStory.ts b/packages/server/graphql/mutations/voteForPokerStory.ts index 76330da4d8a..15705e31292 100644 --- a/packages/server/graphql/mutations/voteForPokerStory.ts +++ b/packages/server/graphql/mutations/voteForPokerStory.ts @@ -3,7 +3,6 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' import {RValue} from '../../database/stricterR' import EstimateUserScore from '../../database/types/EstimateUserScore' -import MeetingPoker from '../../database/types/MeetingPoker' import updateStage from '../../database/updateStage' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' @@ -71,20 +70,20 @@ const voteForPokerStory = { const subOptions = {mutatorId, operationId} //AUTH - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingPoker + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) { return {error: {message: 'Meeting not found'}} } - const {endedAt, phases, meetingType, teamId, templateRefId} = meeting + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } + const {endedAt, phases, teamId, templateRefId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on the team'}} } if (endedAt) { return {error: {message: 'Meeting has ended'}} } - if (meetingType !== 'poker') { - return {error: {message: 'Not a poker meeting'}} - } // No need to check for now (https://github.com/ParabolInc/parabol/issues/7191) // if (isPhaseComplete('ESTIMATE', phases)) { // return {error: {message: 'Estimate phase is already complete'}} diff --git a/packages/server/graphql/mutations/voteForReflectionGroup.ts b/packages/server/graphql/mutations/voteForReflectionGroup.ts index fe193c618c9..171cf1f451e 100644 --- a/packages/server/graphql/mutations/voteForReflectionGroup.ts +++ b/packages/server/graphql/mutations/voteForReflectionGroup.ts @@ -3,7 +3,6 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {VOTE} from 'parabol-client/utils/constants' import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' import getRethink from '../../database/rethinkDriver' -import MeetingRetrospective from '../../database/types/MeetingRetrospective' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -43,7 +42,10 @@ export default { }) } const {meetingId} = reflectionGroup - const meeting = (await r.table('NewMeeting').get(meetingId).run()) as MeetingRetrospective + const meeting = await r.table('NewMeeting').get(meetingId).run() + if (meeting.meetingType !== 'retrospective') { + return {error: {message: 'Meeting type is not retrospective'}} + } const {endedAt, phases, maxVotesPerGroup, teamId} = meeting if (!isTeamMember(authToken, teamId)) { return standardError(new Error('Team not found'), {userId: viewerId}) diff --git a/packages/server/graphql/private/mutations/generateMeetingSummary.ts b/packages/server/graphql/private/mutations/generateMeetingSummary.ts index 8dbb9b5a071..3640fe0541e 100644 --- a/packages/server/graphql/private/mutations/generateMeetingSummary.ts +++ b/packages/server/graphql/private/mutations/generateMeetingSummary.ts @@ -1,7 +1,7 @@ import yaml from 'js-yaml' import getRethink from '../../../database/rethinkDriver' -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import getKysely from '../../../postgres/getKysely' +import {RetrospectiveMeeting} from '../../../postgres/types/Meeting' import OpenAIServerManager from '../../../utils/OpenAIServerManager' import getPhase from '../../../utils/getPhase' import {MutationResolvers} from '../resolverTypes' @@ -20,7 +20,7 @@ const generateMeetingSummary: MutationResolvers['generateMeetingSummary'] = asyn const twoYearsAgo = new Date() twoYearsAgo.setFullYear(endDate.getFullYear() - 2) - const rawMeetings = (await r + const rawMeetings = await r .table('NewMeeting') .getAll(r.args(teamIds), {index: 'teamId'}) .filter((row: any) => @@ -32,7 +32,7 @@ const generateMeetingSummary: MutationResolvers['generateMeetingSummary'] = asyn .and(r.table('MeetingMember').getAll(row('id'), {index: 'meetingId'}).count().gt(1)) .and(row('endedAt').sub(row('createdAt')).gt(MIN_MILLISECONDS)) ) - .run()) as MeetingRetrospective[] + .run() const getComments = async (reflectionGroupId: string) => { const IGNORE_COMMENT_USER_IDS = ['parabolAIUser'] @@ -87,7 +87,7 @@ const generateMeetingSummary: MutationResolvers['generateMeetingSummary'] = asyn return comments } - const getMeetingsContent = async (meeting: MeetingRetrospective) => { + const getMeetingsContent = async (meeting: RetrospectiveMeeting) => { const pg = getKysely() const {id: meetingId, disableAnonymity, name: meetingName, createdAt: meetingDate} = meeting const rawReflectionGroups = await dataLoader @@ -157,6 +157,7 @@ const generateMeetingSummary: MutationResolvers['generateMeetingSummary'] = asyn const updatedMeetingIds = await Promise.all( rawMeetings.map(async (meeting) => { + if (meeting.meetingType !== 'retrospective') return null const meetingsContent = await getMeetingsContent(meeting) if (!meetingsContent || meetingsContent.length === 0) { return null diff --git a/packages/server/graphql/private/mutations/processRecurrence.ts b/packages/server/graphql/private/mutations/processRecurrence.ts index e51547e940c..f27134e03e1 100644 --- a/packages/server/graphql/private/mutations/processRecurrence.ts +++ b/packages/server/graphql/private/mutations/processRecurrence.ts @@ -6,11 +6,8 @@ import {DateTime, RRuleSet} from 'rrule-rust' import TeamMemberId from '../../../../client/shared/gqlIds/TeamMemberId' import {fromDateTime, toDateTime} from '../../../../client/shared/rruleUtil' import getRethink from '../../../database/rethinkDriver' -import MeetingRetrospective, { - isMeetingRetrospective -} from '../../../database/types/MeetingRetrospective' -import MeetingTeamPrompt, {isMeetingTeamPrompt} from '../../../database/types/MeetingTeamPrompt' import {getActiveMeetingSeries} from '../../../postgres/queries/getActiveMeetingSeries' +import {RetrospectiveMeeting, TeamPromptMeeting} from '../../../postgres/types/Meeting' import {MeetingSeries} from '../../../postgres/types/MeetingSeries' import {analytics} from '../../../utils/analytics/analytics' import {getNextRRuleDate} from '../../../utils/getNextRRuleDate' @@ -54,7 +51,7 @@ const startRecurringMeeting = async ( const meetingName = createMeetingSeriesTitle(meetingSeries.title, startTime, rrule.tzid) const meeting = await (async () => { if (meetingSeries.meetingType === 'teamPrompt') { - const teamPromptMeeting = lastMeeting as MeetingTeamPrompt | null + const teamPromptMeeting = lastMeeting as TeamPromptMeeting | null const meeting = await safeCreateTeamPrompt( meetingName, teamId, @@ -73,7 +70,7 @@ const startRecurringMeeting = async ( return meeting } else if (meetingSeries.meetingType === 'retrospective') { const {totalVotes, maxVotesPerGroup, disableAnonymity, templateId} = - (lastMeeting as MeetingRetrospective) ?? { + (lastMeeting as RetrospectiveMeeting) ?? { templateId: meetingSettings.selectedTemplateId, ...meetingSettings } @@ -132,9 +129,9 @@ const processRecurrence: MutationResolvers['processRecurrence'] = async ( const res = await tracer.trace('processRecurrence.endMeetings', async () => Promise.all( meetingsToEnd.map((meeting) => { - if (isMeetingTeamPrompt(meeting)) { + if (meeting.meetingType === 'teamPrompt') { return safeEndTeamPrompt({meeting, now, context, r, subOptions}) - } else if (isMeetingRetrospective(meeting)) { + } else if (meeting.meetingType === 'retrospective') { return safeEndRetrospective({meeting, now, context}) } else { return standardError(new Error('Unhandled recurring meeting type'), { diff --git a/packages/server/graphql/private/mutations/runScheduledJobs.ts b/packages/server/graphql/private/mutations/runScheduledJobs.ts index 2b1c3b70575..bc93f4f84f8 100644 --- a/packages/server/graphql/private/mutations/runScheduledJobs.ts +++ b/packages/server/graphql/private/mutations/runScheduledJobs.ts @@ -31,11 +31,11 @@ const processMeetingStageTimeLimits = async ( const notification = new NotificationMeetingStageTimeLimitEnd({ meetingId, - userId: facilitatorUserId + userId: facilitatorUserId! }) const r = await getRethink() await r.table('Notification').insert(notification).run() - publish(SubscriptionChannel.NOTIFICATION, facilitatorUserId, 'MeetingStageTimeLimitPayload', { + publish(SubscriptionChannel.NOTIFICATION, facilitatorUserId!, 'MeetingStageTimeLimitPayload', { notification }) } diff --git a/packages/server/graphql/private/types/GenerateMeetingSummarySuccess.ts b/packages/server/graphql/private/types/GenerateMeetingSummarySuccess.ts index 8a572dec1e0..ad29fab70d5 100644 --- a/packages/server/graphql/private/types/GenerateMeetingSummarySuccess.ts +++ b/packages/server/graphql/private/types/GenerateMeetingSummarySuccess.ts @@ -1,4 +1,4 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' +import isValid from '../../isValid' import {GenerateMeetingSummarySuccessResolvers} from '../resolverTypes' export type GenerateMeetingSummarySuccessSource = { @@ -7,10 +7,9 @@ export type GenerateMeetingSummarySuccessSource = { const GenerateMeetingSummarySuccess: GenerateMeetingSummarySuccessResolvers = { meetings: async ({meetingIds}, _args, {dataLoader}) => { - const meetings = (await dataLoader - .get('newMeetings') - .loadMany(meetingIds)) as MeetingRetrospective[] - return meetings + return (await dataLoader.get('newMeetings').loadMany(meetingIds)) + .filter(isValid) + .filter((m) => m.meetingType === 'retrospective') } } diff --git a/packages/server/graphql/public/mutations/addTranscriptionBot.ts b/packages/server/graphql/public/mutations/addTranscriptionBot.ts index 6862a934f00..6b78c0bace5 100644 --- a/packages/server/graphql/public/mutations/addTranscriptionBot.ts +++ b/packages/server/graphql/public/mutations/addTranscriptionBot.ts @@ -1,5 +1,4 @@ import {SubscriptionChannel} from '../../../../client/types/constEnums' -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {getUserId, isTeamMember} from '../../../utils/authorization' import publish from '../../../utils/publish' import standardError from '../../../utils/standardError' @@ -14,10 +13,13 @@ const addTranscriptionBot: MutationResolvers['addTranscriptionBot'] = async ( const viewerId = getUserId(authToken) const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingRetrospective + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) { return standardError(new Error('Meeting not found'), {userId: viewerId}) } + if (meeting.meetingType !== 'retrospective') { + return {error: {message: 'Meeting type is not retrospective'}} + } const {teamId} = meeting if (!isTeamMember(authToken, teamId)) { const error = new Error('Not on team') diff --git a/packages/server/graphql/public/mutations/endTeamPrompt.ts b/packages/server/graphql/public/mutations/endTeamPrompt.ts index 692ee81d339..b98a70f1261 100644 --- a/packages/server/graphql/public/mutations/endTeamPrompt.ts +++ b/packages/server/graphql/public/mutations/endTeamPrompt.ts @@ -1,5 +1,4 @@ import getRethink from '../../../database/rethinkDriver' -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {getUserId, isTeamMember} from '../../../utils/authorization' import standardError from '../../../utils/standardError' import safeEndTeamPrompt from '../../mutations/helpers/safeEndTeamPrompt' @@ -14,8 +13,11 @@ const endTeamPrompt: MutationResolvers['endTeamPrompt'] = async (_source, {meeti const subOptions = {mutatorId, operationId} // AUTH - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingTeamPrompt | null + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) + if (meeting.meetingType !== 'teamPrompt') { + return {error: {message: 'Meeting type is not teamPrompt'}} + } const {teamId} = meeting // VALIDATION diff --git a/packages/server/graphql/public/mutations/helpers/getSummaries.ts b/packages/server/graphql/public/mutations/helpers/getSummaries.ts index c80339a30f9..30ff97c9f9d 100644 --- a/packages/server/graphql/public/mutations/helpers/getSummaries.ts +++ b/packages/server/graphql/public/mutations/helpers/getSummaries.ts @@ -1,6 +1,5 @@ import yaml from 'js-yaml' import getRethink from '../../../../database/rethinkDriver' -import MeetingRetrospective from '../../../../database/types/MeetingRetrospective' import OpenAIServerManager from '../../../../utils/OpenAIServerManager' import standardError from '../../../../utils/standardError' @@ -14,7 +13,7 @@ export const getSummaries = async ( const MIN_MILLISECONDS = 60 * 1000 // 1 minute const MIN_REFLECTION_COUNT = 3 - const rawMeetings = (await r + const rawMeetings = await r .table('NewMeeting') .getAll(teamId, {index: 'teamId'}) .filter((row: any) => @@ -27,7 +26,7 @@ export const getSummaries = async ( .and(row('endedAt').sub(row('createdAt')).gt(MIN_MILLISECONDS)) .and(row.hasFields('summary')) ) - .run()) as MeetingRetrospective[] + .run() if (!rawMeetings.length) { return standardError(new Error('No meetings found')) diff --git a/packages/server/graphql/public/mutations/helpers/getTopics.ts b/packages/server/graphql/public/mutations/helpers/getTopics.ts index 4cb74174c5b..47782e177f9 100644 --- a/packages/server/graphql/public/mutations/helpers/getTopics.ts +++ b/packages/server/graphql/public/mutations/helpers/getTopics.ts @@ -1,6 +1,5 @@ import yaml from 'js-yaml' import getRethink from '../../../../database/rethinkDriver' -import MeetingRetrospective from '../../../../database/types/MeetingRetrospective' import getKysely from '../../../../postgres/getKysely' import OpenAIServerManager from '../../../../utils/OpenAIServerManager' import sendToSentry from '../../../../utils/sendToSentry' @@ -113,7 +112,7 @@ export const getTopics = async ( const r = await getRethink() const MIN_REFLECTION_COUNT = 3 const MIN_MILLISECONDS = 60 * 1000 // 1 minute - const rawMeetings = await r + const rawAnyMeetings = await r .table('NewMeeting') .getAll(teamId, {index: 'teamId'}) .filter((row: any) => @@ -126,15 +125,10 @@ export const getTopics = async ( .and(row('endedAt').sub(row('createdAt')).gt(MIN_MILLISECONDS)) ) .run() - + const rawMeetings = rawAnyMeetings.filter((m) => m.meetingType === 'retrospective') const meetings = await Promise.all( rawMeetings.map(async (meeting) => { - const { - id: meetingId, - disableAnonymity, - name: meetingName, - createdAt: meetingDate - } = meeting as MeetingRetrospective + const {id: meetingId, disableAnonymity, name: meetingName, createdAt: meetingDate} = meeting const rawReflectionGroups = await dataLoader .get('retroReflectionGroupsByMeetingId') .load(meetingId) diff --git a/packages/server/graphql/public/mutations/resetReflectionGroups.ts b/packages/server/graphql/public/mutations/resetReflectionGroups.ts index c52fc4361a3..4f574390d0f 100644 --- a/packages/server/graphql/public/mutations/resetReflectionGroups.ts +++ b/packages/server/graphql/public/mutations/resetReflectionGroups.ts @@ -75,7 +75,7 @@ const resetReflectionGroups: MutationResolvers['resetReflectionGroups'] = async .get(meetingId) .replace(r.row.without('resetReflectionGroups') as any) .run() - meeting.resetReflectionGroups = undefined + meeting.resetReflectionGroups = null analytics.resetGroupsClicked(viewer, meetingId, teamId) const data = {meetingId} publish(SubscriptionChannel.MEETING, meetingId, 'ResetReflectionGroupsSuccess', data, subOptions) diff --git a/packages/server/graphql/public/mutations/setTeamHealthVote.ts b/packages/server/graphql/public/mutations/setTeamHealthVote.ts index d186c7940af..7f3fe36af53 100644 --- a/packages/server/graphql/public/mutations/setTeamHealthVote.ts +++ b/packages/server/graphql/public/mutations/setTeamHealthVote.ts @@ -3,12 +3,44 @@ import getRethink from '../../../database/rethinkDriver' import {RValue} from '../../../database/stricterR' import TeamHealthVote from '../../../database/types/TeamHealthVote' import updateStage from '../../../database/updateStage' +import getKysely from '../../../postgres/getKysely' +import {NewMeetingPhase} from '../../../postgres/types/NewMeetingPhase.d' import {getUserId, isTeamMember} from '../../../utils/authorization' import getPhase from '../../../utils/getPhase' import publish from '../../../utils/publish' import {MutationResolvers} from '../resolverTypes' const upsertVote = async (meetingId: string, stageId: string, newVote: TeamHealthVote) => { + const pg = getKysely() + await pg.transaction().execute(async (trx) => { + // console.log('start transaction', newVote) + const meeting = await trx + .selectFrom('NewMeeting') + .select(({fn}) => fn('to_json', ['phases']).as('phases')) + .where('id', '=', meetingId) + .forUpdate() + // NewMeeting: add OrThrow in phase 3 + .executeTakeFirst() + if (!meeting) return + // console.log('got lock', newVote) + const {phases} = meeting + const phase = getPhase(phases, 'TEAM_HEALTH') + const {stages} = phase + const [stage] = stages + const {votes} = stage + const existingVote = votes.find((vote) => vote.userId === newVote.userId) + if (existingVote) { + existingVote.vote = newVote.vote + } else { + votes.push(newVote) + } + await trx + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + .execute() + // console.log('wrote update, commit', newVote) + }) const r = await getRethink() const updater = (stage: RValue) => stage.merge({ diff --git a/packages/server/graphql/public/mutations/startCheckIn.ts b/packages/server/graphql/public/mutations/startCheckIn.ts index b90ffd146a7..62086ac17ba 100644 --- a/packages/server/graphql/public/mutations/startCheckIn.ts +++ b/packages/server/graphql/public/mutations/startCheckIn.ts @@ -5,7 +5,8 @@ import MeetingAction from '../../../database/types/MeetingAction' import generateUID from '../../../generateUID' import getKysely from '../../../postgres/getKysely' import updateTeamByTeamId from '../../../postgres/queries/updateTeamByTeamId' -import {MeetingTypeEnum} from '../../../postgres/types/Meeting' +import {CheckInMeeting, MeetingTypeEnum} from '../../../postgres/types/Meeting' +import {CheckInPhase} from '../../../postgres/types/NewMeetingPhase' import {analytics} from '../../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -48,7 +49,7 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( .run() const meetingId = generateUID() - const phases = await createNewMeetingPhases( + const phases = await createNewMeetingPhases( viewerId, teamId, meetingId, @@ -64,7 +65,7 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( meetingCount, phases, facilitatorUserId: viewerId - }) + }) as CheckInMeeting await r.table('NewMeeting').insert(meeting).run() // Disallow 2 active check-in meetings diff --git a/packages/server/graphql/public/mutations/updateGitLabDimensionField.ts b/packages/server/graphql/public/mutations/updateGitLabDimensionField.ts index 63540c4507b..217bde46903 100644 --- a/packages/server/graphql/public/mutations/updateGitLabDimensionField.ts +++ b/packages/server/graphql/public/mutations/updateGitLabDimensionField.ts @@ -1,5 +1,4 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import MeetingPoker from '../../../database/types/MeetingPoker' import upsertGitLabDimensionFieldMap from '../../../postgres/queries/upsertGitLabDimensionFieldMap' import {Logger} from '../../../utils/Logger' import {isTeamMember} from '../../../utils/authorization' @@ -20,7 +19,10 @@ const updateGitLabDimensionField: MutationResolvers['updateGitLabDimensionField' if (!meeting) { return {error: {message: 'Invalid meetingId'}} } - const {teamId, templateRefId} = meeting as MeetingPoker + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } + const {teamId, templateRefId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on team'}} } diff --git a/packages/server/graphql/public/mutations/updateJiraDimensionField.ts b/packages/server/graphql/public/mutations/updateJiraDimensionField.ts index fd750b7c978..b99f44c49be 100644 --- a/packages/server/graphql/public/mutations/updateJiraDimensionField.ts +++ b/packages/server/graphql/public/mutations/updateJiraDimensionField.ts @@ -1,6 +1,5 @@ import {SprintPokerDefaults, SubscriptionChannel} from 'parabol-client/types/constEnums' import JiraProjectKeyId from '../../../../client/shared/gqlIds/JiraProjectKeyId' -import MeetingPoker from '../../../database/types/MeetingPoker' import {JiraIssue} from '../../../dataloader/atlassianLoaders' import upsertJiraDimensionFieldMap from '../../../postgres/queries/upsertJiraDimensionFieldMap' import {getUserId, isTeamMember} from '../../../utils/authorization' @@ -38,7 +37,10 @@ const updateJiraDimensionField: MutationResolvers['updateJiraDimensionField'] = if (!meeting) { return {error: {message: 'Invalid meetingId'}} } - const {teamId, templateRefId} = meeting as MeetingPoker + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } + const {teamId, templateRefId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on team'}} } diff --git a/packages/server/graphql/public/mutations/updateJiraServerDimensionField.ts b/packages/server/graphql/public/mutations/updateJiraServerDimensionField.ts index e37cde5efb5..2443adc0121 100644 --- a/packages/server/graphql/public/mutations/updateJiraServerDimensionField.ts +++ b/packages/server/graphql/public/mutations/updateJiraServerDimensionField.ts @@ -1,5 +1,4 @@ import {SprintPokerDefaults, SubscriptionChannel} from 'parabol-client/types/constEnums' -import MeetingPoker from '../../../database/types/MeetingPoker' import JiraServerRestManager from '../../../integrations/jiraServer/JiraServerRestManager' import {IntegrationProviderJiraServer} from '../../../postgres/queries/getIntegrationProvidersByIds' import upsertJiraServerDimensionFieldMap from '../../../postgres/queries/upsertJiraServerDimensionFieldMap' @@ -21,7 +20,10 @@ const updateJiraServerDimensionField: MutationResolvers['updateJiraServerDimensi if (!meeting) { return {error: {message: 'Invalid meetingId'}} } - const {teamId, templateRefId} = meeting as MeetingPoker + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } + const {teamId, templateRefId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on team'}} } diff --git a/packages/server/graphql/public/mutations/updateMeetingTemplate.ts b/packages/server/graphql/public/mutations/updateMeetingTemplate.ts index a0be265f8de..d355a6062a2 100644 --- a/packages/server/graphql/public/mutations/updateMeetingTemplate.ts +++ b/packages/server/graphql/public/mutations/updateMeetingTemplate.ts @@ -1,6 +1,5 @@ import {SubscriptionChannel} from '../../../../client/types/constEnums' import getRethink from '../../../database/rethinkDriver' -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {getUserId, isTeamMember} from '../../../utils/authorization' import getPhase from '../../../utils/getPhase' import publish from '../../../utils/publish' @@ -16,8 +15,9 @@ const updateMeetingTemplate: MutationResolvers['updateMeetingTemplate'] = async const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingRetrospective + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) + if (!('templateId' in meeting)) return {error: {message: 'Meeting has no template'}} if (!isTeamMember(authToken, meeting.teamId)) { return standardError(new Error('Team not found'), {userId: viewerId}) } diff --git a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts index 014a0fccef3..39a814265d0 100644 --- a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts +++ b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts @@ -22,7 +22,7 @@ export const startNewMeetingSeries = async ( teamId: string meetingType: MeetingTypeEnum name: string - facilitatorUserId: string + facilitatorUserId: string | null }, recurrenceRule: RRuleSet, meetingSeriesName?: string | null @@ -35,7 +35,9 @@ export const startNewMeetingSeries = async ( facilitatorUserId: facilitatorId } = meeting const r = await getRethink() - + if (!facilitatorId) { + throw new Error('No facilitatorId') + } const newMeetingSeriesParams = { meetingType, title: meetingSeriesName || meetingName.split('-')[0]!.trim(), // if no name is provided, we use the name of the first meeting without the date diff --git a/packages/server/graphql/public/types/AddTranscriptionBotSuccess.ts b/packages/server/graphql/public/types/AddTranscriptionBotSuccess.ts index 880fb7e8538..44839c75df1 100644 --- a/packages/server/graphql/public/types/AddTranscriptionBotSuccess.ts +++ b/packages/server/graphql/public/types/AddTranscriptionBotSuccess.ts @@ -1,4 +1,3 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {AddTranscriptionBotSuccessResolvers} from '../resolverTypes' export type AddTranscriptionBotSuccessSource = { @@ -8,7 +7,9 @@ export type AddTranscriptionBotSuccessSource = { const AddTranscriptionBotSuccess: AddTranscriptionBotSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - return meeting as MeetingRetrospective + if (meeting.meetingType !== 'retrospective') + throw new Error('Meeting type is not retrospective') + return meeting } } diff --git a/packages/server/graphql/public/types/AutogroupSuccess.ts b/packages/server/graphql/public/types/AutogroupSuccess.ts index 9c62aced1b3..1c86eb2efce 100644 --- a/packages/server/graphql/public/types/AutogroupSuccess.ts +++ b/packages/server/graphql/public/types/AutogroupSuccess.ts @@ -1,4 +1,3 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {AutogroupSuccessResolvers} from '../resolverTypes' export type AutogroupSuccessSource = { @@ -8,7 +7,8 @@ export type AutogroupSuccessSource = { const AutogroupSuccess: AutogroupSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - return meeting as MeetingRetrospective + if (meeting.meetingType !== 'retrospective') throw new Error('Not a retrospective meeting') + return meeting } } diff --git a/packages/server/graphql/public/types/Discussion.ts b/packages/server/graphql/public/types/Discussion.ts index 765d795215d..bf3f5908851 100644 --- a/packages/server/graphql/public/types/Discussion.ts +++ b/packages/server/graphql/public/types/Discussion.ts @@ -46,7 +46,9 @@ const Discussion: DiscussionResolvers = { return null } const {stages} = phase - const dbStage = stages.find((stage) => stage.reflectionGroupId === discussionTopicId) + const dbStage = stages.find( + (stage) => 'reflectionGroupId' in stage && stage.reflectionGroupId === discussionTopicId + ) return dbStage ? augmentDBStage(dbStage, meetingId, DISCUSS, teamId) : null } @@ -56,7 +58,9 @@ const Discussion: DiscussionResolvers = { return null } const {stages} = phase - const dbStage = stages.find((stage) => stage.taskId === discussionTopicId) + const dbStage = stages.find( + (stage) => 'taskId' in stage && stage.taskId === discussionTopicId + ) return dbStage ? augmentDBStage(dbStage, meetingId, 'ESTIMATE', teamId) : null } diff --git a/packages/server/graphql/public/types/EndTeamPromptSuccess.ts b/packages/server/graphql/public/types/EndTeamPromptSuccess.ts index a802a25f5aa..2452403289d 100644 --- a/packages/server/graphql/public/types/EndTeamPromptSuccess.ts +++ b/packages/server/graphql/public/types/EndTeamPromptSuccess.ts @@ -1,4 +1,3 @@ -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {EndTeamPromptSuccessResolvers} from '../resolverTypes' export type EndTeamPromptSuccessSource = { @@ -9,7 +8,9 @@ export type EndTeamPromptSuccessSource = { const EndTeamPromptSuccess: EndTeamPromptSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { - return (await dataLoader.get('newMeetings').load(meetingId)) as MeetingTeamPrompt + const meeting = await dataLoader.get('newMeetings').load(meetingId) + if (meeting.meetingType !== 'teamPrompt') throw new Error('Meeting is not a team prompt') + return meeting }, team: async ({teamId}, _args, {dataLoader}) => { return await dataLoader.get('teams').loadNonNull(teamId) diff --git a/packages/server/graphql/public/types/EstimateStage.ts b/packages/server/graphql/public/types/EstimateStage.ts index 112e2c91bc5..9e949ac0de7 100644 --- a/packages/server/graphql/public/types/EstimateStage.ts +++ b/packages/server/graphql/public/types/EstimateStage.ts @@ -1,6 +1,5 @@ import JiraProjectKeyId from '../../../../client/shared/gqlIds/JiraProjectKeyId' import {SprintPokerDefaults} from '../../../../client/types/constEnums' -import MeetingPoker from '../../../database/types/MeetingPoker' import TaskIntegrationAzureDevOps from '../../../database/types/TaskIntegrationAzureDevOps' import TaskIntegrationJiraServer from '../../../database/types/TaskIntegrationJiraServer' import GitLabServerManager from '../../../integrations/gitlab/GitLabServerManager' @@ -23,7 +22,8 @@ const EstimateStage: EstimateStageResolvers = { const {service} = integration const getDimensionName = async (meetingId: string) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - const {templateRefId} = meeting as MeetingPoker + if (meeting.meetingType !== 'poker') throw new Error('Meeting is not a poker meeting') + const {templateRefId} = meeting const templateRef = await dataLoader.get('templateRefs').loadNonNull(templateRefId) const {dimensions} = templateRef const dimensionRef = dimensions[dimensionRefIdx]! @@ -176,7 +176,8 @@ const EstimateStage: EstimateStageResolvers = { dimensionRef: async ({meetingId, dimensionRefIdx}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - const {templateRefId} = meeting as MeetingPoker + if (meeting.meetingType !== 'poker') return null + const {templateRefId} = meeting const templateRef = await dataLoader.get('templateRefs').loadNonNull(templateRefId) const {dimensions} = templateRef const {name, scaleRefId} = dimensions[dimensionRefIdx]! @@ -193,7 +194,8 @@ const EstimateStage: EstimateStageResolvers = { dataLoader.get('newMeetings').load(meetingId), dataLoader.get('meetingTaskEstimates').load({taskId, meetingId}) ]) - const {templateRefId} = meeting as MeetingPoker + if (meeting.meetingType !== 'poker') return null + const {templateRefId} = meeting const templateRef = await dataLoader.get('templateRefs').loadNonNull(templateRefId) const {dimensions} = templateRef const dimensionRef = dimensions[dimensionRefIdx]! diff --git a/packages/server/graphql/public/types/GenerateGroupsSuccess.ts b/packages/server/graphql/public/types/GenerateGroupsSuccess.ts index 421d0142de3..a15db891039 100644 --- a/packages/server/graphql/public/types/GenerateGroupsSuccess.ts +++ b/packages/server/graphql/public/types/GenerateGroupsSuccess.ts @@ -1,4 +1,3 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {GenerateGroupsSuccessResolvers} from '../resolverTypes' export type GenerateGroupsSuccessSource = { @@ -8,7 +7,9 @@ export type GenerateGroupsSuccessSource = { const GenerateGroupsSuccess: GenerateGroupsSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - return meeting as MeetingRetrospective + if (meeting.meetingType !== 'retrospective') + throw new Error('Meeting type is not retrospective') + return meeting } } diff --git a/packages/server/graphql/public/types/GenerateInsightSuccess.ts b/packages/server/graphql/public/types/GenerateInsightSuccess.ts index ac099aa2295..a22c5a7e1cd 100644 --- a/packages/server/graphql/public/types/GenerateInsightSuccess.ts +++ b/packages/server/graphql/public/types/GenerateInsightSuccess.ts @@ -1,4 +1,4 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' +import isValid from '../../isValid' import {GenerateInsightSuccessResolvers} from '../resolverTypes' export type GenerateInsightSuccessSource = { @@ -11,10 +11,8 @@ const GenerateInsightSuccess: GenerateInsightSuccessResolvers = { wins: ({wins}) => wins, challenges: ({challenges}) => challenges, meetings: async ({meetingIds}, _args, {dataLoader}) => { - const meetings = (await dataLoader - .get('newMeetings') - .loadMany(meetingIds)) as MeetingRetrospective[] - return meetings + const meetings = await dataLoader.get('newMeetings').loadMany(meetingIds) + return meetings.filter(isValid).filter((m) => m.meetingType === 'retrospective') } } diff --git a/packages/server/graphql/public/types/NewMeeting.ts b/packages/server/graphql/public/types/NewMeeting.ts index d75bc19bceb..941809c0297 100644 --- a/packages/server/graphql/public/types/NewMeeting.ts +++ b/packages/server/graphql/public/types/NewMeeting.ts @@ -19,7 +19,7 @@ const NewMeeting: NewMeetingResolvers = { return dataLoader.get('users').loadNonNull(createdBy) }, facilitator: ({facilitatorUserId, teamId}, _args, {dataLoader}) => { - const teamMemberId = toTeamMemberId(teamId, facilitatorUserId) + const teamMemberId = toTeamMemberId(teamId, facilitatorUserId!) return dataLoader.get('teamMembers').loadNonNull(teamMemberId) }, locked: async ({endedAt, teamId}, _args, {authToken, dataLoader}) => { diff --git a/packages/server/graphql/public/types/NotifyResponseMentioned.ts b/packages/server/graphql/public/types/NotifyResponseMentioned.ts index 0a8b86906a5..a9bc5d31501 100644 --- a/packages/server/graphql/public/types/NotifyResponseMentioned.ts +++ b/packages/server/graphql/public/types/NotifyResponseMentioned.ts @@ -1,12 +1,12 @@ import TeamPromptResponseId from '../../../../client/shared/gqlIds/TeamPromptResponseId' -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {NotifyResponseMentionedResolvers} from '../resolverTypes' const NotifyResponseMentioned: NotifyResponseMentionedResolvers = { __isTypeOf: ({type}) => type === 'RESPONSE_MENTIONED', meeting: async ({meetingId}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - return meeting as MeetingTeamPrompt + if (meeting.meetingType !== 'teamPrompt') throw new Error('Meeting is not a team prompt') + return meeting }, response: ({responseId}, _args, {dataLoader}) => { // Hack, in a perfect world, this notification would have the numeric DB ID saved on it diff --git a/packages/server/graphql/public/types/NotifyResponseReplied.ts b/packages/server/graphql/public/types/NotifyResponseReplied.ts index 883a71d93bb..87ff6af7198 100644 --- a/packages/server/graphql/public/types/NotifyResponseReplied.ts +++ b/packages/server/graphql/public/types/NotifyResponseReplied.ts @@ -1,4 +1,3 @@ -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {getTeamPromptResponsesByMeetingId} from '../../../postgres/queries/getTeamPromptResponsesByMeetingIds' import {NotifyResponseRepliedResolvers} from '../resolverTypes' @@ -6,7 +5,8 @@ const NotifyResponseReplied: NotifyResponseRepliedResolvers = { __isTypeOf: ({type}) => type === 'RESPONSE_REPLIED', meeting: async ({meetingId}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - return meeting as MeetingTeamPrompt + if (meeting.meetingType !== 'teamPrompt') throw new Error('Meeting is not a team prompt') + return meeting }, response: async ({userId, meetingId}) => { // TODO: implement getTeamPromptResponsesByMeetingIdAndUserId diff --git a/packages/server/graphql/public/types/ReflectPhase.ts b/packages/server/graphql/public/types/ReflectPhase.ts index 8714fad14a0..e11232d3cc6 100644 --- a/packages/server/graphql/public/types/ReflectPhase.ts +++ b/packages/server/graphql/public/types/ReflectPhase.ts @@ -1,4 +1,3 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {ReflectPhaseResolvers} from '../resolverTypes' const ReflectPhase: ReflectPhaseResolvers = { @@ -9,7 +8,8 @@ const ReflectPhase: ReflectPhaseResolvers = { }, reflectPrompts: async ({meetingId}, _args, {dataLoader}) => { - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingRetrospective + const meeting = await dataLoader.get('newMeetings').load(meetingId) + if (!('templateId' in meeting)) return [] const prompts = await dataLoader.get('reflectPromptsByTemplateId').load(meeting.templateId) // only show prompts that were created before the meeting and // either have not been removed or they were removed after the meeting was created diff --git a/packages/server/graphql/public/types/ResetReflectionGroupsSuccess.ts b/packages/server/graphql/public/types/ResetReflectionGroupsSuccess.ts index e15430efc9d..be7d98e9b8c 100644 --- a/packages/server/graphql/public/types/ResetReflectionGroupsSuccess.ts +++ b/packages/server/graphql/public/types/ResetReflectionGroupsSuccess.ts @@ -1,4 +1,3 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {ResetReflectionGroupsSuccessResolvers} from '../resolverTypes' export type ResetReflectionGroupsSuccessSource = { @@ -8,7 +7,8 @@ export type ResetReflectionGroupsSuccessSource = { const ResetReflectionGroupsSuccess: ResetReflectionGroupsSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - return meeting as MeetingRetrospective + if (meeting.meetingType !== 'retrospective') throw new Error('Not a retrospective meeting') + return meeting } } diff --git a/packages/server/graphql/public/types/RetroDiscussStage.ts b/packages/server/graphql/public/types/RetroDiscussStage.ts index cd98f071fb3..3d4aa611c20 100644 --- a/packages/server/graphql/public/types/RetroDiscussStage.ts +++ b/packages/server/graphql/public/types/RetroDiscussStage.ts @@ -1,4 +1,3 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import ReflectionGroup from '../../../database/types/ReflectionGroup' import {RetroDiscussStageResolvers} from '../resolverTypes' @@ -27,7 +26,8 @@ const RetroDiscussStage: RetroDiscussStageResolvers = { reflectionGroup: async ({reflectionGroupId, meetingId}, _args, {dataLoader}) => { if (!reflectionGroupId) { - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingRetrospective + const meeting = await dataLoader.get('newMeetings').load(meetingId) + if (!('templateId' in meeting)) throw new Error('Meeting has no template') const prompts = await dataLoader.get('reflectPromptsByTemplateId').load(meeting.templateId) return new ReflectionGroup({ id: `${meetingId}:dummyGroup`, diff --git a/packages/server/graphql/public/types/RetroReflection.ts b/packages/server/graphql/public/types/RetroReflection.ts index 80c967c6797..1443aba8d35 100644 --- a/packages/server/graphql/public/types/RetroReflection.ts +++ b/packages/server/graphql/public/types/RetroReflection.ts @@ -1,4 +1,3 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {getUserId, isSuperUser} from '../../../utils/authorization' import getGroupedReactjis from '../../../utils/getGroupedReactjis' import {RetroReflectionResolvers} from '../resolverTypes' @@ -35,7 +34,8 @@ const RetroReflection: RetroReflectionResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - return meeting as MeetingRetrospective + if (meeting.meetingType !== 'retrospective') throw new Error('Not a retrospective meeting') + return meeting }, prompt: ({promptId}, _args, {dataLoader}) => { diff --git a/packages/server/graphql/public/types/RetroReflectionGroup.ts b/packages/server/graphql/public/types/RetroReflectionGroup.ts index 2c0834b7635..c5435b3a00e 100644 --- a/packages/server/graphql/public/types/RetroReflectionGroup.ts +++ b/packages/server/graphql/public/types/RetroReflectionGroup.ts @@ -1,5 +1,4 @@ import {Selectable} from 'kysely' -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {RetroReflectionGroup as TRetroReflectionGroup} from '../../../postgres/pg' import {getUserId} from '../../../utils/authorization' import {RetroReflectionGroupResolvers} from '../resolverTypes' @@ -9,7 +8,8 @@ export interface RetroReflectionGroupSource extends Selectable { const retroMeeting = await dataLoader.get('newMeetings').load(meetingId) - return retroMeeting as MeetingRetrospective + if (retroMeeting.meetingType !== 'retrospective') throw new Error('Not a retrospective meeting') + return retroMeeting }, prompt: ({promptId}, _args, {dataLoader}) => { return dataLoader.get('reflectPrompts').loadNonNull(promptId) diff --git a/packages/server/graphql/public/types/StartCheckInSuccess.ts b/packages/server/graphql/public/types/StartCheckInSuccess.ts index 0dedaf9905c..7b44a4004ce 100644 --- a/packages/server/graphql/public/types/StartCheckInSuccess.ts +++ b/packages/server/graphql/public/types/StartCheckInSuccess.ts @@ -1,4 +1,3 @@ -import MeetingAction from '../../../database/types/MeetingAction' import {StartCheckInSuccessResolvers} from '../resolverTypes' export type StartCheckInSuccessSource = { @@ -7,8 +6,10 @@ export type StartCheckInSuccessSource = { } const StartCheckInSuccess: StartCheckInSuccessResolvers = { - meeting: ({meetingId}, _args, {dataLoader}) => { - return dataLoader.get('newMeetings').load(meetingId) as Promise + meeting: async ({meetingId}, _args, {dataLoader}) => { + const meeting = await dataLoader.get('newMeetings').load(meetingId) + if (meeting.meetingType !== 'action') throw new Error('Not a check-in meeting') + return meeting }, team: ({teamId}, _args, {dataLoader}) => { return dataLoader.get('teams').loadNonNull(teamId) diff --git a/packages/server/graphql/public/types/StartRetrospectiveSuccess.ts b/packages/server/graphql/public/types/StartRetrospectiveSuccess.ts index 4ba4baef9be..261edb42fec 100644 --- a/packages/server/graphql/public/types/StartRetrospectiveSuccess.ts +++ b/packages/server/graphql/public/types/StartRetrospectiveSuccess.ts @@ -1,4 +1,3 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {StartRetrospectiveSuccessResolvers} from '../resolverTypes' export type StartRetrospectiveSuccessSource = { @@ -8,8 +7,10 @@ export type StartRetrospectiveSuccessSource = { } const StartRetrospectiveSuccess: StartRetrospectiveSuccessResolvers = { - meeting: ({meetingId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('newMeetings').load(meetingId) as Promise + meeting: async ({meetingId}, _args: unknown, {dataLoader}) => { + const meeting = await dataLoader.get('newMeetings').load(meetingId) + if (meeting.meetingType !== 'retrospective') throw new Error('Not a retrospective meeting') + return meeting }, team: ({teamId}, _args: unknown, {dataLoader}) => { return dataLoader.get('teams').loadNonNull(teamId) diff --git a/packages/server/graphql/public/types/StartTeamPromptSuccess.ts b/packages/server/graphql/public/types/StartTeamPromptSuccess.ts index 15f9fcff241..017c7b63180 100644 --- a/packages/server/graphql/public/types/StartTeamPromptSuccess.ts +++ b/packages/server/graphql/public/types/StartTeamPromptSuccess.ts @@ -1,4 +1,3 @@ -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {StartTeamPromptSuccessResolvers} from '../resolverTypes' export type StartTeamPromptSuccessSource = { @@ -8,7 +7,9 @@ export type StartTeamPromptSuccessSource = { const StartTeamPromptSuccess: StartTeamPromptSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { - return dataLoader.get('newMeetings').load(meetingId) as Promise + const meeting = await dataLoader.get('newMeetings').load(meetingId) + if (meeting.meetingType !== 'teamPrompt') throw new Error('Not a team prompt meeting') + return meeting }, team: async ({teamId}, _args, {dataLoader}) => { return dataLoader.get('teams').loadNonNull(teamId) diff --git a/packages/server/graphql/public/types/TeamPromptMeeting.ts b/packages/server/graphql/public/types/TeamPromptMeeting.ts index 1bf595b202d..870dd09843b 100644 --- a/packages/server/graphql/public/types/TeamPromptMeeting.ts +++ b/packages/server/graphql/public/types/TeamPromptMeeting.ts @@ -1,7 +1,7 @@ import getRethink from '../../../database/rethinkDriver' import {RValue} from '../../../database/stricterR' -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {getTeamPromptResponsesByMeetingId} from '../../../postgres/queries/getTeamPromptResponsesByMeetingIds' +import {TeamPromptMeeting as TeamPromptMeetingSource} from '../../../postgres/types/Meeting' import {getUserId} from '../../../utils/authorization' import filterTasksByMeeting from '../../../utils/filterTasksByMeeting' import getPhase from '../../../utils/getPhase' @@ -28,7 +28,7 @@ const TeamPromptMeeting: TeamPromptMeetingResolvers = { .limit(1) .run() - return meetings[0] as MeetingTeamPrompt + return meetings[0] as TeamPromptMeetingSource }, nextMeeting: async ({meetingSeriesId, createdAt}, _args, {dataLoader}) => { if (!meetingSeriesId) return null @@ -48,7 +48,7 @@ const TeamPromptMeeting: TeamPromptMeetingResolvers = { .limit(1) .run() - return meetings[0] as MeetingTeamPrompt + return meetings[0] as TeamPromptMeetingSource }, tasks: async ({id: meetingId}, _args: unknown, {authToken, dataLoader}) => { const viewerId = getUserId(authToken) diff --git a/packages/server/graphql/public/types/UpdateDimensionFieldSuccess.ts b/packages/server/graphql/public/types/UpdateDimensionFieldSuccess.ts index d74d4f47c02..a6e28d189a1 100644 --- a/packages/server/graphql/public/types/UpdateDimensionFieldSuccess.ts +++ b/packages/server/graphql/public/types/UpdateDimensionFieldSuccess.ts @@ -1,4 +1,3 @@ -import MeetingPoker from '../../../database/types/MeetingPoker' import {UpdateDimensionFieldSuccessResolvers} from '../resolverTypes' export type UpdateDimensionFieldSuccessSource = { @@ -10,7 +9,8 @@ const UpdateDimensionFieldSuccess: UpdateDimensionFieldSuccessResolvers = { team: ({teamId}, _args, {dataLoader}) => dataLoader.get('teams').loadNonNull(teamId), meeting: async ({meetingId}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - return meeting as MeetingPoker + if (meeting.meetingType !== 'poker') throw new Error('Not a poker meeting') + return meeting } } diff --git a/packages/server/graphql/public/types/UpdateMeetingPromptSuccess.ts b/packages/server/graphql/public/types/UpdateMeetingPromptSuccess.ts index 50d348e1e46..0873d892c7d 100644 --- a/packages/server/graphql/public/types/UpdateMeetingPromptSuccess.ts +++ b/packages/server/graphql/public/types/UpdateMeetingPromptSuccess.ts @@ -1,4 +1,3 @@ -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {UpdateMeetingPromptSuccessResolvers} from '../resolverTypes' export type UpdateMeetingPromptSuccessSource = { @@ -8,7 +7,9 @@ export type UpdateMeetingPromptSuccessSource = { const UpdateMeetingPromptSuccess: UpdateMeetingPromptSuccessResolvers = { meeting: async (source, _args, {dataLoader}) => { const {meetingId} = source - return dataLoader.get('newMeetings').load(meetingId) as Promise + const meeting = await dataLoader.get('newMeetings').load(meetingId) + if (meeting.meetingType !== 'teamPrompt') throw new Error('Not a team prompt meeting') + return meeting } } diff --git a/packages/server/graphql/public/types/UpdateRecurrenceSettingsSuccess.ts b/packages/server/graphql/public/types/UpdateRecurrenceSettingsSuccess.ts index b94a914ca27..d9e4f566501 100644 --- a/packages/server/graphql/public/types/UpdateRecurrenceSettingsSuccess.ts +++ b/packages/server/graphql/public/types/UpdateRecurrenceSettingsSuccess.ts @@ -1,4 +1,3 @@ -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {UpdateRecurrenceSettingsSuccessResolvers} from '../resolverTypes' export type UpdateRecurrenceSettingsSuccessSource = { @@ -7,7 +6,9 @@ export type UpdateRecurrenceSettingsSuccessSource = { const UpdateRecurrenceSettingsSuccess: UpdateRecurrenceSettingsSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { - return dataLoader.get('newMeetings').load(meetingId) as Promise + const meeting = await dataLoader.get('newMeetings').load(meetingId) + if (meeting.meetingType !== 'teamPrompt') throw new Error('Not a team prompt') + return meeting } } diff --git a/packages/server/graphql/resolvers.ts b/packages/server/graphql/resolvers.ts index b02819208d0..09c762ab07a 100644 --- a/packages/server/graphql/resolvers.ts +++ b/packages/server/graphql/resolvers.ts @@ -3,7 +3,6 @@ import nullIfEmpty from 'parabol-client/utils/nullIfEmpty' import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' import {NewMeetingPhaseTypeEnum} from '../database/types/GenericMeetingPhase' import GenericMeetingStage from '../database/types/GenericMeetingStage' -import Meeting from '../database/types/Meeting' import Organization from '../database/types/Organization' import Task from '../database/types/Task' import User from '../database/types/User' @@ -116,7 +115,7 @@ export const resolveTeamMembers = ( : teamMembers } -export const resolveGQLStageFromId = (stageId: string | undefined, meeting: Meeting) => { +export const resolveGQLStageFromId = (stageId: string | undefined, meeting: AnyMeeting) => { const {id: meetingId, phases} = meeting const stageRes = findStageById(phases, stageId) if (!stageRes) return undefined diff --git a/packages/server/graphql/types/SetPhaseFocusPayload.ts b/packages/server/graphql/types/SetPhaseFocusPayload.ts index 3bb578e72fc..7f6147a522d 100644 --- a/packages/server/graphql/types/SetPhaseFocusPayload.ts +++ b/packages/server/graphql/types/SetPhaseFocusPayload.ts @@ -1,6 +1,6 @@ import {GraphQLNonNull, GraphQLObjectType} from 'graphql' import {REFLECT} from 'parabol-client/utils/constants' -import MeetingRetrospective from '../../database/types/MeetingRetrospective' +import {RetrospectiveMeeting as RetrospectiveMeetingSource} from '../../postgres/types/Meeting' import {GQLContext} from '../graphql' import {resolveNewMeeting} from '../resolvers' import ReflectPhase from './ReflectPhase' @@ -26,7 +26,7 @@ const SetPhaseFocusPayload = new GraphQLObjectType({ ) => { const meeting = (await dataLoader .get('newMeetings') - .load(meetingId)) as MeetingRetrospective + .load(meetingId)) as RetrospectiveMeetingSource return meeting.phases.find((phase) => phase.phaseType === REFLECT) } } diff --git a/packages/server/postgres/migrations/1726174453131_NewMeeting-phase1.ts b/packages/server/postgres/migrations/1726174453131_NewMeeting-phase1.ts new file mode 100644 index 00000000000..87318cabb7a --- /dev/null +++ b/packages/server/postgres/migrations/1726174453131_NewMeeting-phase1.ts @@ -0,0 +1,99 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + + // Notable changes + // - SlackTs is now a double precision + // - facilitatorUserId is nullable in the case of a user hard delete + // - hasScheduledEndTime index changed to scheduledEndTime + + await client.query(` + DO $$ + BEGIN + CREATE TABLE IF NOT EXISTS "NewMeeting" ( + "id" VARCHAR(100) PRIMARY KEY, + "isLegacy" BOOLEAN NOT NULL DEFAULT FALSE, + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "createdBy" VARCHAR(100), + "endedAt" TIMESTAMP WITH TIME ZONE, + "facilitatorStageId" VARCHAR(100) NOT NULL, + "facilitatorUserId" VARCHAR(100), + "meetingCount" INT NOT NULL, + "meetingNumber" INT NOT NULL, + "name" VARCHAR(100) NOT NULL, + "summarySentAt" TIMESTAMP WITH TIME ZONE, + "teamId" VARCHAR(100) NOT NULL, + "meetingType" "MeetingTypeEnum" NOT NULL, + "phases" JSONB NOT NULL, + "showConversionModal" BOOLEAN NOT NULL DEFAULT FALSE, + "meetingSeriesId" INT, + "scheduledEndTime" TIMESTAMP WITH TIME ZONE, + "summary" VARCHAR(10000), + "sentimentScore" DOUBLE PRECISION, + "usedReactjis" JSONB, + "slackTs" DOUBLE PRECISION, + "engagement" DOUBLE PRECISION, + "totalVotes" INT, + "maxVotesPerGroup" SMALLINT, + "disableAnonymity" BOOLEAN, + "commentCount" INT, + "taskCount" INT, + "agendaItemCount" INT, + "storyCount" INT, + "templateId" VARCHAR(100), + "topicCount" INT, + "reflectionCount" INT, + "transcription" JSONB, + "recallBotId" VARCHAR(255), + "videoMeetingURL" VARCHAR(2048), + "autogroupReflectionGroups" JSONB, + "resetReflectionGroups" JSONB, + "templateRefId" VARCHAR(25), + "meetingPrompt" VARCHAR(255), + CONSTRAINT "fk_createdBy" + FOREIGN KEY("createdBy") + REFERENCES "User"("id") + ON DELETE SET NULL, + CONSTRAINT "fk_facilitatorUserId" + FOREIGN KEY("facilitatorUserId") + REFERENCES "User"("id") + ON DELETE SET NULL, + CONSTRAINT "fk_teamId" + FOREIGN KEY("teamId") + REFERENCES "Team"("id") + ON DELETE CASCADE, + CONSTRAINT "fk_meetingSeriesId" + FOREIGN KEY("meetingSeriesId") + REFERENCES "MeetingSeries"("id") + ON DELETE SET NULL, + CONSTRAINT "fk_templateId" + FOREIGN KEY("templateId") + REFERENCES "MeetingTemplate"("id") + ON DELETE CASCADE + ); + CREATE INDEX IF NOT EXISTS "idx_NewMeeting_createdAt" ON "NewMeeting"("createdAt"); + CREATE INDEX IF NOT EXISTS "idx_NewMeeting_facilitatorUserId" ON "NewMeeting"("facilitatorUserId"); + CREATE INDEX IF NOT EXISTS "idx_NewMeeting_scheduledEndTime" ON "NewMeeting"("scheduledEndTime") WHERE "scheduledEndTime" IS NOT NULL AND "endedAt" IS NULL; + CREATE INDEX IF NOT EXISTS "idx_NewMeeting_meetingSeriesId" ON "NewMeeting"("meetingSeriesId") WHERE "meetingSeriesId" IS NOT NULL; + CREATE INDEX IF NOT EXISTS "idx_NewMeeting_teamId" ON "NewMeeting"("teamId"); + CREATE INDEX IF NOT EXISTS "idx_NewMeeting_templateId" ON "NewMeeting"("templateId") WHERE "templateId" IS NOT NULL; + DROP TRIGGER IF EXISTS "update_NewMeeting_updatedAt" ON "NewMeeting"; + CREATE TRIGGER "update_NewMeeting_updatedAt" BEFORE UPDATE ON "NewMeeting" FOR EACH ROW EXECUTE PROCEDURE "set_updatedAt"(); + END $$; +`) + + await client.end() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TABLE IF EXISTS "NewMeeting"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index affc2e06e00..d0eacbffb2e 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -2,8 +2,8 @@ import type {JSONContent} from '@tiptap/core' import {NotNull, sql} from 'kysely' import {NewMeetingPhaseTypeEnum} from '../graphql/public/resolverTypes' import getKysely from './getKysely' -import {ReactjiDB} from './types' - +import {AutogroupReflectionGroupType, ReactjiDB, TranscriptBlock, UsedReactjis} from './types' +import type {NewMeetingPhase} from './types/NewMeetingPhase' export const selectTimelineEvent = () => { return getKysely().selectFrom('TimelineEvent').selectAll().$narrowType< | { @@ -230,3 +230,53 @@ export const selectComments = () => .select(({fn}) => [fn('to_json', ['reactjis']).as('reactjis')]) export const selectReflectPrompts = () => getKysely().selectFrom('ReflectPrompt').selectAll() + +export const selectNewMeetings = () => + getKysely() + .selectFrom('NewMeeting') + .select(({fn}) => [ + 'id', + 'isLegacy', + 'createdAt', + 'updatedAt', + 'createdBy', + 'endedAt', + 'facilitatorStageId', + 'facilitatorUserId', + 'meetingCount', + 'meetingNumber', + 'name', + 'summarySentAt', + 'teamId', + 'meetingType', + 'showConversionModal', + 'meetingSeriesId', + 'scheduledEndTime', + 'summary', + 'sentimentScore', + 'slackTs', + 'engagement', + 'totalVotes', + 'maxVotesPerGroup', + 'disableAnonymity', + 'commentCount', + 'taskCount', + 'agendaItemCount', + 'storyCount', + 'templateId', + 'topicCount', + 'reflectionCount', + 'recallBotId', + 'videoMeetingURL', + 'templateRefId', + 'meetingPrompt', + fn('to_json', ['phases']).as('phases'), + fn('to_json', ['usedReactjis']).as('usedReactjis'), + fn('to_json', ['transcription']).as('transcription'), + fn('to_json', ['autogroupReflectionGroups']).as( + 'autogroupReflectionGroups' + ), + fn('to_json', ['resetReflectionGroups']).as( + 'resetReflectionGroups' + ) + ]) diff --git a/packages/server/postgres/types/Meeting.d.ts b/packages/server/postgres/types/Meeting.d.ts index 65d7995ce18..b4d38c02e0e 100644 --- a/packages/server/postgres/types/Meeting.d.ts +++ b/packages/server/postgres/types/Meeting.d.ts @@ -1,16 +1,97 @@ +import {NonNullableProps} from '../../../client/types/generics' import ActionMeetingMember from '../../database/types/ActionMeetingMember' -import MeetingAction from '../../database/types/MeetingAction' -import MeetingPoker from '../../database/types/MeetingPoker' -import MeetingRetrospective from '../../database/types/MeetingRetrospective' -import MeetingTeamPrompt from '../../database/types/MeetingTeamPrompt' import PokerMeetingMember from '../../database/types/PokerMeetingMember' import RetroMeetingMember from '../../database/types/RetroMeetingMember' import TeamPromptMeetingMember from '../../database/types/TeamPromptMeetingMember' -import {MeetingTypeEnum} from '../queries/generated/insertTeamQuery' +import {NewMeeting as NewMeetingDB} from '../pg' +import {NewMeeting} from './index.d' -export {MeetingTypeEnum} +import {Insertable} from 'kysely' +import { + CheckInMeetingPhase, + NewMeetingPhase, + PokerMeetingPhase, + RetroMeetingPhase, + TeamPromptPhase +} from './NewMeetingPhase' -export type AnyMeeting = MeetingRetrospective | MeetingPoker | MeetingAction | MeetingTeamPrompt +export type MeetingTypeEnum = NewMeeting['meetingType'] + +type BaseNewMeeting = Pick< + NewMeeting, + | 'id' + | 'isLegacy' + | 'createdAt' + | 'updatedAt' + | 'createdBy' + | 'endedAt' + | 'facilitatorStageId' + | 'facilitatorUserId' + | 'meetingCount' + | 'meetingNumber' + | 'name' + | 'summarySentAt' + | 'teamId' + | 'meetingType' + | 'showConversionModal' + | 'meetingSeriesId' + | 'scheduledEndTime' + | 'summary' + | 'sentimentScore' + | 'usedReactjis' + | 'slackTs' + | 'engagement' +> & {phases: NewMeetingPhase[]} + +type InsertableRetrospectiveMeeting = Insertable & { + meetingType: 'retrospective' + phases: RetroMeetingPhase[] + totalVotes: number + maxVotesPerGroup: number + disableAnonymity: boolean + templateId: string +} + +export type RetrospectiveMeeting = BaseNewMeeting & + NonNullableProps< + Pick + > & + Pick< + NewMeeting, + | 'commentCount' + | 'taskCount' + | 'topicCount' + | 'reflectionCount' + | 'transcription' + | 'recallBotId' + | 'videoMeetingURL' + | 'autogroupReflectionGroups' + | 'resetReflectionGroups' + > & { + meetingType: 'retrospective' + phases: RetroMeetingPhase[] + } + +export type PokerMeeting = BaseNewMeeting & + NonNullableProps> & + Pick & { + meetingType: 'poker' + phases: PokerMeetingPhase[] + } + +export type CheckInMeeting = BaseNewMeeting & + Pick & { + meetingType: 'action' + phases: CheckInMeetingPhase[] + } + +export type TeamPromptMeeting = BaseNewMeeting & + NonNullableProps> & { + meetingType: 'teamPrompt' + phases: TeamPromptPhase[] + } + +export type AnyMeeting = RetrospectiveMeeting | PokerMeeting | CheckInMeeting | TeamPromptMeeting export type AnyMeetingTeamMember = | PokerMeetingMember diff --git a/packages/server/postgres/types/NewMeetingPhase.d.ts b/packages/server/postgres/types/NewMeetingPhase.d.ts new file mode 100644 index 00000000000..6569deba9db --- /dev/null +++ b/packages/server/postgres/types/NewMeetingPhase.d.ts @@ -0,0 +1,204 @@ +interface GenericMeetingStage { + id: string + isAsync?: boolean | null + isComplete: boolean + isNavigable: boolean + isNavigableByFacilitator: boolean + startAt?: Date + endAt?: Date + scheduledEndTime?: Date | null + suggestedEndTime?: Date + suggestedTimeLimit?: number + viewCount: number + readyToAdvance?: string[] + phaseType: string +} + +interface AgendaItemStage extends GenericMeetingStage { + phaseType: 'agendaitems' + agendaItemId: string + discussionId: string +} + +interface CheckInStage extends GenericMeetingStage { + phaseType: 'checkin' + teamMemberId: string + durations?: number[] +} + +interface DiscussStage extends GenericMeetingStage { + phaseType: 'discuss' + reflectionGroupId: string + discussionId: string + sortOrder: number +} + +interface EstimateStage extends GenericMeetingStage { + phaseType: 'ESTIMATE' + creatorUserId: string + serviceTaskId: string + taskId: string + sortOrder: number + dimensionRefIdx: number + finalScore?: number + scores: { + userId: string + label: string + }[] + isVoting: boolean + discussionId: string +} + +interface ReflectStage extends GenericMeetingStage { + phaseType: 'reflect' +} + +interface TeamHealthStage extends GenericMeetingStage { + phaseType: 'TEAM_HEALTH' + votes: { + userId: string + vote: number + }[] + isRevealed: boolean + question: string + labels: string[] + durations?: number[] +} + +interface TeamPromptResponseStage extends GenericMeetingStage { + phaseType: 'RESPONSES' + teamMemberId: string + discussionId: string +} + +interface UpdatesStage extends GenericMeetingStage { + phaseType: 'updates' + teamMemberId: string + durations?: number[] +} + +interface FirstCallStage extends GenericMeetingStage { + phaseType: 'firstcall' +} + +interface LastCallStage extends GenericMeetingStage { + phaseType: 'lastcall' +} + +interface GroupStage extends GenericMeetingStage { + phaseType: 'group' +} + +interface VoteStage extends GenericMeetingStage { + phaseType: 'vote' +} + +interface ScopeStage extends GenericMeetingStage { + phaseType: 'SCOPE' +} + +interface GenericMeetingPhase { + id: string +} + +interface FirstCallPhase extends GenericMeetingPhase { + phaseType: 'firstcall' + stages: [FirstCallStage] +} + +interface LastCallPhase extends GenericMeetingPhase { + phaseType: 'lastcall' + stages: [LastCallStage] +} + +interface GroupPhase extends GenericMeetingPhase { + phaseType: 'group' + stages: [GroupStage] +} + +interface VotePhase extends GenericMeetingPhase { + phaseType: 'vote' + stages: [VoteStage] +} + +interface ScopePhase extends GenericMeetingPhase { + phaseType: 'SCOPE' + stages: [ScopeStage] +} + +interface AgendaItemPhase extends GenericMeetingPhase { + phaseType: 'agendaitems' + stages: AgendaItemStage[] +} + +const a: AgendaItemPhase + +interface CheckInPhase extends GenericMeetingPhase { + phaseType: 'checkin' + stages: [CheckInStage, ...CheckInStage[]] + checkInGreeting: {content: string; language: string} + checkInQuestion: string +} + +interface DiscussPhase extends GenericMeetingPhase { + phaseType: 'discuss' + stages: [DiscussStage, ...DiscussStage[]] +} + +interface EstimatePhase extends GenericMeetingPhase { + phaseType: 'ESTIMATE' + stages: EstimateStage[] +} + +interface ReflectPhase extends GenericMeetingPhase { + phaseType: 'reflect' + stages: [ReflectStage] + teamId: string + focusedPromptId?: string +} + +interface TeamHealthPhase extends GenericMeetingPhase { + phaseType: 'TEAM_HEALTH' + isRevealed: boolean + stages: [TeamHealthStage] +} + +interface TeamPromptResponsesPhase extends GenericMeetingPhase { + phaseType: 'RESPONSES' + stages: [TeamPromptResponseStage, ...TeamPromptResponseStage[]] +} + +interface UpdatesPhase extends GenericMeetingPhase { + phaseType: 'updates' + + stages: [UpdatesStage, ...UpdatesStage[]] +} + +export type RetroMeetingPhase = + | CheckInPhase + | TeamHealthPhase + | ReflectPhase + | GroupPhase + | VotePhase + | DiscussPhase + +export type PokerMeetingPhase = CheckInPhase | TeamHealthPhase | ScopePhase | EstimatePhase + +export type CheckInMeetingPhase = + | CheckInPhase + | TeamHealthPhase + | UpdatesPhase + | FirstCallPhase + | LastCallPhase + | AgendaItemPhase + +export type TeamPromptPhase = TeamPromptResponsesPhase + +export type NewMeetingPhase = + | RetroMeetingPhase + | PokerMeetingPhase + | CheckInMeetingPhase + | TeamPromptPhase + +type TupleToArray = T extends (infer U)[] ? U : never +export type NewMeetingStages = TupleToArray diff --git a/packages/server/postgres/types/index.d.ts b/packages/server/postgres/types/index.d.ts index affb35215d4..2186bdc5ee3 100644 --- a/packages/server/postgres/types/index.d.ts +++ b/packages/server/postgres/types/index.d.ts @@ -8,6 +8,7 @@ import { selectAgendaItems, selectComments, selectMeetingSettings, + selectNewMeetings, selectOrganizations, selectReflectPrompts, selectRetroReflections, @@ -26,6 +27,17 @@ type ExtractTypeFromQueryBuilderSelect any> = export type Discussion = Selectable export type ReactjiDB = {id: string; userId: string} +export type UsedReactjis = Record +export type TranscriptBlock = { + speaker: string + words: string +} + +export type AutogroupReflectionGroupType = { + groupTitle: string + reflectionIds: string[] +} + export interface Organization extends ExtractTypeFromQueryBuilderSelect {} export type OrganizationUser = Selectable @@ -58,3 +70,5 @@ export type SlackNotification = ExtractTypeFromQueryBuilderSelect export type ReflectPrompt = ExtractTypeFromQueryBuilderSelect + +export type NewMeeting = ExtractTypeFromQueryBuilderSelect diff --git a/packages/server/utils/RecallAIServerManager.ts b/packages/server/utils/RecallAIServerManager.ts index 920337d662e..87d5857ec7f 100644 --- a/packages/server/utils/RecallAIServerManager.ts +++ b/packages/server/utils/RecallAIServerManager.ts @@ -2,7 +2,7 @@ import api from 'api' import axios from 'axios' import {ExternalLinks} from '../../client/types/constEnums' import appOrigin from '../appOrigin' -import {TranscriptBlock} from '../database/types/MeetingRetrospective' +import {TranscriptBlock} from '../postgres/types' import {Logger} from './Logger' import sendToSentry from './sendToSentry' diff --git a/packages/server/utils/analytics/analytics.ts b/packages/server/utils/analytics/analytics.ts index 00b37a3fbc3..568cda427e8 100644 --- a/packages/server/utils/analytics/analytics.ts +++ b/packages/server/utils/analytics/analytics.ts @@ -3,16 +3,14 @@ import type {UpgradeCTALocationEnumType} from '../../../client/shared/UpgradeCTA 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 {TaskServiceEnum} from '../../database/types/Task' import {DataLoaderWorker} from '../../graphql/graphql' import {ModifyType, ReactableEnum} from '../../graphql/public/resolverTypes' import {IntegrationProviderServiceEnumType} from '../../graphql/types/IntegrationProviderServiceEnum' import {SlackNotification, TeamPromptResponse, TemplateScale} from '../../postgres/types' -import {MeetingTypeEnum} from '../../postgres/types/Meeting' +import {AnyMeeting, MeetingTypeEnum, RetrospectiveMeeting} from '../../postgres/types/Meeting' import {MeetingSeries} from '../../postgres/types/MeetingSeries' import {AmplitudeAnalytics} from './amplitude/AmplitudeAnalytics' import {createMeetingProperties} from './helpers' @@ -193,7 +191,7 @@ class Analytics { // meeting teamPromptEnd = async ( - completedMeeting: Meeting, + completedMeeting: AnyMeeting, meetingMembers: MeetingMember[], responses: TeamPromptResponse[], dataLoader: DataLoaderWorker @@ -220,7 +218,7 @@ class Analytics { } checkInEnd = async ( - completedMeeting: Meeting, + completedMeeting: AnyMeeting, meetingMembers: MeetingMember[], dataLoader: DataLoaderWorker ) => @@ -238,7 +236,7 @@ class Analytics { ) retrospectiveEnd = async ( - completedMeeting: MeetingRetrospective, + completedMeeting: RetrospectiveMeeting, meetingMembers: MeetingMember[], template: MeetingTemplate, dataLoader: DataLoaderWorker @@ -261,7 +259,7 @@ class Analytics { } sprintPokerEnd = ( - completedMeeting: Meeting, + completedMeeting: AnyMeeting, meetingMembers: MeetingMember[], template: MeetingTemplate, dataLoader: DataLoaderWorker @@ -282,7 +280,7 @@ class Analytics { private meetingEnd = async ( dataloader: DataLoaderWorker, userId: string, - completedMeeting: Meeting, + completedMeeting: AnyMeeting, meetingMembers: MeetingMember[], template?: MeetingTemplate, meetingSpecificProperties?: any @@ -295,7 +293,7 @@ class Analytics { }) } - meetingStarted = (user: AnalyticsUser, meeting: Meeting, template?: MeetingTemplate) => { + meetingStarted = (user: AnalyticsUser, meeting: AnyMeeting, template?: MeetingTemplate) => { this.track(user, 'Meeting Started', createMeetingProperties(meeting, undefined, template)) } @@ -307,7 +305,7 @@ class Analytics { this.track(user, 'Meeting Recurrence Stopped', meetingSeries) } - meetingJoined = (user: AnalyticsUser, meeting: Meeting) => { + meetingJoined = (user: AnalyticsUser, meeting: AnyMeeting) => { this.track(user, 'Meeting Joined', createMeetingProperties(meeting, undefined, undefined)) } @@ -326,7 +324,7 @@ class Analytics { commentAdded = ( user: AnalyticsUser, - meeting: Meeting, + meeting: AnyMeeting, isAnonymous: boolean, isAsync: boolean, isReply: boolean diff --git a/packages/server/utils/analytics/helpers.ts b/packages/server/utils/analytics/helpers.ts index fb3b90cddeb..1e9f965b50c 100644 --- a/packages/server/utils/analytics/helpers.ts +++ b/packages/server/utils/analytics/helpers.ts @@ -1,11 +1,10 @@ import {CHECKIN} from '../../../client/utils/constants' -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 {AnyMeeting} from '../../postgres/types/Meeting' export const createMeetingProperties = ( - meeting: Meeting, + meeting: AnyMeeting, meetingMembers?: MeetingMember[], template?: MeetingTemplate ) => { @@ -28,8 +27,6 @@ export const createMeetingProperties = ( meetingTemplateCategory: template?.mainCategory, meetingSeriesId: meeting.meetingSeriesId, disableAnonymity: - meetingType === 'retrospective' - ? (meeting as MeetingRetrospective).disableAnonymity ?? false - : undefined + meetingType === 'retrospective' ? meeting.disableAnonymity ?? false : undefined } } diff --git a/packages/server/utils/getPhase.ts b/packages/server/utils/getPhase.ts index 94a825bac70..8c7936e36ab 100644 --- a/packages/server/utils/getPhase.ts +++ b/packages/server/utils/getPhase.ts @@ -1,26 +1,13 @@ -import AgendaItemsPhase from '../database/types/AgendaItemsPhase' -import CheckInPhase from '../database/types/CheckInPhase' -import DiscussPhase from '../database/types/DiscussPhase' -import EstimatePhase from '../database/types/EstimatePhase' -import GenericMeetingPhase from '../database/types/GenericMeetingPhase' -import ReflectPhase from '../database/types/ReflectPhase' -import TeamHealthPhase from '../database/types/TeamHealthPhase' -import TeamPromptResponsesPhase from '../database/types/TeamPromptResponsesPhase' -import UpdatesPhase from '../database/types/UpdatesPhase' +import {NewMeetingPhase} from '../postgres/types/NewMeetingPhase' -interface PhaseTypeLookup { - agendaitems: AgendaItemsPhase - checkin: CheckInPhase - discuss: DiscussPhase - ESTIMATE: EstimatePhase - reflect: ReflectPhase - updates: UpdatesPhase - RESPONSES: TeamPromptResponsesPhase - TEAM_HEALTH: TeamHealthPhase -} - -const getPhase = (phases: GenericMeetingPhase[], phaseType: T) => { - return phases.find((phase) => phase.phaseType === phaseType) as unknown as PhaseTypeLookup[T] +const getPhase = ( + phases: NewMeetingPhase[], + phaseType: T +) => { + return phases.find((phase) => phase.phaseType === phaseType) as Extract< + NewMeetingPhase, + {phaseType: T} + > } export default getPhase