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: update org feature flags backend #10446

Merged
merged 24 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c2612ec
feat: add use ai to org
nickoferrall Oct 31, 2024
ed8e516
implement feature flag resolver and dataloaders to get all flags
nickoferrall Nov 1, 2024
c734ba1
implement toggle feature flag
nickoferrall Nov 1, 2024
5fff7ec
update dataloader
nickoferrall Nov 4, 2024
e943ab4
add checks in toggle feature flag mutation
nickoferrall Nov 4, 2024
ebbbb67
feature flag subscription working
nickoferrall Nov 4, 2024
412f30d
add lookup
nickoferrall Nov 5, 2024
dde53d2
Merge branch 'feat/add-use-ai' into feat/org-feature-flags-dynamic
nickoferrall Nov 5, 2024
f447d71
merge useAi
nickoferrall Nov 5, 2024
52d0507
clean up
nickoferrall Nov 5, 2024
74f684f
info icon centered
nickoferrall Nov 5, 2024
f0003d9
fix codegen error
nickoferrall Nov 5, 2024
ad7dc0e
Revert changes to yarn.lock
nickoferrall Nov 5, 2024
bb8f42e
fix ToggleFeatureFlagSuccess ts error
nickoferrall Nov 5, 2024
c2e559d
update enabled type
nickoferrall Nov 6, 2024
7e81e1c
Merge branch 'feat/feature-flags-ui' into feat/org-feature-flags-dynamic
nickoferrall Nov 6, 2024
85f77a6
only org admin can see ui
nickoferrall Nov 6, 2024
fd693f6
update err message
nickoferrall Nov 6, 2024
93fa3cf
update props
nickoferrall Nov 6, 2024
cf6af28
seperate owned feature flag and feature flag
nickoferrall Nov 15, 2024
947178a
state validScope type
nickoferrall Nov 15, 2024
0620a0d
chore: remove pg.d.ts from version control
nickoferrall Nov 18, 2024
0d2fdae
update featureFlag type in index.d.ts
nickoferrall Nov 18, 2024
f1dd5c4
feat: implement toggle ai features mutation (#10457)
nickoferrall Nov 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 39 additions & 37 deletions codegen.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface Props {
}

const isServer = typeof window === 'undefined'
const hasAI = isServer
const hasAiApiKey = isServer
? !!process.env.OPEN_AI_API_KEY
: !!window.__ACTION__ && !!window.__ACTION__.hasOpenAI

Expand All @@ -25,7 +25,7 @@ const WholeMeetingSummary = (props: Props) => {
summary
organization {
hasStandupAISummaryFlag: featureFlag(featureName: "standupAISummary")
hasNoAISummaryFlag: featureFlag(featureName: "noAISummary")
useAI
}
... on RetrospectiveMeeting {
reflectionGroups(sortBy: voteCount) {
Expand All @@ -47,15 +47,16 @@ const WholeMeetingSummary = (props: Props) => {
const {summary: wholeMeetingSummary, reflectionGroups, organization} = meeting
const reflections = reflectionGroups?.flatMap((group) => group.reflections) // reflectionCount hasn't been calculated yet so check reflections length
const hasMoreThanOneReflection = reflections?.length && reflections.length > 1
if (!hasMoreThanOneReflection || organization.hasNoAISummaryFlag || !hasAI) return null
if (!hasMoreThanOneReflection || !organization.useAI || !hasAiApiKey) return null
if (!wholeMeetingSummary) return <WholeMeetingSummaryLoading />
return <WholeMeetingSummaryResult meetingRef={meeting} />
} else if (meeting.__typename === 'TeamPromptMeeting') {
const {summary: wholeMeetingSummary, responses, organization} = meeting
const {hasStandupAISummaryFlag, useAI} = organization
if (
!organization.hasStandupAISummaryFlag ||
organization.hasNoAISummaryFlag ||
!hasAI ||
!hasStandupAISummaryFlag ||
!useAI ||
!hasAiApiKey ||
!responses ||
responses.length === 0
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const NewCheckInQuestion = (props: Props) => {
}
team {
organization {
hasNoAISummaryFlag: featureFlag(featureName: "noAISummary")
useAI
}
}
}
Expand All @@ -101,7 +101,7 @@ const NewCheckInQuestion = (props: Props) => {
localPhase,
facilitatorUserId,
team: {
organization: {hasNoAISummaryFlag}
organization: {useAI}
}
} = meeting
const {checkInQuestion} = localPhase
Expand Down Expand Up @@ -226,7 +226,7 @@ const NewCheckInQuestion = (props: Props) => {
}
})
}
const showAiIcebreaker = !hasNoAISummaryFlag && isFacilitating && window.__ACTION__.hasOpenAI
const showAiIcebreaker = useAI && isFacilitating && window.__ACTION__.hasOpenAI

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const OrgDetails = (props: Props) => {
fragment OrgDetails_organization on Organization {
...OrgBillingDangerZone_organization
...EditableOrgName_organization
...OrgFeatureFlags_organization
...OrgFeatures_organization
orgId: id
isBillingLeader
createdAt
Expand Down Expand Up @@ -69,8 +71,8 @@ const OrgDetails = (props: Props) => {
</div>
</div>

<OrgFeatures />
<OrgFeatureFlags />
<OrgFeatures organizationRef={organization} />
<OrgFeatureFlags organizationRef={organization} />
<OrgBillingDangerZone organization={organization} isWide />
</Suspense>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import styled from '@emotion/styled'
import {Info as InfoIcon} from '@mui/icons-material'
import React, {useState} from 'react'
import graphql from 'babel-plugin-relay/macro'
import React from 'react'
import {useFragment} from 'react-relay'
import {OrgFeatureFlags_organization$key} from '../../../../__generated__/OrgFeatureFlags_organization.graphql'
import Panel from '../../../../components/Panel/Panel'
import Toggle from '../../../../components/Toggle/Toggle'
import useAtmosphere from '../../../../hooks/useAtmosphere'
import useMutationProps from '../../../../hooks/useMutationProps'
import ToggleFeatureFlagMutation from '../../../../mutations/ToggleFeatureFlagMutation'
import {PALETTE} from '../../../../styles/paletteV3'
import {ElementWidth, Layout} from '../../../../types/constEnums'
import {Tooltip} from '../../../../ui/Tooltip/Tooltip'
Expand All @@ -28,63 +34,73 @@ const FeatureRow = styled('div')({
const FeatureNameGroup = styled('div')({
display: 'flex',
alignItems: 'center',
gap: 4
gap: 4,
'& svg': {
display: 'block'
}
})

const features = [
{
id: 'suggestGroups',
name: 'Suggest Groups',
tooltip:
'Get AI-powered suggestions for creating new groups based on team activity and collaboration patterns'
},
{
id: 'publicTeams',
name: 'Public Teams',
tooltip: 'Allow teams to be discoverable by anyone in your organization'
},
{
id: 'standupAISummary',
name: 'Stand-Up AI Summary',
tooltip: 'Automatically generate summaries of your team standups using AI'
},
{
id: 'relatedDiscussions',
name: 'Related Discussions',
tooltip: 'See AI-suggested related discussions and threads across your organization'
}
]
// TODO: create a migration that updates featureName to be a readable string
// then update the references throughout the app and remove this
const FEATURE_NAME_LOOKUP: Record<string, string> = {
insights: 'Team Insights',
publicTeams: 'Public Teams',
relatedDiscussions: 'Related Discussions',
standupAISummary: 'Standup AI Summary',
suggestGroups: 'AI Reflection Group Suggestions'
}

const OrgFeatureFlags = () => {
const [featureStates, setFeatureStates] = useState<Record<string, boolean>>({
suggestGroups: false,
publicTeams: false,
standupAISummary: false,
relatedDiscussions: false
})
interface Props {
organizationRef: OrgFeatureFlags_organization$key
}

const OrgFeatureFlags = (props: Props) => {
const {organizationRef} = props
const atmosphere = useAtmosphere()
const {onError, onCompleted} = useMutationProps()
const organization = useFragment(
graphql`
fragment OrgFeatureFlags_organization on Organization {
id
isOrgAdmin
orgFeatureFlags {
featureName
description
enabled
}
}
`,
organizationRef
)
const {isOrgAdmin} = organization

const handleToggle = (featureId: string) => {
setFeatureStates((prev) => ({
...prev,
[featureId]: !prev[featureId]
}))
const handleToggle = async (featureName: string) => {
const variables = {
featureName,
orgId: organization.id
}
ToggleFeatureFlagMutation(atmosphere, variables, {
onError,
onCompleted
})
}

if (!isOrgAdmin) return null
return (
<StyledPanel isWide label='Organization Feature Flags'>
<PanelRow>
{features.map((feature) => (
<FeatureRow key={feature.id}>
{organization.orgFeatureFlags.map((feature) => (
<FeatureRow key={feature.featureName}>
<FeatureNameGroup>
<span>{feature.name}</span>
<span>{FEATURE_NAME_LOOKUP[feature.featureName] || feature.featureName}</span>
<Tooltip>
<TooltipTrigger className='bg-transparent hover:cursor-pointer'>
<InfoIcon className='h-4 w-4 text-slate-600' />
</TooltipTrigger>
<TooltipContent>{feature.tooltip}</TooltipContent>
<TooltipContent>{feature.description}</TooltipContent>
</Tooltip>
</FeatureNameGroup>
<Toggle active={featureStates[feature.id]!} onClick={() => handleToggle(feature.id)} />
<Toggle active={!!feature.enabled} onClick={() => handleToggle(feature.featureName)} />
</FeatureRow>
))}
</PanelRow>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import styled from '@emotion/styled'
import {Info as InfoIcon} from '@mui/icons-material'
import React, {useState} from 'react'
import graphql from 'babel-plugin-relay/macro'
import React from 'react'
import {useFragment} from 'react-relay'
import {OrgFeatures_organization$key} from '../../../../__generated__/OrgFeatures_organization.graphql'
import Panel from '../../../../components/Panel/Panel'
import Toggle from '../../../../components/Toggle/Toggle'
import useAtmosphere from '../../../../hooks/useAtmosphere'
import useMutationProps from '../../../../hooks/useMutationProps'
import ToggleAIFeaturesMutation from '../../../../mutations/ToggleAIFeaturesMutation'
import {PALETTE} from '../../../../styles/paletteV3'
import {ElementWidth, Layout} from '../../../../types/constEnums'
import {Tooltip} from '../../../../ui/Tooltip/Tooltip'
Expand All @@ -28,26 +34,55 @@ const FeatureRow = styled('div')({
const FeatureNameGroup = styled('div')({
display: 'flex',
alignItems: 'center',
gap: 4
gap: 4,
'& svg': {
display: 'block'
}
})

const OrgFeatures = () => {
const [showAIFeatures, setShowAIFeatures] = useState(false)
interface Props {
organizationRef: OrgFeatures_organization$key
}

const OrgFeatures = (props: Props) => {
const {organizationRef} = props
const atmosphere = useAtmosphere()
const {onError, onCompleted} = useMutationProps()
const organization = useFragment(
graphql`
fragment OrgFeatures_organization on Organization {
id
isOrgAdmin
useAI
}
`,
organizationRef
)
const {id: orgId, isOrgAdmin, useAI} = organization

const handleToggle = () => {
const variables = {orgId}
ToggleAIFeaturesMutation(atmosphere, variables, {
onError,
onCompleted
})
}

if (!isOrgAdmin) return null
return (
<StyledPanel isWide label='AI Features'>
<PanelRow>
<FeatureRow>
<FeatureNameGroup>
<span>Show AI Features</span>
<span>Enable AI Features</span>
<Tooltip>
<TooltipTrigger className='bg-transparent hover:cursor-pointer'>
<InfoIcon className='h-4 w-4 text-slate-600' />
</TooltipTrigger>
<TooltipContent>Enable AI-powered features across your organization</TooltipContent>
</Tooltip>
</FeatureNameGroup>
<Toggle active={showAIFeatures} onClick={() => setShowAIFeatures(!showAIFeatures)} />
<Toggle active={useAI} onClick={handleToggle} />
</FeatureRow>
</PanelRow>
</StyledPanel>
Expand Down
4 changes: 2 additions & 2 deletions packages/client/mutations/EndRetrospectiveMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ graphql`
groupTitle
}
organization {
hasNoAISummaryFlag: featureFlag(featureName: "noAISummary")
useAI
}
reflectionGroups(sortBy: voteCount) {
reflections {
Expand Down Expand Up @@ -125,7 +125,7 @@ export const endRetrospectiveTeamOnNext: OnNextHandler<
const reflections = reflectionGroups.flatMap((group) => group.reflections) // reflectionCount hasn't been calculated yet so check reflections length
const hasMoreThanOneReflection = reflections.length > 1
const hasOpenAISummary =
hasMoreThanOneReflection && !organization.hasNoAISummaryFlag && window.__ACTION__.hasOpenAI
hasMoreThanOneReflection && organization.useAI && window.__ACTION__.hasOpenAI
const hasTeamHealth = phases.some((phase) => phase.phaseType === 'TEAM_HEALTH')
const pathname = `/new-summary/${meetingId}`
const search = new URLSearchParams()
Expand Down
41 changes: 41 additions & 0 deletions packages/client/mutations/ToggleAIFeaturesMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import graphql from 'babel-plugin-relay/macro'
import {commitMutation} from 'react-relay'
import {ToggleAIFeaturesMutation as TToggleAIFeaturesMutation} from '../__generated__/ToggleAIFeaturesMutation.graphql'
import {StandardMutation} from '../types/relayMutations'

graphql`
fragment ToggleAIFeaturesMutation_organization on ToggleAIFeaturesSuccess {
organization {
id
useAI
}
}
`

const mutation = graphql`
mutation ToggleAIFeaturesMutation($orgId: ID!) {
toggleAIFeatures(orgId: $orgId) {
... on ErrorPayload {
error {
message
}
}
...ToggleAIFeaturesMutation_organization @relay(mask: false)
}
}
`

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

export default ToggleAIFeaturesMutation
41 changes: 41 additions & 0 deletions packages/client/mutations/ToggleFeatureFlagMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import graphql from 'babel-plugin-relay/macro'
import {commitMutation} from 'react-relay'
import {ToggleFeatureFlagMutation as TToggleFeatureFlagMutation} from '../__generated__/ToggleFeatureFlagMutation.graphql'
import {StandardMutation} from '../types/relayMutations'

graphql`
fragment ToggleFeatureFlagMutation_notification on ToggleFeatureFlagSuccess {
featureFlag {
featureName
enabled
}
}
`

const mutation = graphql`
mutation ToggleFeatureFlagMutation($featureName: String!, $orgId: ID, $teamId: ID, $userId: ID) {
toggleFeatureFlag(featureName: $featureName, orgId: $orgId, teamId: $teamId, userId: $userId) {
... on ErrorPayload {
error {
message
}
}
...ToggleFeatureFlagMutation_notification @relay(mask: false)
}
}
`

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

export default ToggleFeatureFlagMutation
Loading
Loading