Skip to content

Commit

Permalink
feat: show default insight (#10283)
Browse files Browse the repository at this point in the history
Co-authored-by: Terry Acker <ackernaut@gmail.com>
  • Loading branch information
nickoferrall and ackernaut authored Oct 16, 2024
1 parent aa8e931 commit f99e63a
Show file tree
Hide file tree
Showing 24 changed files with 429 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const WholeMeetingSummaryResult = ({meetingRef}: Props) => {
<td
align='center'
style={textStyle}
className='summary-link-style'
className='link-style'
dangerouslySetInnerHTML={{__html: sanitizedSummary}}
/>
</tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,14 @@ const TeamDashHeader = (props: Props) => {
...DashboardAvatars_team
id
name
hasInsightsFlag: featureFlag(featureName: "insights")
organization {
id
name
}
viewerTeamMember {
isLead
}
teamMembers(sortBy: "preferredName") {
...InviteTeamMemberAvatar_teamMembers
...DashboardAvatar_teamMember
Expand All @@ -109,15 +113,23 @@ const TeamDashHeader = (props: Props) => {
`,
teamRef
)
const {organization, id: teamId, name: teamName, teamMembers} = team
const {
organization,
id: teamId,
name: teamName,
teamMembers,
viewerTeamMember,
hasInsightsFlag
} = team
const {name: orgName, id: orgId} = organization
const canViewInsights = viewerTeamMember?.isLead && hasInsightsFlag
const {history} = useRouter()

const tabs = [
{label: 'Activity', path: 'activity'},
{label: 'Tasks', path: 'tasks'},
{label: 'Integrations', path: 'integrations'},
{label: 'Insights', path: 'insights'}
...(canViewInsights ? [{label: 'Insights', path: 'insights'}] : [])
]

const activePath = location.pathname.split('/').pop()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome'
import graphql from 'babel-plugin-relay/macro'
import dayjs from 'dayjs'
import {marked} from 'marked'
import React from 'react'
import {useFragment} from 'react-relay'
import sanitizeHtml from 'sanitize-html'
import {TeamInsightContent_team$key} from '../../../../__generated__/TeamInsightContent_team.graphql'

interface Props {
teamName: string
insightRef: TeamInsightContent_team$key
}

const TeamInsightContent = (props: Props) => {
const {insightRef, teamName} = props
const insight = useFragment(
graphql`
fragment TeamInsightContent_team on Insight {
meetingsCount
wins
challenges
startDateTime
endDateTime
}
`,
insightRef
)
const {meetingsCount, wins, challenges} = insight

const renderMarkdown = (text: string) => {
const renderedText = marked(text, {
gfm: true,
breaks: true
}) as string
return sanitizeHtml(renderedText, {
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['a']),
allowedAttributes: {
...sanitizeHtml.defaults.allowedAttributes,
a: ['href', 'target', 'rel']
},
transformTags: {
a: (tagName, attribs) => {
return {
tagName,
attribs: {
...attribs,
target: '_blank',
rel: 'noopener noreferrer'
}
}
}
}
})
}

const formatDateRange = (start: string, end: string) => {
const startDate = dayjs(start)
const endDate = dayjs(end)

if (startDate.year() === endDate.year()) {
return `${startDate.format('MMM')} to ${endDate.format('MMM YYYY')}`
} else {
return `${startDate.format('MMM YYYY')} to ${endDate.format('MMM YYYY')}`
}
}

const dateRange = insight
? formatDateRange(insight.startDateTime, insight.endDateTime)
: 'Date range not available'

return (
<div className='mx-auto aspect-[1/1.414] w-[640px] max-w-3xl overflow-y-auto rounded-lg bg-white px-[56px] pt-8 shadow-md'>
<h2 className='mb-4 mt-0 flex items-center pt-0 text-2xl font-semibold leading-9'>
<AutoAwesomeIcon className='mr-2 h-9 w-9 text-grape-500' />
<span>Insights - {dateRange}</span>
</h2>
<p className='mb-6 text-sm text-slate-600'>Summarized {meetingsCount} meetings</p>

{wins && wins.length > 0 && (
<>
<h3 className='mb-0 text-lg font-semibold text-slate-700'>Wins</h3>
<p className='mb-2 mt-0 text-sm italic text-slate-600'>
What wins has "{teamName}" seen during this timeframe?
</p>
<ul className='mb-6 list-disc space-y-0 pl-6'>
{wins.map((win, index) => (
<li key={index} className='text-sm text-slate-700'>
<span
className='link-style'
dangerouslySetInnerHTML={{__html: renderMarkdown(win)}}
/>
</li>
))}
</ul>
</>
)}

{challenges && challenges.length > 0 && (
<>
<h3 className='mb-0 text-lg font-semibold text-slate-700'>Challenges</h3>
<p className='mb-2 mt-0 text-sm italic text-slate-600'>
What challenges has "{teamName}" faced during this timeframe?
</p>
<ul className='list-disc space-y-0 pl-6'>
{challenges.map((challenge, index) => (
<li key={index} className='text-sm text-slate-700'>
<span
className='link-style'
dangerouslySetInnerHTML={{__html: renderMarkdown(challenge)}}
/>
</li>
))}
</ul>
</>
)}
</div>
)
}

export default TeamInsightContent
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import AddIcon from '@mui/icons-material/Add'
import React from 'react'
import insightsEmptyStateImg from '../../../../../../static/images/illustrations/insights-empty-state.png'
import useAtmosphere from '../../../../hooks/useAtmosphere'
import useMutationProps from '../../../../hooks/useMutationProps'
import GenerateInsightMutation from '../../../../mutations/GenerateInsightMutation'
import plural from '../../../../utils/plural'

interface Props {
meetingsCount?: number
teamId?: string
}

const TeamInsightEmptyState = (props: Props) => {
const {meetingsCount, teamId} = props
const atmosphere = useAtmosphere()
const {onError, error, onCompleted, submitMutation, submitting} = useMutationProps()

if (meetingsCount === undefined || !teamId) return null

const canGenerateInsight = meetingsCount > 1

const handleGenerateInsight = () => {
if (submitting) return
submitMutation()
const now = new Date()
const threeMonthsAgo = new Date(now.getFullYear(), now.getMonth() - 3, now.getDate())
GenerateInsightMutation(
atmosphere,
{
teamId,
startDate: threeMonthsAgo.toISOString(), // TODO: let users choose date range
endDate: now.toISOString()
},
{onError, onCompleted}
)
}

return (
<div className='flex flex-col items-center text-center'>
<img src={insightsEmptyStateImg} alt='Empty state' width={300} height={300} />
<div className='space-y-1'>
<p className='mt-4 text-lg leading-tight'>
Your team has completed <strong>{meetingsCount}</strong>{' '}
{plural(meetingsCount, 'meeting')}.
</p>
{canGenerateInsight ? (
<p className='text-lg leading-tight'>
This should be{' '}
<strong>
<em>good fodder</em>
</strong>{' '}
for some interesting insights!
</p>
) : (
<p className='text-lg leading-tight'>
Create more meetings to start generating insights for your team.
</p>
)}
</div>
{canGenerateInsight && (
<button
className='mt-6 flex items-center rounded-full bg-grape-500 py-2 px-6 font-bold text-white transition-all duration-200 ease-in-out hover:cursor-pointer hover:bg-grape-600 hover:shadow-md disabled:opacity-50'
onClick={handleGenerateInsight}
disabled={submitting}
>
<AddIcon className='mr-2 h-5 w-5' />
{submitting ? 'Generating...' : 'Generate Insights'}
</button>
)}
{error && (
<div className='mt-2 pr-4 text-xs font-semibold text-tomato-500'>{error.message}</div>
)}
</div>
)
}

export default TeamInsightEmptyState
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome'
import graphql from 'babel-plugin-relay/macro'
import React from 'react'
import {PreloadedQuery, usePreloadedQuery} from 'react-relay'
import {TeamInsightsQuery} from '../../../../__generated__/TeamInsightsQuery.graphql'
import TeamInsightContent from './TeamInsightContent'
import TeamInsightEmptyState from './TeamInsightEmptyState'

interface Props {
queryRef: PreloadedQuery<TeamInsightsQuery>
Expand All @@ -12,36 +13,52 @@ const query = graphql`
query TeamInsightsQuery($teamId: ID!) {
viewer {
team(teamId: $teamId) {
id
...TeamInsights_team @relay(mask: false)
name
insight {
wins
...TeamInsightContent_team
}
}
}
}
`

const Insights = (props: Props) => {
graphql`
fragment TeamInsights_team on Team {
id
retroMeetingsCount
}
`

const TeamInsights = (props: Props) => {
const {queryRef} = props
const data = usePreloadedQuery<TeamInsightsQuery>(query, queryRef)
// TODO: use the query rather than just console logging it
console.log('🚀 ~ data:', data)
const {viewer} = data
const {team} = viewer
const {id: teamId, insight, name, retroMeetingsCount} = team ?? {}

return (
<div className='mb-8 space-y-6'>
<p className='mb-6 mt-[20px] text-sm text-slate-900'>
Only you (as <span className='font-bold'>Team Lead</span>) can see Team Insights. Insights
are auto-generated.{' '}
<a href='#' className='font-semibold text-sky-500 hover:underline'>
<a
href='#'
className='font-semibold text-sky-500 hover:underline'
target='_blank'
rel='noopener noreferrer'
>
Give us feedback
</a>
</p>
<div className='mx-auto aspect-[1/1.414] w-[640px] max-w-3xl overflow-y-auto rounded-lg bg-white px-[56px] pt-8 shadow-md'>
<h2 className='mb-4 mt-0 flex items-center pt-0 text-2xl font-semibold leading-9'>
<AutoAwesomeIcon className='mr-2 h-9 w-9 text-grape-500' />
<span>Insights - Aug to Sep 2024</span>
</h2>
</div>
{insight ? (
<TeamInsightContent insightRef={insight} teamName={name!} />
) : (
<TeamInsightEmptyState teamId={teamId} meetingsCount={retroMeetingsCount} />
)}
</div>
)
}

export default Insights
export default TeamInsights
1 change: 1 addition & 0 deletions packages/client/mutations/EndRetrospectiveMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ graphql`
activeMeetings {
id
}
...TeamInsights_team
insights {
...TeamDashInsights_insights
}
Expand Down
10 changes: 8 additions & 2 deletions packages/client/mutations/GenerateInsightMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ import {StandardMutation} from '../types/relayMutations'

graphql`
fragment GenerateInsightMutation_team on GenerateInsightSuccess {
wins
challenges
team {
id
insight {
wins
challenges
meetingsCount
}
}
}
`

Expand Down
7 changes: 7 additions & 0 deletions packages/client/shared/gqlIds/InsightId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const InsightId = {
join: (ownerId: string, databaseId: number) => `insight:${ownerId}:${databaseId}`,
split: (id: string) => {
const [, ownerId, databaseId] = id.split(':')
return {ownerId, databaseId}
}
}
2 changes: 1 addition & 1 deletion packages/client/styles/theme/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@
@apply m-0 rounded-[1px] border-l-2 border-solid border-l-slate-500 bg-slate-200 py-0 px-[8px] font-mono font-[13px] leading-normal;
}

.summary-link-style a {
.link-style a {
@apply text-sky-500;
text-decoration: underline;
}
3 changes: 3 additions & 0 deletions packages/client/subscriptions/TeamSubscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ const subscription = graphql`
subscription TeamSubscription {
teamSubscription {
fieldName
GenerateInsightSuccess {
...GenerateInsightMutation_team @relay(mask: false)
}
UpdateRecurrenceSettingsSuccess {
...UpdateRecurrenceSettingsMutation_team @relay(mask: false)
}
Expand Down
Loading

0 comments on commit f99e63a

Please sign in to comment.