Skip to content

Commit

Permalink
feat: Allow retro meeting series naming (#9348)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dschoordsch committed Jan 29, 2024
1 parent 7505fc3 commit 894b716
Show file tree
Hide file tree
Showing 14 changed files with 156 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import DialogContainer from '../DialogContainer'
interface Props {
onRecurrenceSettingsUpdated: (recurrenceSettings: RecurrenceSettings) => void
recurrenceSettings: RecurrenceSettings
placeholder: string
}

export const ActivityDetailsRecurrenceSettings = (props: Props) => {
const {onRecurrenceSettingsUpdated, recurrenceSettings} = props
const {onRecurrenceSettingsUpdated, recurrenceSettings, placeholder} = props
const {togglePortal, modalPortal} = useModal({
id: 'activityDetailsRecurrenceSettings'
})
Expand All @@ -32,6 +33,7 @@ export const ActivityDetailsRecurrenceSettings = (props: Props) => {
<RecurrenceSettings
onRecurrenceSettingsUpdated={onRecurrenceSettingsUpdated}
recurrenceSettings={recurrenceSettings}
placeholder={placeholder}
/>
</DialogContainer>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ const ActivityDetailsSidebar = (props: Props) => {
<ActivityDetailsRecurrenceSettings
onRecurrenceSettingsUpdated={setRecurrenceSettings}
recurrenceSettings={recurrenceSettings}
placeholder='Retro'
/>
)}
</>
Expand All @@ -422,6 +423,7 @@ const ActivityDetailsSidebar = (props: Props) => {
<ActivityDetailsRecurrenceSettings
onRecurrenceSettingsUpdated={setRecurrenceSettings}
recurrenceSettings={recurrenceSettings}
placeholder='Standup'
/>
)}
</>
Expand Down
1 change: 1 addition & 0 deletions packages/client/components/NewMeeting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const NewMeeting = (props: Props) => {
<NewMeetingRecurrenceSettings
onRecurrenceSettingsUpdated={setRecurrenceSettings}
recurrenceSettings={recurrenceSettings}
placeholder='Standup'
/>
)}
</SettingsRow>
Expand Down
4 changes: 3 additions & 1 deletion packages/client/components/NewMeetingRecurrenceSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import {RecurrenceSettings} from './TeamPrompt/Recurrence/RecurrenceSettings'
interface Props {
onRecurrenceSettingsUpdated: (recurrenceSettings: RecurrenceSettings) => void
recurrenceSettings: RecurrenceSettings
placeholder: string
}

