diff --git a/codegen.json b/codegen.json
index a0a38ad29f0..83f9993f249 100644
--- a/codegen.json
+++ b/codegen.json
@@ -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",
@@ -74,8 +75,8 @@
"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",
@@ -83,11 +84,11 @@
"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",
@@ -96,6 +97,7 @@
"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",
@@ -103,11 +105,10 @@
"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",
@@ -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"
}
diff --git a/packages/client/components/RetroDrawer.tsx b/packages/client/components/RetroDrawer.tsx
index 8d6404ada0e..6249386cabf 100644
--- a/packages/client/components/RetroDrawer.tsx
+++ b/packages/client/components/RetroDrawer.tsx
@@ -24,6 +24,7 @@ const RetroDrawer = (props: Props) => {
viewer {
meeting(meetingId: $meetingId) {
... on RetrospectiveMeeting {
+ id
reflectionGroups {
id
}
@@ -60,9 +61,13 @@ const RetroDrawer = (props: Props) => {
setShowDrawer(!showDrawer)
}
+ const handleCloseDrawer = () => {
+ setShowDrawer(false)
+ }
+
useEffect(() => {
if (hasReflections && showDrawer) {
- setShowDrawer(false)
+ handleCloseDrawer()
}
}, [hasReflections])
@@ -97,7 +102,12 @@ const RetroDrawer = (props: Props) => {
{templates.map((template) => (
-
+
))}
diff --git a/packages/client/components/RetroDrawerTemplateCard.tsx b/packages/client/components/RetroDrawerTemplateCard.tsx
index c126f6d0d6c..73fa7c30e3e 100644
--- a/packages/client/components/RetroDrawerTemplateCard.tsx
+++ b/packages/client/components/RetroDrawerTemplateCard.tsx
@@ -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
@@ -27,12 +35,25 @@ const RetroDrawerTemplateCard = (props: Props) => {
templateRef
)
+ const handleClick = () => {
+ UpdateMeetingTemplateMutation(
+ atmosphere,
+ {
+ meetingId: meetingId,
+ templateId: template.id
+ },
+ {onError, onCompleted}
+ )
+ handleCloseDrawer()
+ }
+
return (
-
+
)
}
export default RetroDrawerTemplateCard
diff --git a/packages/client/mutations/UpdateMeetingTemplateMutation.ts b/packages/client/mutations/UpdateMeetingTemplateMutation.ts
new file mode 100644
index 00000000000..c4dd1d6951b
--- /dev/null
+++ b/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 = (
+ atmosphere,
+ variables,
+ {onError, onCompleted}
+) => {
+ return commitMutation(atmosphere, {
+ mutation,
+ variables,
+ onCompleted,
+ onError
+ })
+}
+
+export default UpdateMeetingTemplateMutation
diff --git a/packages/client/subscriptions/MeetingSubscription.ts b/packages/client/subscriptions/MeetingSubscription.ts
index 65e3978b03c..d6f30e0a0f1 100644
--- a/packages/client/subscriptions/MeetingSubscription.ts
+++ b/packages/client/subscriptions/MeetingSubscription.ts
@@ -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)
}
diff --git a/packages/server/graphql/public/mutations/updateMeetingTemplate.ts b/packages/server/graphql/public/mutations/updateMeetingTemplate.ts
new file mode 100644
index 00000000000..a0be265f8de
--- /dev/null
+++ b/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
diff --git a/packages/server/graphql/public/typeDefs/Subscriptions.graphql b/packages/server/graphql/public/typeDefs/Subscriptions.graphql
index f3391104caf..3f2e1a48de5 100644
--- a/packages/server/graphql/public/typeDefs/Subscriptions.graphql
+++ b/packages/server/graphql/public/typeDefs/Subscriptions.graphql
@@ -54,6 +54,7 @@ type MeetingSubscriptionPayload {
SetPokerSpectateSuccess: SetPokerSpectateSuccess
SetTaskEstimateSuccess: SetTaskEstimateSuccess
UpsertTeamPromptResponseSuccess: UpsertTeamPromptResponseSuccess
+ UpdateMeetingTemplateSuccess: UpdateMeetingTemplateSuccess
}
type NotificationSubscriptionPayload {
diff --git a/packages/server/graphql/public/typeDefs/updateMeetingTemplate.graphql b/packages/server/graphql/public/typeDefs/updateMeetingTemplate.graphql
new file mode 100644
index 00000000000..1f378e42dbe
--- /dev/null
+++ b/packages/server/graphql/public/typeDefs/updateMeetingTemplate.graphql
@@ -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!
+}
diff --git a/packages/server/graphql/public/types/UpdateMeetingTemplateSuccess.ts b/packages/server/graphql/public/types/UpdateMeetingTemplateSuccess.ts
new file mode 100644
index 00000000000..33a52658893
--- /dev/null
+++ b/packages/server/graphql/public/types/UpdateMeetingTemplateSuccess.ts
@@ -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