Skip to content

Commit

Permalink
feat: release standups ai to all users (#10724)
Browse files Browse the repository at this point in the history
  • Loading branch information
nickoferrall authored Feb 4, 2025
1 parent d3654e6 commit 260daf3
Show file tree
Hide file tree
Showing 14 changed files with 59 additions and 74 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import graphql from 'babel-plugin-relay/macro'
import {WholeMeetingSummary_meeting$key} from 'parabol-client/__generated__/WholeMeetingSummary_meeting.graphql'
import {
WholeMeetingSummary_meeting$data,
WholeMeetingSummary_meeting$key
} from 'parabol-client/__generated__/WholeMeetingSummary_meeting.graphql'
import {useFragment} from 'react-relay'
import WholeMeetingSummaryLoading from './WholeMeetingSummaryLoading'
import WholeMeetingSummaryResult from './WholeMeetingSummaryResult'
Expand All @@ -13,6 +16,17 @@ const hasAiApiKey = isServer
? !!process.env.OPEN_AI_API_KEY
: !!window.__ACTION__ && !!window.__ACTION__.hasOpenAI

const hasContent = (meeting: WholeMeetingSummary_meeting$data): boolean => {
if (meeting.__typename === 'RetrospectiveMeeting') {
const reflections = meeting.reflectionGroups?.flatMap((group) => group.reflections)
return Boolean(reflections?.length && reflections.length > 1)
}
if (meeting.__typename === 'TeamPromptMeeting') {
return Boolean(meeting.responses?.length)
}
return false
}

const WholeMeetingSummary = (props: Props) => {
const {meetingRef} = props
const meeting = useFragment(
Expand All @@ -23,7 +37,6 @@ const WholeMeetingSummary = (props: Props) => {
id
summary
organization {
hasStandupAISummaryFlag: featureFlag(featureName: "standupAISummary")
useAI
}
... on RetrospectiveMeeting {
Expand All @@ -35,6 +48,7 @@ const WholeMeetingSummary = (props: Props) => {
}
}
... on TeamPromptMeeting {
isLoadingSummary
responses {
id
}
Expand All @@ -43,29 +57,15 @@ const WholeMeetingSummary = (props: Props) => {
`,
meetingRef
)
if (meeting.__typename === 'RetrospectiveMeeting') {
const {reflectionGroups, organization, isLoadingSummary} = 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.useAI || !hasAiApiKey) return null
if (isLoadingSummary) return <WholeMeetingSummaryLoading />
return <WholeMeetingSummaryResult meetingRef={meeting} />
} else if (meeting.__typename === 'TeamPromptMeeting') {
const {summary: wholeMeetingSummary, responses, organization} = meeting
const {hasStandupAISummaryFlag, useAI} = organization
if (
!hasStandupAISummaryFlag ||
!useAI ||
!hasAiApiKey ||
!responses ||
responses.length === 0
) {
return null
}
if (!wholeMeetingSummary) return <WholeMeetingSummaryLoading />
return <WholeMeetingSummaryResult meetingRef={meeting} />
}
return null

const {organization} = meeting
const {useAI} = organization

if (!useAI || !hasAiApiKey || !hasContent(meeting)) return null

if (meeting.isLoadingSummary) return <WholeMeetingSummaryLoading />

return <WholeMeetingSummaryResult meetingRef={meeting} />
}

export default WholeMeetingSummary
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,10 @@ const WholeMeetingSummary = () => {
borderBottom: `1px solid ${PALETTE.SLATE_400}`
}}
>
<tr>
<td style={explainerStyle}>
{'Hold tight! Our AI 🤖 is generating your meeting summary'}
<Ellipsis />
</td>
</tr>
<div style={explainerStyle}>
{'Hold tight! Our AI 🤖 is generating your meeting summary'}
<Ellipsis />
</div>
</td>
</tr>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,22 +81,13 @@ const WholeMeetingSummaryResult = ({meetingRef}: Props) => {
<>
<tr>
<td align='center' style={{paddingTop: 20}}>
<tr>
<td style={explainerStyle}>{explainerText}</td>
</tr>
<tr>
<td align='center' style={topicTitleStyle}>
{'🤖 Meeting Summary'}
</td>
</tr>
<tr>
<td
align='center'
style={textStyle}
className='link-style'
dangerouslySetInnerHTML={{__html: sanitizedSummary}}
/>
</tr>
<div style={explainerStyle}>{explainerText}</div>
<div style={topicTitleStyle}>{'🤖 Meeting Summary'}</div>
<div
style={textStyle}
className='link-style'
dangerouslySetInnerHTML={{__html: sanitizedSummary}}
/>
</td>
</tr>
<EmailBorderBottom />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ const FeatureNameGroup = styled('div')({
const FEATURE_NAME_LOOKUP: Record<string, string> = {
insights: 'Team Insights',
publicTeams: 'Public Teams',
relatedDiscussions: 'Related Discussions',
standupAISummary: 'Standup AI Summary'
relatedDiscussions: 'Related Discussions'
}

interface Props {
Expand Down
12 changes: 1 addition & 11 deletions packages/server/graphql/mutations/helpers/canAccessAI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,11 @@ import {Team} from '../../../postgres/types'
import {DataLoaderWorker} from '../../graphql'
import {getFeatureTier} from '../../types/helpers/getFeatureTier'

const canAccessAI = async (
team: Team,
meetingType: 'standup' | 'retrospective',
dataLoader: DataLoaderWorker
) => {
const canAccessAI = async (team: Team, dataLoader: DataLoaderWorker) => {
const {qualAIMeetingsCount, orgId} = team
const org = await dataLoader.get('organizations').loadNonNull(orgId)

if (!org.useAI) return false
if (meetingType === 'standup') {
const hasStandupFlag = await dataLoader
.get('featureFlagByOwnerId')
.load({ownerId: orgId, featureName: 'standupAISummary'})
return hasStandupFlag
}

if (getFeatureTier(team) !== 'starter') return true
return qualAIMeetingsCount < Threshold.MAX_QUAL_AI_MEETINGS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const generateDiscussionPrompt = async (
dataLoader.get('users').loadNonNull(facilitatorUserId),
dataLoader.get('teams').loadNonNull(teamId)
])
const isAIAvailable = await canAccessAI(team, 'retrospective', dataLoader)
const isAIAvailable = await canAccessAI(team, dataLoader)
if (!isAIAvailable) return
const [reflections, reflectionGroups] = await Promise.all([
dataLoader.get('retroReflectionsByMeetingId').load(meetingId),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const generateGroups = async (
if (reflections.length === 0) return
const {meetingId} = reflections[0]!
const team = await dataLoader.get('teams').loadNonNull(teamId)
if (!(await canAccessAI(team, 'retrospective', dataLoader))) return
if (!(await canAccessAI(team, dataLoader))) return
const groupReflectionsInput = reflections.map((reflection) => reflection.plaintextContent)
const manager = new OpenAIServerManager()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const generateRetroSummary = async (
const {teamId} = meeting

const team = await dataLoader.get('teams').loadNonNull(teamId)
const isAISummaryAccessible = await canAccessAI(team, 'retrospective', dataLoader)
const isAISummaryAccessible = await canAccessAI(team, dataLoader)
if (!isAISummaryAccessible) {
return setSummaryToNull(meetingId)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const generateStandupMeetingSummary = async (
dataLoader: DataLoaderWorker
) => {
const team = await dataLoader.get('teams').loadNonNull(meeting.teamId)
const isAIAvailable = await canAccessAI(team, 'standup', dataLoader)
if (!isAIAvailable) return
const isAIAvailable = await canAccessAI(team, dataLoader)
if (!isAIAvailable) return null

const responses = await getTeamPromptResponsesByMeetingId(meeting.id)

Expand All @@ -23,11 +23,11 @@ const generateStandupMeetingSummary = async (
user: users[idx]?.preferredName ?? 'Anonymous'
}))

if (contentWithUsers.length === 0) return
if (contentWithUsers.length === 0) return null

const manager = new OpenAIServerManager()
const summary = await manager.getStandupSummary(contentWithUsers, meeting.meetingPrompt)
if (!summary) return
if (!summary) return null
return summary
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const generateWholeMeetingSentimentScore = async (
dataLoader.get('retroReflectionsByMeetingId').load(meetingId)
])
const team = await dataLoader.get('teams').loadNonNull(meeting.teamId)
const isAIAvailable = await canAccessAI(team, 'retrospective', dataLoader)
const isAIAvailable = await canAccessAI(team, dataLoader)
if (!isAIAvailable || reflections.length === 0) return undefined
const reflectionsWithSentimentScores = reflections.filter(
({sentimentScore}) => sentimentScore !== undefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ const summarizeTeamPrompt = async (meeting: TeamPromptMeeting, context: Internal
const pg = getKysely()

const summary = await generateStandupMeetingSummary(meeting, dataLoader)
if (summary) {
await pg.updateTable('NewMeeting').set({summary}).where('id', '=', meeting.id).execute()
}
await pg.updateTable('NewMeeting').set({summary}).where('id', '=', meeting.id).execute()

dataLoader.clearAll('newMeetings')
// wait for whole meeting summary to be generated before sending summary email and updating qualAIMeetingCount
Expand Down Expand Up @@ -61,7 +59,8 @@ const safeEndTeamPrompt = async ({
.set({
endedAt: sql`CURRENT_TIMESTAMP`,
usedReactjis: JSON.stringify(insights.usedReactjis),
engagement: insights.engagement
engagement: insights.engagement,
summary: '<loading>' // set as "<loading>" while the AI summary is being generated
})
.where('id', '=', meetingId)
.execute()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const updateGroupTitle = async (input: Input) => {
return
}
const team = await dataLoader.get('teams').loadNonNull(teamId)
const hasAIAccess = await canAccessAI(team, 'retrospective', dataLoader)
const hasAIAccess = await canAccessAI(team, dataLoader)
if (!hasAIAccess) {
const smartTitle = getGroupSmartTitle(reflections)
await updateSmartGroupTitle(reflectionGroupId, smartTitle)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ type TeamPromptMeeting implements NewMeeting {
"""
showConversionModal: Boolean!

"""
Is the OpenAI summary still being generated?
"""
isLoadingSummary: Boolean!

"""
The OpenAI generated summary of all the content in the meeting, such as reflections, tasks, and comments. Undefined if the user doesnt have access to the feature or it's unavailable in this meeting type`
"""
Expand Down
5 changes: 4 additions & 1 deletion packages/server/graphql/public/types/TeamPromptMeeting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,10 @@ const TeamPromptMeeting: TeamPromptMeetingResolvers = {
).filter(isValid)
const commentCount = commentCounts.reduce((cumSum, count) => cumSum + count, 0)
return commentCount
}
},

summary: ({summary}) => (summary === '<loading>' ? null : summary),
isLoadingSummary: ({summary}) => summary === '<loading>'
}

export default TeamPromptMeeting

0 comments on commit 260daf3

Please sign in to comment.