export const NewMeetingRecurrenceSettings = (props: Props) => {
const {onRecurrenceSettingsUpdated, recurrenceSettings} = props
const {onRecurrenceSettingsUpdated, recurrenceSettings, placeholder} = props
const {togglePortal, menuPortal, originRef, portalStatus} = useMenu<HTMLDivElement>(
MenuPosition.LOWER_RIGHT,
{
Expand Down Expand Up @@ -41,6 +42,7 @@ export const NewMeetingRecurrenceSettings = (props: Props) => {
<RecurrenceSettings
onRecurrenceSettingsUpdated={onRecurrenceSettingsUpdated}
recurrenceSettings={recurrenceSettings}
placeholder={placeholder}
/>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,11 @@ interface Props {
validationErrors: string[] | undefined
) => void
recurrenceSettings: RecurrenceSettings
placeholder: string
}

export const RecurrenceSettings = (props: Props) => {
const {onRecurrenceSettingsUpdated, recurrenceSettings} = props
const {onRecurrenceSettingsUpdated, recurrenceSettings, placeholder} = props
const {name: meetingSeriesName, rrule: recurrenceRule} = recurrenceSettings
const [name, setName] = React.useState(meetingSeriesName)
const [nameError, setNameError] = React.useState<string | undefined>()
Expand Down Expand Up @@ -268,7 +269,7 @@ export const RecurrenceSettings = (props: Props) => {
hasError={!!nameError}
id='series-title'
type='text'
placeholder='Standup'
placeholder={placeholder}
value={meetingSeriesName}
onChange={handleNameChange}
min={1}
Expand Down Expand Up @@ -304,7 +305,7 @@ export const RecurrenceSettings = (props: Props) => {
<Description>
The next meeting in this series will be called{' '}
<span className='font-semibold'>
"{meetingSeriesName || 'Standup'} - {dayjs(recurrenceStartTime).format('MMM DD')}"
"{meetingSeriesName || placeholder} - {dayjs(recurrenceStartTime).format('MMM DD')}"
</span>
</Description>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ export const UpdateRecurrenceSettingsModal = (props: Props) => {

const meeting = useFragment(
graphql`
fragment UpdateRecurrenceSettingsModal_meeting on TeamPromptMeeting {
fragment UpdateRecurrenceSettingsModal_meeting on NewMeeting {
id
meetingType
meetingSeries {
id
title
Expand All @@ -103,6 +104,13 @@ export const UpdateRecurrenceSettingsModal = (props: Props) => {
meetingRef
)

const {meetingType} = meeting
const placeholder =
meetingType === 'teamPrompt'
? 'Standup'
: meetingType === 'retrospective'
? 'Retrospective'
: 'Meeting'
const currentRecurrenceRule = meeting.meetingSeries?.recurrenceRule
const atmosphere = useAtmosphere()
const isMeetingSeriesActive = meeting.meetingSeries?.cancelledAt === null
Expand Down Expand Up @@ -191,6 +199,7 @@ export const UpdateRecurrenceSettingsModal = (props: Props) => {
<RecurrenceSettings
recurrenceSettings={newRecurrenceSettings}
onRecurrenceSettingsUpdated={handleNewRecurrenceSettings}
placeholder={placeholder}
/>
<StyledCloseButton onClick={closeModal}>
<CloseIcon />
Expand Down
95 changes: 95 additions & 0 deletions packages/server/__tests__/startRetrospective.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import ms from 'ms'
import {RRule} from 'rrule'
import getRethink from '../database/rethinkDriver'
import MeetingTeamPrompt from '../database/types/MeetingTeamPrompt'
import TeamPromptResponsesPhase from '../database/types/TeamPromptResponsesPhase'
import generateUID from '../generateUID'
import {insertMeetingSeries as insertMeetingSeriesQuery} from '../postgres/queries/insertMeetingSeries'
import {getUserTeams, sendIntranet, sendPublic, signUp} from './common'

test('Retro is named Retro #1 by default', async () => {
const r = await getRethink()
const {userId, authToken} = await signUp()
const {id: teamId} = (await getUserTeams(userId))[0]

const newRetro = await sendPublic({
query: `
mutation StartRetrospectiveMutation($teamId: ID!, $recurrenceSettings: RecurrenceSettingsInput, $gcalInput: CreateGcalEventInput) {
startRetrospective(teamId: $teamId, recurrenceSettings: $recurrenceSettings, gcalInput: $gcalInput) {
... on ErrorPayload {
error {
message
}
}
... on StartRetrospectiveSuccess {
meeting {
id
name
}
}
}
}
`,
variables: {
teamId
},
authToken
})
expect(newRetro).toMatchObject({
data: {
startRetrospective: {
meeting: {
id: expect.anything(),
name: 'Retro 1'
}
}
}
})
})

test('Recurring retro is named like RetroSeries Jan 1', async () => {
const r = await getRethink()
const {userId, authToken} = await signUp()
const {id: teamId} = (await getUserTeams(userId))[0]

const now = new Date()
const newRetro = await sendPublic({
query: `
mutation StartRetrospectiveMutation($teamId: ID!, $recurrenceSettings: RecurrenceSettingsInput, $gcalInput: CreateGcalEventInput) {
startRetrospective(teamId: $teamId, recurrenceSettings: $recurrenceSettings, gcalInput: $gcalInput) {
... on ErrorPayload {
error {
message
}
}
... on StartRetrospectiveSuccess {
meeting {
id
name
}
}
}
}
`,
variables: {
teamId,
recurrenceSettings: {
rrule: 'DTSTART;TZID=Europe/Berlin:20240117T060000\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=WE',
name: 'RetroSeries'
}
},
authToken
})

const formattedDate = now.toLocaleDateString('en-US', {month: 'short', day: 'numeric'}, 'UTC')
expect(newRetro).toMatchObject({
data: {
startRetrospective: {
meeting: {
id: expect.anything(),
name: `RetroSeries - ${formattedDate}`
}
}
}
})
})
4 changes: 2 additions & 2 deletions packages/server/database/types/MeetingRetrospective.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface Input {
id?: string
teamId: string
meetingCount: number
name?: string
name: string
phases: [GenericMeetingPhase, ...GenericMeetingPhase[]]
facilitatorUserId: string
showConversionModal?: boolean
Expand Down Expand Up @@ -84,7 +84,7 @@ export default class MeetingRetrospective extends Meeting {
phases,
facilitatorUserId,
meetingType: 'retrospective',
name: name ?? `Retro #${meetingCount + 1}`,
name,
meetingSeriesId,
scheduledEndTime
})
Expand Down
14 changes: 0 additions & 14 deletions packages/server/database/types/MeetingTeamPrompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,6 @@ export function isMeetingTeamPrompt(meeting: Meeting): meeting is MeetingTeamPro
return meeting.meetingType === 'teamPrompt'
}

export function createTeamPromptTitle(
meetingSeriesName: string,
startTime: Date,
timeZone: string
) {
const formattedDate = startTime.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
timeZone
})

return `${meetingSeriesName} - ${formattedDate}`
}

export default class MeetingTeamPrompt extends Meeting {
meetingType!: 'teamPrompt'
meetingPrompt: string
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export function createMeetingSeriesTitle(
meetingSeriesName: string,
startTime: Date,
timeZone: string
) {
const formattedDate = startTime.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
timeZone
})

return `${meetingSeriesName} - ${formattedDate}`
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ const safeCreateRetrospective = async (
videoMeetingURL?: string
meetingSeriesId?: number
scheduledEndTime?: Date
name?: string
},
dataLoader: DataLoaderWorker
) => {
const r = await getRethink()
const {teamId, facilitatorUserId} = meetingSettings
const {teamId, facilitatorUserId, name} = meetingSettings
const meetingType: MeetingTypeEnum = 'retrospective'
const [meetingCount, team] = await Promise.all([
r
Expand All @@ -37,6 +38,7 @@ const safeCreateRetrospective = async (
const {showConversionModal} = organization

const meetingId = generateUID()
const meetingName = name ?? `Retro ${meetingCount + 1}`
const phases = await createNewMeetingPhases(
facilitatorUserId,
teamId,
Expand All @@ -51,7 +53,8 @@ const safeCreateRetrospective = async (
meetingCount,
phases,
showConversionModal,
...meetingSettings
...meetingSettings,
name: meetingName
})
}

Expand Down
19 changes: 9 additions & 10 deletions packages/server/graphql/private/mutations/processRecurrence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums'
import {getRRuleDateFromJSDate, getJSDateFromRRuleDate} from 'parabol-client/shared/rruleUtil'
import {RRule} from 'rrule'
import getRethink from '../../../database/rethinkDriver'
import MeetingTeamPrompt, {
createTeamPromptTitle,
isMeetingTeamPrompt
} from '../../../database/types/MeetingTeamPrompt'
import MeetingTeamPrompt, {isMeetingTeamPrompt} from '../../../database/types/MeetingTeamPrompt'
import {getActiveMeetingSeries} from '../../../postgres/queries/getActiveMeetingSeries'
import {MeetingSeries} from '../../../postgres/types/MeetingSeries'
import {analytics} from '../../../utils/analytics/analytics'
Expand All @@ -24,6 +21,7 @@ import MeetingRetrospective, {
import safeEndRetrospective from '../../mutations/helpers/safeEndRetrospective'
import safeCreateRetrospective from '../../mutations/helpers/safeCreateRetrospective'
import MeetingSettingsRetrospective from '../../../database/types/MeetingSettingsRetrospective'
import {createMeetingSeriesTitle} from '../../mutations/helpers/createMeetingSeriesTitle'

const startRecurringMeeting = async (
meetingSeries: MeetingSeries,
Expand Down Expand Up @@ -52,13 +50,13 @@ const startRecurringMeeting = async (
? getJSDateFromRRuleDate(nextMeetingStartDate)
: undefined

const meetingName = createMeetingSeriesTitle(
meetingSeries.title,
startTime,
rrule.options.tzid ?? 'UTC'
)
const meeting = await (async () => {
if (meetingSeries.meetingType === 'teamPrompt') {
const meetingName = createTeamPromptTitle(
meetingSeries.title,
startTime,
rrule.options.tzid ?? 'UTC'
)
const teamPromptMeeting = lastMeeting as MeetingTeamPrompt | null
const meeting = await safeCreateTeamPrompt(
meetingName,
Expand Down Expand Up @@ -92,7 +90,8 @@ const startRecurringMeeting = async (
templateId,
videoMeetingURL: undefined,
meetingSeriesId: meetingSeries.id,
scheduledEndTime
scheduledEndTime,
name: meetingName
},
dataLoader
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import isStartMeetingLocked from '../../mutations/helpers/isStartMeetingLocked'
import {IntegrationNotifier} from '../../mutations/helpers/notifications/IntegrationNotifier'
import {startNewMeetingSeries} from './updateRecurrenceSettings'
import safeCreateRetrospective from '../../mutations/helpers/safeCreateRetrospective'
import {createMeetingSeriesTitle} from '../../mutations/helpers/createMeetingSeriesTitle'

const startRetrospective: MutationResolvers['startRetrospective'] = async (
_source,
Expand Down Expand Up @@ -50,6 +51,10 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async (
videoMeetingURL
} = meetingSettings

const name = recurrenceSettings?.name
? createMeetingSeriesTitle(recurrenceSettings.name, new Date(), 'UTC')
: undefined

const meeting = await safeCreateRetrospective(
{
teamId,
Expand All @@ -58,7 +63,8 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async (
maxVotesPerGroup,
disableAnonymity,
templateId: selectedTemplateId,
videoMeetingURL: videoMeetingURL ?? undefined
videoMeetingURL: videoMeetingURL ?? undefined,
name
},
dataLoader
)
Expand Down

0 comments on commit 894b716

Please sign in to comment.