diff --git a/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx b/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx index 3d688e42d93..65f2f386017 100644 --- a/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx +++ b/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx @@ -5,16 +5,14 @@ import clsx from 'clsx' import React, {useEffect, useRef, useState} from 'react' import {useFragment} from 'react-relay' import {useHistory} from 'react-router' +import {RRule} from 'rrule' import {ActivityDetailsSidebar_teams$key} from '~/__generated__/ActivityDetailsSidebar_teams.graphql' import {ActivityDetailsSidebar_template$key} from '~/__generated__/ActivityDetailsSidebar_template.graphql' import StartRetrospectiveMutation from '~/mutations/StartRetrospectiveMutation' import StartSprintPokerMutation from '~/mutations/StartSprintPokerMutation' import UpdateReflectTemplateScopeMutation from '~/mutations/UpdateReflectTemplateScopeMutation' import {MeetingTypeEnum} from '../../__generated__/ActivityDetailsQuery.graphql' -import { - CreateGcalEventInput, - RecurrenceSettingsInput -} from '../../__generated__/StartRetrospectiveMutation.graphql' +import {CreateGcalEventInput} from '../../__generated__/StartRetrospectiveMutation.graphql' import useAtmosphere from '../../hooks/useAtmosphere' import useMutationProps from '../../hooks/useMutationProps' import SelectTemplateMutation from '../../mutations/SelectTemplateMutation' @@ -116,10 +114,7 @@ const ActivityDetailsSidebar = (props: Props) => { const {onError, onCompleted, submitting, submitMutation, error} = mutationProps const history = useHistory() - const handleStartActivity = ( - gcalInput?: CreateGcalEventInput, - recurrenceSettings?: RecurrenceSettingsInput - ) => { + const handleStartActivity = (name?: string, rrule?: RRule, gcalInput?: CreateGcalEventInput) => { if (submitting) return submitMutation() if (type === 'teamPrompt') { @@ -127,12 +122,8 @@ const ActivityDetailsSidebar = (props: Props) => { atmosphere, { teamId: selectedTeam.id, - recurrenceSettings: recurrenceSettings - ? { - rrule: recurrenceSettings.rrule?.toString(), - name: recurrenceSettings.name - } - : undefined, + name, + rrule: rrule?.toString(), gcalInput }, {history, onError, onCompleted} @@ -155,12 +146,8 @@ const ActivityDetailsSidebar = (props: Props) => { atmosphere, { teamId: selectedTeam.id, - recurrenceSettings: recurrenceSettings - ? { - rrule: recurrenceSettings.rrule?.toString(), - name: recurrenceSettings.name - } - : undefined, + name, + rrule: rrule?.toString(), gcalInput }, {history, onError, onCompleted} diff --git a/packages/client/components/ActivityLibrary/ScheduleMeetingButton.tsx b/packages/client/components/ActivityLibrary/ScheduleMeetingButton.tsx index 3b66168331c..dda2754f057 100644 --- a/packages/client/components/ActivityLibrary/ScheduleMeetingButton.tsx +++ b/packages/client/components/ActivityLibrary/ScheduleMeetingButton.tsx @@ -1,11 +1,9 @@ import graphql from 'babel-plugin-relay/macro' import React from 'react' import {useFragment} from 'react-relay' +import {RRule} from 'rrule' import {ScheduleMeetingButton_team$key} from '~/__generated__/ScheduleMeetingButton_team.graphql' -import { - CreateGcalEventInput, - RecurrenceSettingsInput -} from '../../__generated__/StartRetrospectiveMutation.graphql' +import {CreateGcalEventInput} from '../../__generated__/StartRetrospectiveMutation.graphql' import useModal from '../../hooks/useModal' import {MenuMutationProps} from '../../hooks/useMutationProps' import DialogContainer from '../DialogContainer' @@ -14,10 +12,7 @@ import SecondaryButton from '../SecondaryButton' type Props = { mutationProps: MenuMutationProps - handleStartActivity: ( - gcalInput?: CreateGcalEventInput, - recurrenceInput?: RecurrenceSettingsInput - ) => void + handleStartActivity: (name?: string, rrule?: RRule, gcalInput?: CreateGcalEventInput) => void teamRef: ScheduleMeetingButton_team$key placeholder: string withRecurrence?: boolean @@ -60,11 +55,8 @@ const ScheduleMeetingButton = (props: Props) => { const handleClick = () => { toggleModal() } - const onStartActivity = ( - gcalInput?: CreateGcalEventInput, - recurrenceInput?: RecurrenceSettingsInput - ) => { - handleStartActivity(gcalInput, recurrenceInput) + const onStartActivity = (name?: string, rrule?: RRule, gcalInput?: CreateGcalEventInput) => { + handleStartActivity(name, rrule, gcalInput) closeModal() } diff --git a/packages/client/components/Recurrence/EndRecurringMeetingModal.tsx b/packages/client/components/Recurrence/EndRecurringMeetingModal.tsx index 49c409083d9..2165e45b31f 100644 --- a/packages/client/components/Recurrence/EndRecurringMeetingModal.tsx +++ b/packages/client/components/Recurrence/EndRecurringMeetingModal.tsx @@ -86,7 +86,7 @@ export const EndRecurringMeetingModal = (props: Props) => { if (!isMeetingOnly) { UpdateRecurrenceSettingsMutation( atmosphere, - {meetingId, recurrenceSettings: {name: null, rrule: null}}, + {meetingId, name: null, rrule: null}, {onError, onCompleted} ) } else { diff --git a/packages/client/components/Recurrence/RecurrenceSettings.tsx b/packages/client/components/Recurrence/RecurrenceSettings.tsx index a1e039e913a..f5cca8d6f43 100644 --- a/packages/client/components/Recurrence/RecurrenceSettings.tsx +++ b/packages/client/components/Recurrence/RecurrenceSettings.tsx @@ -255,7 +255,7 @@ export const RecurrenceSettings = (props: Props) => { The next meeting in this series will be called{' '} - "{title} - {dayjs(recurrenceStartTime).format('MMM DD')}" + "{title} - {dayjs(new Date()).format('MMM DD')}" )} diff --git a/packages/client/components/Recurrence/UpdateRecurrenceSettingsModal.tsx b/packages/client/components/Recurrence/UpdateRecurrenceSettingsModal.tsx index da69cf7f869..63c17f230f6 100644 --- a/packages/client/components/Recurrence/UpdateRecurrenceSettingsModal.tsx +++ b/packages/client/components/Recurrence/UpdateRecurrenceSettingsModal.tsx @@ -175,10 +175,8 @@ export const UpdateRecurrenceSettingsModal = (props: Props) => { atmosphere, { meetingId: meeting.id, - recurrenceSettings: { - rrule: rrule?.toString(), - name: title - } + rrule: rrule?.toString(), + name: title }, {onError, onCompleted: onRecurrenceSettingsUpdated} ) @@ -190,7 +188,7 @@ export const UpdateRecurrenceSettingsModal = (props: Props) => { UpdateRecurrenceSettingsMutation( atmosphere, - {meetingId: meeting.id, recurrenceSettings: {rrule: null}}, + {meetingId: meeting.id, rrule: null}, {onError, onCompleted: onRecurrenceSettingsUpdated} ) } diff --git a/packages/client/components/ScheduleDialog.tsx b/packages/client/components/ScheduleDialog.tsx index 8f26e708164..351f242064f 100644 --- a/packages/client/components/ScheduleDialog.tsx +++ b/packages/client/components/ScheduleDialog.tsx @@ -7,10 +7,7 @@ import React, {ChangeEvent, useState} from 'react' import {useFragment} from 'react-relay' import {RRule} from 'rrule' import {ScheduleDialog_team$key} from '~/__generated__/ScheduleDialog_team.graphql' -import { - CreateGcalEventInput, - RecurrenceSettingsInput -} from '../__generated__/StartRetrospectiveMutation.graphql' +import {CreateGcalEventInput} from '../__generated__/StartRetrospectiveMutation.graphql' import useAtmosphere from '../hooks/useAtmosphere' import useForm from '../hooks/useForm' import {MenuMutationProps} from '../hooks/useMutationProps' @@ -34,7 +31,7 @@ const validateTitle = (title: string) => new Legitity(title).trim().min(2, `C’mon, you call that a title?`) interface Props { - onStartActivity: (gcalInput?: CreateGcalEventInput, recurrence?: RecurrenceSettingsInput) => void + onStartActivity: (name?: string, rrule?: RRule, gcalInput?: CreateGcalEventInput) => void placeholder: string teamRef: ScheduleDialog_team$key onCancel: () => void @@ -101,16 +98,15 @@ export const ScheduleDialog = (props: Props) => { } const handleSubmit = () => { - const title = fields.title.value || placeholder - const titleRes = validateTitle(title) - if (titleRes.error) { - fields.title.setError(titleRes.error) + const name = fields.title.value || placeholder + const nameRes = validateTitle(name) + if (nameRes.error) { + fields.title.setError(nameRes.error) return } const gcalEventInput = addedInvite ? { - title, startTimestamp: gcalInput.start.unix(), endTimestamp: gcalInput.end.unix(), timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, @@ -118,7 +114,7 @@ export const ScheduleDialog = (props: Props) => { videoType: gcalInput.videoType ?? undefined } : undefined - props.onStartActivity(gcalEventInput, rrule ? {rrule} : undefined) + props.onStartActivity(name, rrule ?? undefined, gcalEventInput) } const onAddInvite = () => { @@ -201,7 +197,11 @@ export const ScheduleDialog = (props: Props) => { - + )} diff --git a/packages/client/mutations/StartRetrospectiveMutation.ts b/packages/client/mutations/StartRetrospectiveMutation.ts index ec3a120f8b9..5e60fc72048 100644 --- a/packages/client/mutations/StartRetrospectiveMutation.ts +++ b/packages/client/mutations/StartRetrospectiveMutation.ts @@ -25,14 +25,11 @@ graphql` const mutation = graphql` mutation StartRetrospectiveMutation( $teamId: ID! - $recurrenceSettings: RecurrenceSettingsInput + $name: String + $rrule: RRule $gcalInput: CreateGcalEventInput ) { - startRetrospective( - teamId: $teamId - recurrenceSettings: $recurrenceSettings - gcalInput: $gcalInput - ) { + startRetrospective(teamId: $teamId, name: $name, rrule: $rrule, gcalInput: $gcalInput) { ... on ErrorPayload { error { message diff --git a/packages/client/mutations/StartTeamPromptMutation.ts b/packages/client/mutations/StartTeamPromptMutation.ts index bc8821c3bd7..7320d2b7094 100644 --- a/packages/client/mutations/StartTeamPromptMutation.ts +++ b/packages/client/mutations/StartTeamPromptMutation.ts @@ -19,14 +19,11 @@ graphql` const mutation = graphql` mutation StartTeamPromptMutation( $teamId: ID! - $recurrenceSettings: RecurrenceSettingsInput + $name: String + $rrule: RRule $gcalInput: CreateGcalEventInput ) { - startTeamPrompt( - teamId: $teamId - recurrenceSettings: $recurrenceSettings - gcalInput: $gcalInput - ) { + startTeamPrompt(teamId: $teamId, name: $name, rrule: $rrule, gcalInput: $gcalInput) { ... on ErrorPayload { error { message diff --git a/packages/client/mutations/UpdateRecurrenceSettingsMutation.ts b/packages/client/mutations/UpdateRecurrenceSettingsMutation.ts index af0168c01ea..8646e886116 100644 --- a/packages/client/mutations/UpdateRecurrenceSettingsMutation.ts +++ b/packages/client/mutations/UpdateRecurrenceSettingsMutation.ts @@ -21,11 +21,8 @@ graphql` ` const mutation = graphql` - mutation UpdateRecurrenceSettingsMutation( - $meetingId: ID! - $recurrenceSettings: RecurrenceSettingsInput! - ) { - updateRecurrenceSettings(meetingId: $meetingId, recurrenceSettings: $recurrenceSettings) { + mutation UpdateRecurrenceSettingsMutation($meetingId: ID!, $name: String, $rrule: RRule) { + updateRecurrenceSettings(meetingId: $meetingId, name: $name, rrule: $rrule) { ... on ErrorPayload { error { message diff --git a/packages/server/__tests__/startRetrospective.test.ts b/packages/server/__tests__/startRetrospective.test.ts index 17deae321e5..2dc04eb1bd0 100644 --- a/packages/server/__tests__/startRetrospective.test.ts +++ b/packages/server/__tests__/startRetrospective.test.ts @@ -8,8 +8,8 @@ test('Retro is named Retro #1 by default', async () => { const newRetro = await sendPublic({ query: ` - mutation StartRetrospectiveMutation($teamId: ID!, $recurrenceSettings: RecurrenceSettingsInput, $gcalInput: CreateGcalEventInput) { - startRetrospective(teamId: $teamId, recurrenceSettings: $recurrenceSettings, gcalInput: $gcalInput) { + mutation StartRetrospectiveMutation($teamId: ID!, $name: String, $rrule: RRule, $gcalInput: CreateGcalEventInput) { + startRetrospective(teamId: $teamId, name: $name, rrule: $rrule, gcalInput: $gcalInput) { ... on ErrorPayload { error { message @@ -34,7 +34,49 @@ test('Retro is named Retro #1 by default', async () => { startRetrospective: { meeting: { id: expect.anything(), - name: 'Retro 1' + name: 'Retro #1' + } + } + } + }) +}) + +test('Single Retro can be named', async () => { + await getRethink() + const {userId, authToken} = await signUp() + const {id: teamId} = (await getUserTeams(userId))[0] + + const name = 'My Retro' + const newRetro = await sendPublic({ + query: ` + mutation StartRetrospectiveMutation($teamId: ID!, $name: String, $rrule: RRule, $gcalInput: CreateGcalEventInput) { + startRetrospective(teamId: $teamId, name: $name, rrule: $rrule, gcalInput: $gcalInput) { + ... on ErrorPayload { + error { + message + } + } + ... on StartRetrospectiveSuccess { + meeting { + id + name + } + } + } + } + `, + variables: { + teamId, + name + }, + authToken + }) + expect(newRetro).toMatchObject({ + data: { + startRetrospective: { + meeting: { + id: expect.anything(), + name } } } @@ -49,8 +91,8 @@ test('Recurring retro is named like RetroSeries Jan 1', async () => { const now = new Date() const newRetro = await sendPublic({ query: ` - mutation StartRetrospectiveMutation($teamId: ID!, $recurrenceSettings: RecurrenceSettingsInput, $gcalInput: CreateGcalEventInput) { - startRetrospective(teamId: $teamId, recurrenceSettings: $recurrenceSettings, gcalInput: $gcalInput) { + mutation StartRetrospectiveMutation($teamId: ID!, $name: String, $rrule: RRule, $gcalInput: CreateGcalEventInput) { + startRetrospective(teamId: $teamId, name: $name, rrule: $rrule, gcalInput: $gcalInput) { ... on ErrorPayload { error { message @@ -67,10 +109,8 @@ test('Recurring retro is named like RetroSeries Jan 1', async () => { `, variables: { teamId, - recurrenceSettings: { - rrule: 'DTSTART;TZID=Europe/Berlin:20240117T060000\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=WE', - name: 'RetroSeries' - } + rrule: 'DTSTART;TZID=Europe/Berlin:20240117T060000\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=WE', + name: 'RetroSeries' }, authToken }) diff --git a/packages/server/database/types/MeetingAction.ts b/packages/server/database/types/MeetingAction.ts index bc0157cdd16..fee9b580b8d 100644 --- a/packages/server/database/types/MeetingAction.ts +++ b/packages/server/database/types/MeetingAction.ts @@ -9,7 +9,7 @@ interface Input { id?: string teamId: string meetingCount: number - name?: string + name: string phases: [CheckInMeetingPhase, ...CheckInMeetingPhase[]] facilitatorUserId: string } @@ -32,7 +32,7 @@ export default class MeetingAction extends Meeting { phases, facilitatorUserId, meetingType: 'action', - name: name ?? `Check-in #${meetingCount + 1}` + name }) } } diff --git a/packages/server/database/types/MeetingPoker.ts b/packages/server/database/types/MeetingPoker.ts index 94c87c2826a..5883fd6d8ac 100644 --- a/packages/server/database/types/MeetingPoker.ts +++ b/packages/server/database/types/MeetingPoker.ts @@ -8,7 +8,7 @@ interface Input { id: string teamId: string meetingCount: number - name?: string + name: string phases: [PokerPhase, ...PokerPhase[]] facilitatorUserId: string templateId: string @@ -35,7 +35,7 @@ export default class MeetingPoker extends Meeting { phases, facilitatorUserId, meetingType: 'poker', - name: name ?? `Sprint Poker #${meetingCount + 1}` + name }) this.templateId = templateId this.templateRefId = templateRefId diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index 46a03715026..4de0beb1abb 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -858,3 +858,27 @@ export const fileStoreAsset = (parent: RootDataLoader) => { } ) } + +export const meetingCount = (parent: RootDataLoader) => { + return new DataLoader<{teamId: string; meetingType: MeetingTypeEnum}, number, string>( + async (keys) => { + const r = await getRethink() + const res = await Promise.all( + keys.map(async ({teamId, meetingType}) => { + return r + .table('NewMeeting') + .getAll(teamId, {index: 'teamId'}) + .filter({meetingType: meetingType as any}) + .count() + .default(0) + .run() + }) + ) + return res + }, + { + ...parent.dataLoaderOptions, + cacheKeyFn: (key) => `${key.teamId}:${key.meetingType}` + } + ) +} diff --git a/packages/server/graphql/mutations/helpers/createGcalEvent.ts b/packages/server/graphql/mutations/helpers/createGcalEvent.ts index 575fc9e4cdd..8aaa7efeddd 100644 --- a/packages/server/graphql/mutations/helpers/createGcalEvent.ts +++ b/packages/server/graphql/mutations/helpers/createGcalEvent.ts @@ -24,6 +24,7 @@ const convertRruleToGcal = (rrule: RRule | null | undefined) => { } type Input = { + name: string gcalInput?: CreateGcalEventInput | null meetingId: string viewerId: string @@ -35,12 +36,12 @@ type Input = { const createGcalEvent = async ( input: Input ): Promise<{gcalSeriesId?: string; error?: StandardMutationError}> => { - const {gcalInput, meetingId, viewerId, dataLoader, teamId, rrule} = input + const {name, gcalInput, meetingId, viewerId, dataLoader, teamId, rrule} = input if (!gcalInput) { return {} } - const {startTimestamp, endTimestamp, title, timeZone, invitees, videoType} = gcalInput + const {startTimestamp, endTimestamp, timeZone, invitees, videoType} = gcalInput const gcalAuth = await dataLoader.get('freshGcalAuth').load({teamId, userId: viewerId}) if (!gcalAuth) { @@ -78,7 +79,7 @@ const createGcalEvent = async ( const recurrence = convertRruleToGcal(rrule) const eventInput = { - summary: title, + summary: name, description, start: { dateTime: startDateTime, @@ -115,14 +116,14 @@ const createGcalEvent = async ( export type UpdateGcalSeriesInput = { gcalSeriesId: string - title?: string + name?: string rrule: RRule | null userId: string teamId: string dataLoader: DataLoaderWorker } export const updateGcalSeries = async (input: UpdateGcalSeriesInput) => { - const {gcalSeriesId, title, rrule, userId, teamId, dataLoader} = input + const {gcalSeriesId, name, rrule, userId, teamId, dataLoader} = input const gcalAuth = await dataLoader.get('freshGcalAuth').load({teamId, userId}) if (!gcalAuth) { @@ -146,7 +147,7 @@ export const updateGcalSeries = async (input: UpdateGcalSeriesInput) => { eventId: gcalSeriesId, requestBody: { recurrence, - summary: title + summary: name }, conferenceDataVersion: 1 }) diff --git a/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts b/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts index 221557c3b09..a756463e106 100644 --- a/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts @@ -1,4 +1,3 @@ -import getRethink from '../../../database/rethinkDriver' import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import generateUID from '../../../generateUID' import {MeetingTypeEnum} from '../../../postgres/types/Meeting' @@ -16,21 +15,14 @@ const safeCreateRetrospective = async ( videoMeetingURL?: string meetingSeriesId?: number scheduledEndTime?: Date - name?: string + name: string }, dataLoader: DataLoaderWorker ) => { - const r = await getRethink() const {teamId, facilitatorUserId, name} = meetingSettings const meetingType: MeetingTypeEnum = 'retrospective' const [meetingCount, team] = await Promise.all([ - r - .table('NewMeeting') - .getAll(teamId, {index: 'teamId'}) - .filter({meetingType}) - .count() - .default(0) - .run(), + dataLoader.get('meetingCount').load({teamId, meetingType}), dataLoader.get('teams').loadNonNull(teamId) ]) @@ -38,7 +30,6 @@ const safeCreateRetrospective = async ( const {showConversionModal} = organization const meetingId = generateUID() - const meetingName = name ?? `Retro ${meetingCount + 1}` const phases = await createNewMeetingPhases( facilitatorUserId, teamId, @@ -54,7 +45,7 @@ const safeCreateRetrospective = async ( phases, showConversionModal, ...meetingSettings, - name: meetingName + name }) } diff --git a/packages/server/graphql/mutations/startSprintPoker.ts b/packages/server/graphql/mutations/startSprintPoker.ts index 0c5388ba834..b25cc5364de 100644 --- a/packages/server/graphql/mutations/startSprintPoker.ts +++ b/packages/server/graphql/mutations/startSprintPoker.ts @@ -1,4 +1,4 @@ -import {GraphQLID, GraphQLNonNull} from 'graphql' +import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import toTeamMemberId from '../../../client/utils/relay/toTeamMemberId' import getRethink from '../../database/rethinkDriver' @@ -74,6 +74,10 @@ export default { type: new GraphQLNonNull(GraphQLID), description: 'The team starting the meeting' }, + name: { + type: GraphQLString, + description: 'The name of the meeting' + }, gcalInput: { type: CreateGcalEventInput, description: 'The gcal event to create. If not provided, no event will be created' @@ -81,7 +85,11 @@ export default { }, async resolve( _source: unknown, - {teamId, gcalInput}: {teamId: string; gcalInput?: CreateGcalEventInputType}, + { + teamId, + name, + gcalInput + }: {teamId: string; name: string | null | undefined; gcalInput?: CreateGcalEventInputType}, {authToken, socketId: mutatorId, dataLoader}: GQLContext ) { const r = await getRethink() @@ -128,6 +136,7 @@ export default { const meeting = new MeetingPoker({ id: meetingId, teamId, + name: name ?? `Sprint Poker #${meetingCount + 1}`, meetingCount, phases, facilitatorUserId: viewerId, @@ -175,7 +184,14 @@ export default { ]) IntegrationNotifier.startMeeting(dataLoader, meetingId, teamId) analytics.meetingStarted(viewer, meeting, template) - const {error} = await createGcalEvent({gcalInput, meetingId, teamId, viewerId, dataLoader}) + const {error} = await createGcalEvent({ + name: meeting.name, + gcalInput, + meetingId, + teamId, + viewerId, + dataLoader + }) const data = {teamId, meetingId: meetingId, hasGcalError: !!error?.message} publish(SubscriptionChannel.TEAM, teamId, 'StartSprintPokerSuccess', data, subOptions) return data diff --git a/packages/server/graphql/public/mutations/startCheckIn.ts b/packages/server/graphql/public/mutations/startCheckIn.ts index 9c5f10de62b..c411c3fcd9d 100644 --- a/packages/server/graphql/public/mutations/startCheckIn.ts +++ b/packages/server/graphql/public/mutations/startCheckIn.ts @@ -17,7 +17,7 @@ import {MutationResolvers} from '../resolverTypes' const startCheckIn: MutationResolvers['startCheckIn'] = async ( _source, - {teamId, gcalInput}, + {teamId, name, gcalInput}, context ) => { const r = await getRethink() @@ -59,6 +59,7 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( const meeting = new MeetingAction({ id: meetingId, teamId, + name: name ?? `Check-in #${meetingCount + 1}`, meetingCount, phases, facilitatorUserId: viewerId @@ -92,7 +93,14 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( ]) IntegrationNotifier.startMeeting(dataLoader, meetingId, teamId) analytics.meetingStarted(viewer, meeting) - const {error} = await createGcalEvent({gcalInput, teamId, meetingId, viewerId, dataLoader}) + const {error} = await createGcalEvent({ + name: meeting.name, + gcalInput, + teamId, + meetingId, + viewerId, + dataLoader + }) const data = {teamId, meetingId, hasGcalError: !!error?.message} publish(SubscriptionChannel.TEAM, teamId, 'StartCheckInSuccess', data, subOptions) return data diff --git a/packages/server/graphql/public/mutations/startRetrospective.ts b/packages/server/graphql/public/mutations/startRetrospective.ts index cc240ec1fb4..b4c7ba3ddad 100644 --- a/packages/server/graphql/public/mutations/startRetrospective.ts +++ b/packages/server/graphql/public/mutations/startRetrospective.ts @@ -20,7 +20,7 @@ import {startNewMeetingSeries} from './updateRecurrenceSettings' const startRetrospective: MutationResolvers['startRetrospective'] = async ( _source, - {teamId, recurrenceSettings, gcalInput}, + {teamId, name, rrule, gcalInput}, {authToken, socketId: mutatorId, dataLoader} ) => { const r = await getRethink() @@ -36,12 +36,14 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( if (unpaidError) return standardError(new Error(unpaidError), {userId: viewerId}) // RESOLUTION - const viewer = await dataLoader.get('users').loadNonNull(viewerId) - const meetingType: MeetingTypeEnum = 'retrospective' - const meetingSettings = (await dataLoader - .get('meetingSettingsByType') - .load({teamId, meetingType})) as MeetingSettingsRetrospective + const [viewer, meetingSettings, meetingCount] = await Promise.all([ + dataLoader.get('users').loadNonNull(viewerId), + dataLoader + .get('meetingSettingsByType') + .load({teamId, meetingType}) as Promise, + dataLoader.get('meetingCount').load({teamId, meetingType}) + ]) const { id: meetingSettingsId, @@ -52,9 +54,12 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( videoMeetingURL } = meetingSettings - const name = recurrenceSettings?.name - ? createMeetingSeriesTitle(recurrenceSettings.name, new Date(), 'UTC') - : undefined + const meetingName = !name + ? `Retro #${meetingCount + 1}` + : rrule + ? createMeetingSeriesTitle(name, new Date(), 'UTC') + : name + const meetingSeriesName = name || meetingName const meeting = await safeCreateRetrospective( { @@ -65,7 +70,7 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( disableAnonymity, templateId: selectedTemplateId, videoMeetingURL: videoMeetingURL ?? undefined, - name + name: meetingName }, dataLoader ) @@ -93,8 +98,7 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( lastMeetingType: meetingType } const [meetingSeries] = await Promise.all([ - recurrenceSettings?.rrule && - startNewMeetingSeries(meeting, recurrenceSettings.rrule, recurrenceSettings.name), + rrule && startNewMeetingSeries(meeting, rrule, meetingSeriesName), r .table('MeetingMember') .insert( @@ -120,11 +124,12 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( IntegrationNotifier.startMeeting(dataLoader, meetingId, teamId) analytics.meetingStarted(viewer, meeting, template) const {error, gcalSeriesId} = await createGcalEvent({ + name: meetingSeriesName, gcalInput, meetingId, teamId, viewerId, - rrule: recurrenceSettings?.rrule, + rrule, dataLoader }) if (meetingSeries && gcalSeriesId) { diff --git a/packages/server/graphql/public/mutations/startTeamPrompt.ts b/packages/server/graphql/public/mutations/startTeamPrompt.ts index 75dcd21b975..393e802e1b7 100644 --- a/packages/server/graphql/public/mutations/startTeamPrompt.ts +++ b/packages/server/graphql/public/mutations/startTeamPrompt.ts @@ -18,7 +18,7 @@ const MEETING_START_DELAY_MS = 3000 const startTeamPrompt: MutationResolvers['startTeamPrompt'] = async ( _source, - {teamId, recurrenceSettings, gcalInput}, + {teamId, name, rrule, gcalInput}, {authToken, dataLoader, socketId: mutatorId} ) => { const r = await getRethink() @@ -46,11 +46,8 @@ const startTeamPrompt: MutationResolvers['startTeamPrompt'] = async ( } //TODO: use client timezone here (requires sending it from the client and passing it via gql context most likely) - const meetingName = createMeetingSeriesTitle( - recurrenceSettings?.name || 'Standup', - new Date(), - 'UTC' - ) + const meetingName = createMeetingSeriesTitle(name || 'Standup', new Date(), 'UTC') + const eventName = rrule ? name || 'Standup' : meetingName const meeting = await safeCreateTeamPrompt(meetingName, teamId, viewerId, r, dataLoader) await Promise.all([ @@ -64,19 +61,22 @@ const startTeamPrompt: MutationResolvers['startTeamPrompt'] = async ( ]) const {id: meetingId} = meeting - if (recurrenceSettings?.rrule) { - const meetingSeries = await startNewMeetingSeries( - meeting, - recurrenceSettings.rrule, - recurrenceSettings.name - ) + if (rrule) { + const meetingSeries = await startNewMeetingSeries(meeting, rrule, name) // meeting was modified if a new meeting series was created dataLoader.get('newMeetings').clear(meetingId) analytics.recurrenceStarted(viewer, meetingSeries) } IntegrationNotifier.startMeeting(dataLoader, meetingId, teamId) analytics.meetingStarted(viewer, meeting) - const {error} = await createGcalEvent({gcalInput, meetingId, teamId, viewerId, dataLoader}) + const {error} = await createGcalEvent({ + name: eventName, + gcalInput, + meetingId, + teamId, + viewerId, + dataLoader + }) const data = {teamId, meetingId: meetingId, hasGcalError: !!error?.message} publish(SubscriptionChannel.TEAM, teamId, 'StartTeamPromptSuccess', data, subOptions) return data diff --git a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts index d3ce01392c1..96f4f1b590d 100644 --- a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts +++ b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts @@ -124,7 +124,7 @@ const updateGCalRecurrenceRule = (oldRule: RRule, newRule: RRule | null | undefi const updateRecurrenceSettings: MutationResolvers['updateRecurrenceSettings'] = async ( _source, - {meetingId, recurrenceSettings}, + {meetingId, name, rrule}, {authToken, dataLoader, socketId: mutatorId} ) => { const viewerId = getUserId(authToken) @@ -152,46 +152,39 @@ const updateRecurrenceSettings: MutationResolvers['updateRecurrenceSettings'] = const meetingSeries = await dataLoader.get('meetingSeries').loadNonNull(meetingSeriesId) const {gcalSeriesId, teamId, facilitatorId, recurrenceRule} = meetingSeries - if (!recurrenceSettings.rrule) { + if (!rrule) { await stopMeetingSeries(meetingSeries) analytics.recurrenceStopped(viewer, meetingSeries) } else { - await updateMeetingSeries(meetingSeries, recurrenceSettings.rrule) + await updateMeetingSeries(meetingSeries, rrule) analytics.recurrenceStarted(viewer, meetingSeries) } if (gcalSeriesId) { - const rrule = updateGCalRecurrenceRule( - RRule.fromString(recurrenceRule), - recurrenceSettings.rrule - ) + const newRrule = updateGCalRecurrenceRule(RRule.fromString(recurrenceRule), rrule) await updateGcalSeries({ gcalSeriesId, - title: recurrenceSettings.name ?? undefined, - rrule, + name: name ?? undefined, + rrule: newRrule, teamId, userId: facilitatorId, dataLoader }) } - if (recurrenceSettings.name) { - await updateMeetingSeriesQuery({title: recurrenceSettings.name}, meetingSeries.id) + if (name) { + await updateMeetingSeriesQuery({title: name}, meetingSeries.id) } dataLoader.get('meetingSeries').clear(meetingSeries.id) } else { - if (!recurrenceSettings.rrule) { + if (!rrule) { return standardError( new Error('When meeting is not recurring, recurrence rule has to be provided'), {userId: viewerId} ) } - const newMeetingSeries = await startNewMeetingSeries( - meeting, - recurrenceSettings.rrule, - recurrenceSettings.name - ) + const newMeetingSeries = await startNewMeetingSeries(meeting, rrule, name) analytics.recurrenceStarted(viewer, newMeetingSeries) } diff --git a/packages/server/graphql/public/typeDefs/CreateGcalEventInput.graphql b/packages/server/graphql/public/typeDefs/CreateGcalEventInput.graphql new file mode 100644 index 00000000000..634041f5edd --- /dev/null +++ b/packages/server/graphql/public/typeDefs/CreateGcalEventInput.graphql @@ -0,0 +1,22 @@ +input CreateGcalEventInput { + """ + The start timestamp of the event + """ + startTimestamp: Int! + """ + The end timestamp of the event + """ + endTimestamp: Int! + """ + The timezone of the event + """ + timeZone: String! + """ + The type of video call to added to the gcal event. If not provided, no video call will be added + """ + videoType: GcalVideoTypeEnum + """ + The emails that will be invited to the gcal event. If not provided, the no one will be invited + """ + invitees: [Email!] +} diff --git a/packages/server/graphql/public/typeDefs/RecurrenceSettingsInput.graphql b/packages/server/graphql/public/typeDefs/RecurrenceSettingsInput.graphql deleted file mode 100644 index 04a67753120..00000000000 --- a/packages/server/graphql/public/typeDefs/RecurrenceSettingsInput.graphql +++ /dev/null @@ -1,14 +0,0 @@ -""" -Recurrence settings for a meeting series, used to create or update a meeting series -""" -input RecurrenceSettingsInput { - """ - The recurrence rule for the meeting series in RRULE format - """ - rrule: RRule - - """ - Meeting series name, by default "Standup" - """ - name: String -} diff --git a/packages/server/graphql/public/typeDefs/_legacy.graphql b/packages/server/graphql/public/typeDefs/_legacy.graphql index 4b4a247f5b1..bcf909a9706 100644 --- a/packages/server/graphql/public/typeDefs/_legacy.graphql +++ b/packages/server/graphql/public/typeDefs/_legacy.graphql @@ -5851,6 +5851,10 @@ type Mutation { """ teamId: ID! """ + The name of the meeting + """ + name: String + """ The gcal input if creating a gcal event """ gcalInput: CreateGcalEventInput diff --git a/packages/server/graphql/public/typeDefs/startCheckIn.graphql b/packages/server/graphql/public/typeDefs/startCheckIn.graphql index 35a8c0f4b69..ba6eb2e8cc9 100644 --- a/packages/server/graphql/public/typeDefs/startCheckIn.graphql +++ b/packages/server/graphql/public/typeDefs/startCheckIn.graphql @@ -20,6 +20,10 @@ extend type Mutation { """ teamId: ID! """ + The name of the meeting + """ + name: String + """ The gcal input if creating a gcal event """ gcalInput: CreateGcalEventInput diff --git a/packages/server/graphql/public/typeDefs/startRetrospective.graphql b/packages/server/graphql/public/typeDefs/startRetrospective.graphql index 91bb03261c2..8c60c60d8ef 100644 --- a/packages/server/graphql/public/typeDefs/startRetrospective.graphql +++ b/packages/server/graphql/public/typeDefs/startRetrospective.graphql @@ -8,9 +8,13 @@ extend type Mutation { """ teamId: ID! """ - The recurrence settings of the meeting + Name of the meeting or series """ - recurrenceSettings: RecurrenceSettingsInput + name: String + """ + The recurrence rule for the meeting series in RRULE format + """ + rrule: RRule """ The gcal input if creating a gcal event """ @@ -29,30 +33,3 @@ type StartRetrospectiveSuccess { team: Team! hasGcalError: Boolean } - -input CreateGcalEventInput { - """ - The title of the event - """ - title: String! - """ - The start timestamp of the event - """ - startTimestamp: Int! - """ - The end timestamp of the event - """ - endTimestamp: Int! - """ - The timezone of the event - """ - timeZone: String! - """ - The type of video call to added to the gcal event. If not provided, no video call will be added - """ - videoType: GcalVideoTypeEnum - """ - The emails that will be invited to the gcal event. If not provided, the no one will be invited - """ - invitees: [Email!] -} diff --git a/packages/server/graphql/public/typeDefs/startTeamPrompt.graphql b/packages/server/graphql/public/typeDefs/startTeamPrompt.graphql index 01bd98483b5..ea02edf4a2b 100644 --- a/packages/server/graphql/public/typeDefs/startTeamPrompt.graphql +++ b/packages/server/graphql/public/typeDefs/startTeamPrompt.graphql @@ -8,9 +8,13 @@ extend type Mutation { """ teamId: ID! """ - The recurrence settings of the meeting + Meeting or series name, by default "Standup" """ - recurrenceSettings: RecurrenceSettingsInput + name: String + """ + The recurrence rule for the meeting series in RRULE format + """ + rrule: RRule """ The gcal input if creating a gcal event. If not provided, no gcal event will be created """ @@ -37,30 +41,3 @@ type StartTeamPromptSuccess { """ hasGcalError: Boolean } - -input CreateGcalEventInput { - """ - The title of the event - """ - title: String! - """ - The start timestamp of the event - """ - startTimestamp: Int! - """ - The end timestamp of the event - """ - endTimestamp: Int! - """ - The timezone of the event - """ - timeZone: String! - """ - The type of video call to added to the gcal event. If not provided, no video call will be added - """ - videoType: GcalVideoTypeEnum - """ - The emails that will be invited to the gcal event. If not provided, the no one will be invited - """ - invitees: [Email!] -} diff --git a/packages/server/graphql/public/typeDefs/updateRecurrenceSettings.graphql b/packages/server/graphql/public/typeDefs/updateRecurrenceSettings.graphql index 1715cb3fa79..fad9895053d 100644 --- a/packages/server/graphql/public/typeDefs/updateRecurrenceSettings.graphql +++ b/packages/server/graphql/public/typeDefs/updateRecurrenceSettings.graphql @@ -12,9 +12,14 @@ extend type Mutation { meetingId: ID! """ - Updated recurrence settings + New meeting series name """ - recurrenceSettings: RecurrenceSettingsInput! + name: String + + """ + The recurrence rule for the meeting series in RRULE format + """ + rrule: RRule ): UpdateRecurrenceSettingsPayload! } diff --git a/packages/server/graphql/public/types/CreateGcalEventInput.ts b/packages/server/graphql/public/types/CreateGcalEventInput.ts index 49172372bd2..97a4e6ad98c 100644 --- a/packages/server/graphql/public/types/CreateGcalEventInput.ts +++ b/packages/server/graphql/public/types/CreateGcalEventInput.ts @@ -11,10 +11,6 @@ import GraphQLEmailType from '../../types/GraphQLEmailType' const CreateGcalEventInput = new GraphQLInputObjectType({ name: 'CreateGcalEventInput', fields: () => ({ - title: { - type: new GraphQLNonNull(GraphQLString), - description: 'The title of the meeting' - }, startTimestamp: { type: new GraphQLNonNull(GraphQLInt), description: 'The start dateTime of the meeting' @@ -40,14 +36,11 @@ const CreateGcalEventInput = new GraphQLInputObjectType({ }) }) -type GcalVideoTypeEnum = 'meet' | 'zoom' - export type CreateGcalEventInputType = { - title: string startTimestamp: number endTimestamp: number timeZone: string - videoType?: GcalVideoTypeEnum + videoType?: 'meet' | 'zoom' invitees?: string[] }