Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow 2 custom templates for every user #9518

Merged
merged 15 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import NewMeetingSettingsToggleCheckIn from '../NewMeetingSettingsToggleCheckIn'
import StyledError from '../StyledError'
import FlatPrimaryButton from '../FlatPrimaryButton'
import NewMeetingActionsCurrentMeetings from '../NewMeetingActionsCurrentMeetings'
import RaisedButton from '../RaisedButton'
import NewMeetingTeamPicker from '../NewMeetingTeamPicker'
import {AdhocTeamMultiSelect, Option} from '../AdhocTeamMultiSelect/AdhocTeamMultiSelect'
import {Select} from '../../ui/Select/Select'
Expand Down Expand Up @@ -78,7 +77,6 @@ const ActivityDetailsSidebar = (props: Props) => {
fragment ActivityDetailsSidebar_viewer on User {
featureFlags {
adHocTeams
noTemplateLimit
}
...AdhocTeamMultiSelect_viewer
organizations {
Expand Down Expand Up @@ -295,14 +293,6 @@ const ActivityDetailsSidebar = (props: Props) => {
</div>
)

const handleUpgrade = () => {
SendClientSideEvent(atmosphere, 'Upgrade CTA Clicked', {
upgradeCTALocation: 'publicTemplate',
meetingType: type
})
history.push(`/me/organizations/${selectedTeam.orgId}/billing`)
}

const meetingNamePlaceholder =
type === 'retrospective'
? 'Retro'
Expand Down Expand Up @@ -396,42 +386,22 @@ const ActivityDetailsSidebar = (props: Props) => {
/>
)}

{selectedTeam.tier === 'starter' &&
!viewer.featureFlags.noTemplateLimit &&
!selectedTemplate.isFree ? (
<div className='flex grow flex-col'>
<div className='my-auto text-center'>
Upgrade to the <b>Team Plan</b> to create custom activities unlocking your
team’s ideal workflow.
</div>
<RaisedButton
palette='pink'
className='h-12 w-full text-lg font-semibold text-white focus:outline-none focus:ring-2 focus:ring-offset-2'
onClick={handleUpgrade}
>
Upgrade to Team Plan
</RaisedButton>
</div>
) : (
{type === 'retrospective' && (
<>
{type === 'retrospective' && (
<>
<NewMeetingSettingsToggleCheckIn settingsRef={selectedTeam.retroSettings} />
<NewMeetingSettingsToggleTeamHealth
settingsRef={selectedTeam.retroSettings}
teamRef={selectedTeam}
/>
<NewMeetingSettingsToggleAnonymity settingsRef={selectedTeam.retroSettings} />
</>
)}
{type === 'poker' && (
<NewMeetingSettingsToggleCheckIn settingsRef={selectedTeam.pokerSettings} />
)}
{type === 'action' && (
<NewMeetingSettingsToggleCheckIn settingsRef={selectedTeam.actionSettings} />
)}
<NewMeetingSettingsToggleCheckIn settingsRef={selectedTeam.retroSettings} />
<NewMeetingSettingsToggleTeamHealth
settingsRef={selectedTeam.retroSettings}
teamRef={selectedTeam}
/>
<NewMeetingSettingsToggleAnonymity settingsRef={selectedTeam.retroSettings} />
</>
)}
{type === 'poker' && (
<NewMeetingSettingsToggleCheckIn settingsRef={selectedTeam.pokerSettings} />
)}
{type === 'action' && (
<NewMeetingSettingsToggleCheckIn settingsRef={selectedTeam.actionSettings} />
)}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,8 @@ const SUPPORTED_CUSTOM_ACTIVITIES: SupportedActivity[] = [
const query = graphql`
query CreateNewActivityQuery {
viewer {
featureFlags {
noTemplateLimit
}
freeCustomRetroTemplatesRemaining
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created two flags, freeCustomRetroTemplatesRemaining and freeCustomPokerTemplatesRemaining, thinking of our engineering principle that we should repeat the code twice and, on the third time, refactor it into something reusable.

freeCustomPokerTemplatesRemaining
preferredTeamId
teams {
id
Expand Down Expand Up @@ -135,12 +134,21 @@ export const CreateNewActivity = (props: Props) => {
return selectedActivity
})
const {viewer} = data
const {teams, preferredTeamId, featureFlags} = viewer
const {
teams,
preferredTeamId,
freeCustomRetroTemplatesRemaining,
freeCustomPokerTemplatesRemaining
} = viewer
const [selectedTeam, setSelectedTeam] = useState(
teams.find((team) => team.id === preferredTeamId) ?? sortByTier(teams)[0]!
)
const {submitting, error, submitMutation, onError, onCompleted} = useMutationProps()
const history = useHistory()
const freeCustomTemplatesRemaining =
selectedActivity.type === 'retrospective'
? freeCustomRetroTemplatesRemaining
: freeCustomPokerTemplatesRemaining

const handleCreateRetroTemplate = () => {
if (submitting) {
Expand Down Expand Up @@ -284,16 +292,15 @@ export const CreateNewActivity = (props: Props) => {
</div>
{error && <div className='px-4 text-tomato-500'>{error.message}</div>}
<div className='mt-auto flex w-full bg-slate-200 p-2 shadow-card-1'>
{selectedTeam.tier === 'starter' && !featureFlags.noTemplateLimit ? (
<div className='mx-auto flex h-12 items-center gap-24'>
<div className='w-96'>
Upgrade to the <b>Team Plan</b> to create custom activities unlocking your team’s
ideal workflow.
</div>
{selectedTeam.tier === 'starter' && freeCustomTemplatesRemaining === 0 ? (
<div className='flex w-full items-center justify-center gap-4'>
<span className='pr-4 text-center'>
Upgrade to the <b>Team Plan</b> to create more custom activities
</span>

<RaisedButton
palette='pink'
className='mx-auto h-12 text-lg font-semibold text-white focus:outline-none focus:ring-2 focus:ring-offset-2'
className='h-12 px-4 text-lg font-semibold text-white focus:outline-none focus:ring-2 focus:ring-offset-2'
onClick={handleUpgrade}
>
Upgrade to Team Plan
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,17 @@ const AddNewPokerTemplate = (props: Props) => {
id
user {
id
featureFlags {
noTemplateLimit
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we've removed the template limit, we should remove this flag altogether. I've created an issue: #9523

}
freeCustomPokerTemplatesRemaining
}
}
}
`,
teamRef
)
const {id: teamId, tier, viewerTeamMember} = team
const {user} = viewerTeamMember || {}
const {freeCustomPokerTemplatesRemaining} = user || {}

const {onError, onCompleted, submitMutation, submitting, error} = useMutationProps()
const errorTimerId = useRef<undefined | number>()
useEffect(() => {
Expand All @@ -71,7 +72,8 @@ const AddNewPokerTemplate = (props: Props) => {
}
}, [])
const canEditTemplates =
tier !== 'starter' || viewerTeamMember?.user?.featureFlags?.noTemplateLimit
tier !== 'starter' ||
(freeCustomPokerTemplatesRemaining && freeCustomPokerTemplatesRemaining > 0)
const addNewTemplate = () => {
if (submitting) return
if (!canEditTemplates) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,16 @@ const AddNewReflectTemplate = (props: Props) => {
id
user {
id
featureFlags {
noTemplateLimit
}
freeCustomRetroTemplatesRemaining
}
}
}
`,
teamRef
)
const {id: teamId, tier, viewerTeamMember} = team
const {user} = viewerTeamMember || {}
const {freeCustomRetroTemplatesRemaining} = user || {}
const {onError, onCompleted, submitMutation, submitting, error} = useMutationProps()
const errorTimerId = useRef<undefined | number>()
useEffect(() => {
Expand All @@ -71,7 +71,8 @@ const AddNewReflectTemplate = (props: Props) => {
}
}, [])
const canEditTemplates =
tier !== 'starter' || viewerTeamMember?.user?.featureFlags?.noTemplateLimit
tier !== 'starter' ||
(freeCustomRetroTemplatesRemaining && freeCustomRetroTemplatesRemaining > 0)
const addNewTemplate = () => {
if (submitting) return
if (!canEditTemplates) {
Expand Down
3 changes: 3 additions & 0 deletions packages/client/mutations/AddPokerTemplateMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import handleAddMeetingTemplate from './handlers/handleAddMeetingTemplate'

graphql`
fragment AddPokerTemplateMutation_team on AddPokerTemplatePayload {
user {
freeCustomPokerTemplatesRemaining
}
pokerTemplate {
...TemplateSharing_template
...PokerTemplateDetailsTemplate
Expand Down
3 changes: 3 additions & 0 deletions packages/client/mutations/AddReflectTemplateMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import handleAddMeetingTemplate from './handlers/handleAddMeetingTemplate'

graphql`
fragment AddReflectTemplateMutation_team on AddReflectTemplatePayload {
user {
freeCustomRetroTemplatesRemaining
}
reflectTemplate {
...TemplateSharing_template
...ReflectTemplateDetailsTemplate
Expand Down
15 changes: 11 additions & 4 deletions packages/server/graphql/mutations/addPokerTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import AddPokerTemplatePayload from '../types/AddPokerTemplatePayload'
import getTemplateIllustrationUrl from './helpers/getTemplateIllustrationUrl'
import {analytics} from '../../utils/analytics/analytics'
import {getFeatureTier} from '../types/helpers/getFeatureTier'
import decrementFreeTemplatesRemaining from '../../postgres/queries/decrementFreeTemplatesRemaining'

const addPokerTemplate = {
description: 'Add a new poker template with a default dimension created',
Expand Down Expand Up @@ -51,9 +52,11 @@ const addPokerTemplate = {
}
if (
getFeatureTier(viewerTeam) === 'starter' &&
!viewer.featureFlags.includes('noTemplateLimit')
viewer.freeCustomPokerTemplatesRemaining === 0
) {
return standardError(new Error('Creating templates is a premium feature'), {userId: viewerId})
return standardError(new Error('You have reached the limit of free custom templates.'), {
userId: viewerId
})
}
let data
if (parentTemplateId) {
Expand Down Expand Up @@ -102,8 +105,10 @@ const addPokerTemplate = {

await Promise.all([
r.table('TemplateDimension').insert(newTemplateDimensions).run(),
insertMeetingTemplate(newTemplate)
insertMeetingTemplate(newTemplate),
decrementFreeTemplatesRemaining(viewerId, 'poker')
])
viewer.freeCustomPokerTemplatesRemaining = viewer.freeCustomPokerTemplatesRemaining - 1
analytics.templateMetrics(viewer, newTemplate, 'Template Cloned')
data = {templateId: newTemplate.id}
} else {
Expand All @@ -129,8 +134,10 @@ const addPokerTemplate = {

await Promise.all([
r.table('TemplateDimension').insert(newDimension).run(),
insertMeetingTemplate(newTemplate)
insertMeetingTemplate(newTemplate),
decrementFreeTemplatesRemaining(viewerId, 'poker')
])
viewer.freeCustomPokerTemplatesRemaining = viewer.freeCustomPokerTemplatesRemaining - 1
analytics.templateMetrics(viewer, newTemplate, 'Template Created')
data = {templateId}
}
Expand Down
15 changes: 11 additions & 4 deletions packages/server/graphql/mutations/addReflectTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import AddReflectTemplatePayload from '../types/AddReflectTemplatePayload'
import makeRetroTemplates from './helpers/makeRetroTemplates'
import {analytics} from '../../utils/analytics/analytics'
import {getFeatureTier} from '../types/helpers/getFeatureTier'
import decrementFreeCustomTemplatesRemaining from '../../postgres/queries/decrementFreeTemplatesRemaining'

const addReflectTemplate = {
description: 'Add a new template full of prompts',
Expand Down Expand Up @@ -52,9 +53,11 @@ const addReflectTemplate = {
}
if (
getFeatureTier(viewerTeam) === 'starter' &&
!viewer.featureFlags.includes('noTemplateLimit')
viewer.freeCustomRetroTemplatesRemaining === 0
) {
return standardError(new Error('Creating templates is a premium feature'), {userId: viewerId})
return standardError(new Error('You have reached the limit of free custom templates.'), {
userId: viewerId
})
}
let data
if (parentTemplateId) {
Expand Down Expand Up @@ -102,8 +105,10 @@ const addReflectTemplate = {

await Promise.all([
r.table('ReflectPrompt').insert(newTemplatePrompts).run(),
insertMeetingTemplate(newTemplate)
insertMeetingTemplate(newTemplate),
decrementFreeCustomTemplatesRemaining(viewerId, 'retro')
])
viewer.freeCustomRetroTemplatesRemaining = viewer.freeCustomRetroTemplatesRemaining - 1
analytics.templateMetrics(viewer, newTemplate, 'Template Cloned')
data = {templateId: newTemplate.id}
} else {
Expand All @@ -129,8 +134,10 @@ const addReflectTemplate = {
const {id: templateId} = newTemplate
await Promise.all([
r.table('ReflectPrompt').insert(newTemplatePrompts).run(),
insertMeetingTemplate(newTemplate)
insertMeetingTemplate(newTemplate),
decrementFreeCustomTemplatesRemaining(viewerId, 'retro')
])
viewer.freeCustomRetroTemplatesRemaining = viewer.freeCustomRetroTemplatesRemaining - 1
analytics.templateMetrics(viewer, newTemplate, 'Template Created')
data = {templateId}
}
Expand Down
8 changes: 8 additions & 0 deletions packages/server/graphql/public/typeDefs/User.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -458,4 +458,12 @@ type User {
"""
domain: String!
): ParseSAMLMetadataPayload!
"""
The number of free custom retro templates remaining
"""
freeCustomRetroTemplatesRemaining: Int!
"""
The number of free custom poker templates remaining
"""
freeCustomPokerTemplatesRemaining: Int!
}
10 changes: 10 additions & 0 deletions packages/server/graphql/types/AddPokerTemplatePayload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {GraphQLObjectType} from 'graphql'
import {GQLContext} from '../graphql'
import PokerTemplate from './PokerTemplate'
import StandardMutationError from './StandardMutationError'
import {getUserId} from '../../utils/authorization'
import User from './User'

const AddPokerTemplatePayload = new GraphQLObjectType<any, GQLContext>({
name: 'AddPokerTemplatePayload',
Expand All @@ -15,6 +17,14 @@ const AddPokerTemplatePayload = new GraphQLObjectType<any, GQLContext>({
if (!templateId) return null
return dataLoader.get('meetingTemplates').load(templateId)
}
},
user: {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll refactor this to the SDL pattern in a follow-up PR. This PR already touches a lot of files, so I didn't want to bloat it further

type: User,
resolve: (_, _args: unknown, {dataLoader, authToken}) => {
const userId = getUserId(authToken)
if (!userId) return null
return dataLoader.get('users').load(userId)
}
}
})
})
Expand Down
10 changes: 10 additions & 0 deletions packages/server/graphql/types/AddReflectTemplatePayload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {GraphQLObjectType} from 'graphql'
import {GQLContext} from '../graphql'
import ReflectTemplate from './ReflectTemplate'
import StandardMutationError from './StandardMutationError'
import User from './User'
import {getUserId} from '../../utils/authorization'

const AddReflectTemplatePayload = new GraphQLObjectType<any, GQLContext>({
name: 'AddReflectTemplatePayload',
Expand All @@ -15,6 +17,14 @@ const AddReflectTemplatePayload = new GraphQLObjectType<any, GQLContext>({
if (!templateId) return null
return dataLoader.get('meetingTemplates').load(templateId)
}
},
user: {
type: User,
resolve: (_, _args: unknown, {dataLoader, authToken}) => {
const userId = getUserId(authToken)
if (!userId) return null
return dataLoader.get('users').load(userId)
}
}
})
})
Expand Down