Skip to content

Commit

Permalink
feat: add functionality to change templates during a retro (#9544)
Browse files Browse the repository at this point in the history
  • Loading branch information
nickoferrall committed Mar 25, 2024
1 parent 2171065 commit e6434e1
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 11 deletions.
11 changes: 6 additions & 5 deletions codegen.json
Expand Up @@ -49,6 +49,7 @@
"ActionMeeting": "../../database/types/MeetingAction#default",
"ActionMeetingMember": "../../database/types/ActionMeetingMember#default as ActionMeetingMemberDB",
"AddApprovedOrganizationDomainsSuccess": "./types/AddApprovedOrganizationDomainsSuccess#AddApprovedOrganizationDomainsSuccessSource",
"AddReactjiToReactableSuccess": "./types/AddReactjiToReactableSuccess#AddReactjiToReactableSuccessSource",
"AddReflectTemplateSuccess": "./types/AddReflectTemplateSuccess#AddReflectTemplateSuccessSource",
"AddPokerTemplateSuccess": "./types/AddPokerTemplateSuccess#AddPokerTemplateSuccessSource",
"AddTranscriptionBotSuccess": "./types/AddTranscriptionBotSuccess#AddTranscriptionBotSuccessSource",
Expand All @@ -74,20 +75,20 @@
"InviteToTeamPayload": "./types/InviteToTeamPayload#InviteToTeamPayloadSource",
"JiraIssue": "./types/JiraIssue#JiraIssueSource",
"JiraRemoteProject": "../types/JiraRemoteProject#JiraRemoteProjectSource",
"MeetingSeries": "../../postgres/types/MeetingSeries#MeetingSeries",
"Kudos": "../../postgres/types/Kudos#Kudos",
"MeetingSeries": "../../postgres/types/MeetingSeries#MeetingSeries",
"MeetingTemplate": "../../database/types/MeetingTemplate#default",
"NewMeeting": "../../postgres/types/Meeting#AnyMeeting",
"NewMeetingPhase": "../../database/types/GenericMeetingPhase #default as GenericMeetingPhaseDB",
"NotificationMeetingStageTimeLimitEnd": "../../database/types/NotificationMeetingStageTimeLimitEnd#default as NotificationMeetingStageTimeLimitEndDB",
"NotificationTeamInvitation": "../../database/types/NotificationTeamInvitation#default as NotificationTeamInvitationDB",
"NotifyDiscussionMentioned": "../../database/types/NotificationDiscussionMentioned#default as NotificationDiscussionMentionedDB",
"NotifyKickedOut": "../../database/types/NotificationKickedOut#default",
"NotifyMentioned": "../../database/types/NotificationMentioned#default as NotificationMentionedDB",
"NotifyPaymentRejected": "../../database/types/NotificationPaymentRejected#default",
"NotifyPromoteToOrgLeader": "../../database/types/NotificationPromoteToBillingLeader#default",
"NotifyRequestToJoinOrg": "../../database/types/NotificationRequestToJoinOrg#default",
"NotifyResponseMentioned": "../../database/types/NotificationResponseMentioned#default as NotificationResponseMentionedDB",
"NotifyMentioned": "../../database/types/NotificationMentioned#default as NotificationMentionedDB",
"NotifyResponseReplied": "../../database/types/NotifyResponseReplied#default as NotifyResponseRepliedDB",
"NotifyTaskInvolves": "../../database/types/NotificationTaskInvolves#default",
"NotifyTeamArchived": "../../database/types/NotificationTeamArchived#default",
Expand All @@ -96,18 +97,18 @@
"PokerMeeting": "../../database/types/MeetingPoker#default as MeetingPoker",
"PokerMeetingMember": "../../database/types/MeetingPokerMeetingMember#default as PokerMeetingMemberDB",
"RRule": "rrule#RRule",
"Reactable": "../../database/types/Reactable#Reactable",
"ReflectPrompt": "../../database/types/RetrospectivePrompt#default",
"ReflectTemplate": "../../database/types/ReflectTemplate#default",
"RemoveApprovedOrganizationDomainsSuccess": "./types/RemoveApprovedOrganizationDomainsSuccess#RemoveApprovedOrganizationDomainsSuccessSource",
"RemoveIntegrationSearchQuerySuccess": "./types/RemoveIntegrationSearchQuerySuccess#RemoveIntegrationSearchQuerySuccessSource",
"RemoveTeamMemberPayload": "./types/RemoveTeamMemberPayload#RemoveTeamMemberPayloadSource",
"RequestToJoinDomainSuccess": "./types/RequestToJoinDomainSuccess#RequestToJoinDomainSuccessSource",
"ResetReflectionGroupsSuccess": "./types/ResetReflectionGroupsSuccess#ResetReflectionGroupsSuccessSource",
"RetroReflectionGroup": "../../database/types/RetroReflectionGroup#default as RetroReflectionGroupDB",
"RetroReflection": "../../database/types/RetroReflection#default as RetroReflectionDB",
"RetroReflectionGroup": "../../database/types/RetroReflectionGroup#default as RetroReflectionGroupDB",
"RetrospectiveMeeting": "../../database/types/MeetingRetrospective#default",
"RetrospectiveMeetingMember": "../../database/types/RetroMeetingMember#default",
"Reactable": "../../database/types/Reactable#Reactable",
"RetrospectiveMeetingSettings": "../../database/types/MeetingSettingsRetrospective#default",
"SAML": "./types/SAML#SAMLSource",
"SetMeetingSettingsPayload": "../types/SetMeetingSettingsPayload#SetMeetingSettingsPayloadSource",
Expand Down Expand Up @@ -141,13 +142,13 @@
"UpdateMeetingPromptSuccess": "./types/UpdateMeetingPromptSuccess#UpdateMeetingPromptSuccessSource",
"UpdateOrgPayload": "./types/UpdateOrgPayload#UpdateOrgPayloadSource",
"UpdateRecurrenceSettingsSuccess": "./types/UpdateRecurrenceSettingsSuccess#UpdateRecurrenceSettingsSuccessSource",
"UpdateMeetingTemplateSuccess": "./types/UpdateMeetingTemplateSuccess#UpdateMeetingTemplateSuccessSource",
"UpdateTaskPayload": "./types/UpdateTaskPayload#UpdateTaskPayloadSource",
"UpdateTemplateCategorySuccess": "./types/UpdateTemplateCategorySuccess#UpdateTemplateCategorySuccessSource",
"UpdateUserProfilePayload": "./types/UpdateUserProfilePayload#UpdateUserProfilePayloadSource",
"UpdatedNotification": "./types/AddedNotification#UpdatedNotificationSource",
"UpgradeToTeamTierSuccess": "./types/UpgradeToTeamTierSuccess#UpgradeToTeamTierSuccessSource",
"UpsertTeamPromptResponseSuccess": "./types/UpsertTeamPromptResponseSuccess#UpsertTeamPromptResponseSuccessSource",
"AddReactjiToReactableSuccess": "./types/AddReactjiToReactableSuccess#AddReactjiToReactableSuccessSource",
"User": "../../postgres/types/IUser#default as IUser",
"UserLogInPayload": "./types/UserLogInPayload#UserLogInPayloadSource"
}
Expand Down
14 changes: 12 additions & 2 deletions packages/client/components/RetroDrawer.tsx
Expand Up @@ -24,6 +24,7 @@ const RetroDrawer = (props: Props) => {
viewer {
meeting(meetingId: $meetingId) {
... on RetrospectiveMeeting {
id
reflectionGroups {
id
}
Expand Down Expand Up @@ -60,9 +61,13 @@ const RetroDrawer = (props: Props) => {
setShowDrawer(!showDrawer)
}

const handleCloseDrawer = () => {
setShowDrawer(false)
}

useEffect(() => {
if (hasReflections && showDrawer) {
setShowDrawer(false)
handleCloseDrawer()
}
}, [hasReflections])

Expand Down Expand Up @@ -97,7 +102,12 @@ const RetroDrawer = (props: Props) => {
</div>
</div>
{templates.map((template) => (
<RetroDrawerTemplateCard key={template.node.id} templateRef={template.node} />
<RetroDrawerTemplateCard
key={template.node.id}
meetingId={meeting.id!}
templateRef={template.node}
handleCloseDrawer={handleCloseDrawer}
/>
))}
</div>
</Drawer>
Expand Down
29 changes: 25 additions & 4 deletions packages/client/components/RetroDrawerTemplateCard.tsx
Expand Up @@ -7,17 +7,25 @@ import {ActivityLibraryCard} from './ActivityLibrary/ActivityLibraryCard'
import {ActivityCardImage} from './ActivityLibrary/ActivityCard'
import {RetroDrawerTemplateCard_template$key} from '~/__generated__/RetroDrawerTemplateCard_template.graphql'
import {CategoryID, CATEGORY_THEMES} from '././ActivityLibrary/Categories'
import UpdateMeetingTemplateMutation from '../mutations/UpdateMeetingTemplateMutation'
import useMutationProps from '../hooks/useMutationProps'
import useAtmosphere from '../hooks/useAtmosphere'

interface Props {
templateRef: RetroDrawerTemplateCard_template$key
meetingId: string
handleCloseDrawer: () => void
}

const RetroDrawerTemplateCard = (props: Props) => {
const {templateRef} = props
const {templateRef, meetingId, handleCloseDrawer} = props
const {onError, onCompleted} = useMutationProps()
const atmosphere = useAtmosphere()
const template = useFragment(
graphql`
fragment RetroDrawerTemplateCard_template on MeetingTemplate {
...ActivityLibraryCardDescription_template
id
name
category
illustrationUrl
Expand All @@ -27,29 +35,42 @@ const RetroDrawerTemplateCard = (props: Props) => {
templateRef
)

const handleClick = () => {
UpdateMeetingTemplateMutation(
atmosphere,
{
meetingId: meetingId,
templateId: template.id
},
{onError, onCompleted}
)
handleCloseDrawer()
}

return (
<div className='px-4 py-2'>
<form className='px-4 py-2' onClick={handleClick}>
<ActivityLibraryCard
className='group aspect-[256/160] flex-1 hover:cursor-pointer'
theme={CATEGORY_THEMES[template.category as CategoryID]}
title={template.name}
type='retrospective'
badge={
!template.isFree ? (
<ActivityBadge className='m-2 bg-gold-300 text-grape-700'>Premium</ActivityBadge>
) : null
}
>
<ActivityCardImage
category='retrospective'
className='group-hover/card:hidden'
src={template.illustrationUrl}
category='retrospective'
/>
<ActivityLibraryCardDescription
className='hidden group-hover/card:flex'
templateRef={template}
/>
</ActivityLibraryCard>
</div>
</form>
)
}
export default RetroDrawerTemplateCard
51 changes: 51 additions & 0 deletions packages/client/mutations/UpdateMeetingTemplateMutation.ts
@@ -0,0 +1,51 @@
import graphql from 'babel-plugin-relay/macro'
import {commitMutation} from 'react-relay'
import {StandardMutation} from '../types/relayMutations'
import {UpdateMeetingTemplateMutation as TUpdateMeetingTemplateMutation} from '../__generated__/UpdateMeetingTemplateMutation.graphql'

graphql`
fragment UpdateMeetingTemplateMutation_meeting on UpdateMeetingTemplateSuccess {
meeting {
... on RetrospectiveMeeting {
id
templateId
phases {
id
... on ReflectPhase {
reflectPrompts {
id
}
}
}
}
}
}
`

const mutation = graphql`
mutation UpdateMeetingTemplateMutation($meetingId: ID!, $templateId: ID!) {
updateMeetingTemplate(meetingId: $meetingId, templateId: $templateId) {
... on ErrorPayload {
error {
message
}
}
...UpdateMeetingTemplateMutation_meeting @relay(mask: false)
}
}
`

const UpdateMeetingTemplateMutation: StandardMutation<TUpdateMeetingTemplateMutation> = (
atmosphere,
variables,
{onError, onCompleted}
) => {
return commitMutation<TUpdateMeetingTemplateMutation>(atmosphere, {
mutation,
variables,
onCompleted,
onError
})
}

export default UpdateMeetingTemplateMutation
3 changes: 3 additions & 0 deletions packages/client/subscriptions/MeetingSubscription.ts
Expand Up @@ -150,6 +150,9 @@ const subscription = graphql`
UpdateRetroMaxVotesSuccess {
...UpdateRetroMaxVotesMutation_meeting @relay(mask: false)
}
UpdateMeetingTemplateSuccess {
...UpdateMeetingTemplateMutation_meeting @relay(mask: false)
}
VoteForReflectionGroupPayload {
...VoteForReflectionGroupMutation_meeting @relay(mask: false)
}
Expand Down
49 changes: 49 additions & 0 deletions packages/server/graphql/public/mutations/updateMeetingTemplate.ts
@@ -0,0 +1,49 @@
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'
import standardError from '../../../utils/standardError'
import {MutationResolvers} from '../resolverTypes'

const updateMeetingTemplate: MutationResolvers['updateMeetingTemplate'] = async (
_source,
{meetingId, templateId},
{authToken, dataLoader, socketId: mutatorId}
) => {
const viewerId = getUserId(authToken)
const r = await getRethink()
const operationId = dataLoader.share()
const subOptions = {mutatorId, operationId}
const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingRetrospective
if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId})
if (!isTeamMember(authToken, meeting.teamId)) {
return standardError(new Error('Team not found'), {userId: viewerId})
}
const reflections = await dataLoader.get('retroReflectionsByMeetingId').load(meetingId)
if (reflections.length > 0) {
return standardError(new Error('Cannot change template after reflections have been created'), {
userId: viewerId
})
}
const reflectPhase = getPhase(meeting.phases, 'reflect')
const hasCompletedReflectPhase = reflectPhase.stages.every((stage) => stage.isComplete)
if (hasCompletedReflectPhase) {
return standardError(
new Error('Cannot change template after reflection phase has been completed'),
{
userId: viewerId
}
)
}

await r.table('NewMeeting').get(meetingId).update({templateId}).run()
meeting.templateId = templateId

const data = {meetingId, templateId}
publish(SubscriptionChannel.MEETING, meetingId, 'UpdateMeetingTemplateSuccess', data, subOptions)
return data
}

export default updateMeetingTemplate
Expand Up @@ -54,6 +54,7 @@ type MeetingSubscriptionPayload {
SetPokerSpectateSuccess: SetPokerSpectateSuccess
SetTaskEstimateSuccess: SetTaskEstimateSuccess
UpsertTeamPromptResponseSuccess: UpsertTeamPromptResponseSuccess
UpdateMeetingTemplateSuccess: UpdateMeetingTemplateSuccess
}

type NotificationSubscriptionPayload {
Expand Down
@@ -0,0 +1,27 @@
extend type Mutation {
"""
Update a meeting template
"""
updateMeetingTemplate(
"""
The id of the meeting
"""
meetingId: ID!
"""
The id of the meeting template
"""
templateId: ID!
): UpdateMeetingTemplatePayload!
}

"""
Return value for updateMeetingTemplate, which could be an error
"""
union UpdateMeetingTemplatePayload = ErrorPayload | UpdateMeetingTemplateSuccess

type UpdateMeetingTemplateSuccess {
"""
The updated meeting
"""
meeting: NewMeeting!
}
@@ -0,0 +1,14 @@
import {UpdateMeetingTemplateSuccessResolvers} from '../resolverTypes'

export type UpdateMeetingTemplateSuccessSource = {
meetingId: string
}

const UpdateMeetingTemplateSuccess: UpdateMeetingTemplateSuccessResolvers = {
meeting: async ({meetingId}, _args, {dataLoader}) => {
const meeting = await dataLoader.get('newMeetings').load(meetingId)
return meeting
}
}

export default UpdateMeetingTemplateSuccess

0 comments on commit e6434e1

Please sign in to comment.