Skip to content

Commit

Permalink
feat: add notifications for mention in reflections and show kudos pre…
Browse files Browse the repository at this point in the history
…view (#9354)

* feat: add notifications for mention in reflections and show kudos preview

* Fix types

* chore(kudos): track kudos type and meeting type in kudosSent event (#9373)

* senderName senderPicture

* Fix tests

* Add TODO comment to use single Mentioned notification everywhere
  • Loading branch information
igorlesnenko committed Jan 25, 2024
1 parent d991532 commit a7f9b5d
Show file tree
Hide file tree
Showing 16 changed files with 402 additions and 28 deletions.
2 changes: 2 additions & 0 deletions codegen.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"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",
Expand All @@ -101,6 +102,7 @@
"RequestToJoinDomainSuccess": "./types/RequestToJoinDomainSuccess#RequestToJoinDomainSuccessSource",
"ResetReflectionGroupsSuccess": "./types/ResetReflectionGroupsSuccess#ResetReflectionGroupsSuccessSource",
"RetroReflectionGroup": "../../database/types/RetroReflectionGroup#default as RetroReflectionGroupDB",
"RetroReflection": "../../database/types/RetroReflection#default as RetroReflectionDB",
"RetrospectiveMeeting": "../../database/types/MeetingRetrospective#default",
"RetrospectiveMeetingMember": "../../database/types/RetroMeetingMember#default",
"Reactable": "../../database/types/Reactable#Reactable",
Expand Down
111 changes: 111 additions & 0 deletions packages/client/components/Mentioned.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import graphql from 'babel-plugin-relay/macro'
import React, {useEffect} from 'react'
import {useFragment} from 'react-relay'
import NotificationAction from '~/components/NotificationAction'
import useRouter from '../hooks/useRouter'
import {Mentioned_notification$key} from '../__generated__/Mentioned_notification.graphql'
import NotificationTemplate from './NotificationTemplate'
import SendClientSideEvent from '../utils/SendClientSideEvent'
import useAtmosphere from '~/hooks/useAtmosphere'
import anonymousAvatar from '~/styles/theme/images/anonymous-avatar.svg'
import useEditorState from '../hooks/useEditorState'
import {Editor} from 'draft-js'

interface Props {
notification: Mentioned_notification$key
}

const Mentioned = (props: Props) => {
const {notification: notificationRef} = props
const notification = useFragment(
graphql`
fragment Mentioned_notification on NotifyMentioned {
...NotificationTemplate_notification
type
status
senderName
senderPicture
kudosEmojiUnicode
createdAt
meetingId
meetingName
retroReflection {
content
}
retroDiscussStageIdx
}
`,
notificationRef
)
const {history} = useRouter()
const atmosphere = useAtmosphere()

const {
senderName,
senderPicture,
meetingId,
meetingName,
kudosEmojiUnicode,
type,
status,
retroReflection,
retroDiscussStageIdx
} = notification
const isAnonymous = !senderName
const authorName = isAnonymous ? 'Someone' : senderName

useEffect(() => {
SendClientSideEvent(atmosphere, 'Notification Viewed', {
notificationType: type,
notificationStatus: status,
kudosEmojiUnicode
})
}, [])

let locationType = 'their response'
let actionLabel = 'See their response'
let actionUrl = `/meet/${meetingId}`
let previewContent = null

if (retroReflection) {
actionLabel = 'See their reflection'
locationType = 'their reflection'
previewContent = retroReflection.content
if (retroDiscussStageIdx) {
actionUrl = `/meet/${meetingId}/discuss/${retroDiscussStageIdx}`
}
}

const message = !kudosEmojiUnicode
? `${authorName} mentioned you in ${locationType} in ${meetingName}`
: `${kudosEmojiUnicode} ${authorName} gave you kudos in ${locationType} in ${meetingName}`

const goThere = () => {
history.push(actionUrl)
}

const [editorState] = useEditorState(previewContent)

return (
<NotificationTemplate
avatar={!isAnonymous ? senderPicture : anonymousAvatar}
message={message}
notification={notification}
action={<NotificationAction label={actionLabel} onClick={goThere} />}
>
{previewContent && (
<div className='my-1 mx-0 mt-4 rounded bg-white p-2 text-sm leading-5 shadow-card'>
<Editor
readOnly
editorState={editorState}
onChange={() => {
/*noop*/
}}
/>
</div>
)}
</NotificationTemplate>
)
}

export default Mentioned
2 changes: 2 additions & 0 deletions packages/client/components/NotificationPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const typePicker: Record<NotificationEnum, LazyExoticPreload<any>> = {
RESPONSE_MENTIONED: lazyPreload(
() => import(/* webpackChunkName: 'ResponseMentioned' */ './ResponseMentioned')
),
MENTIONED: lazyPreload(() => import(/* webpackChunkName: 'Mentioned' */ './Mentioned')),
RESPONSE_REPLIED: lazyPreload(
() => import(/* webpackChunkName: 'ResponseReplied' */ './ResponseReplied')
),
Expand Down Expand Up @@ -79,6 +80,7 @@ const NotificationPicker = (props: Props) => {
...TeamInvitationNotification_notification
...MeetingStageTimeLimitEnd_notification
...ResponseMentioned_notification
...Mentioned_notification
...ResponseReplied_notification
...TeamsLimitExceededNotification_notification
...TeamsLimitReminderNotification_notification
Expand Down
84 changes: 84 additions & 0 deletions packages/client/mutations/toasts/mapMentionedToToast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import graphql from 'babel-plugin-relay/macro'
import {Snack} from '../../components/Snackbar'
import {OnNextHistoryContext} from '../../types/relayMutations'
import {mapMentionedToToast_notification$data} from '../../__generated__/mapMentionedToToast_notification.graphql'
import SendClientSideEvent from '../../utils/SendClientSideEvent'
import makeNotificationToastKey from './makeNotificationToastKey'

graphql`
fragment mapMentionedToToast_notification on NotifyMentioned {
id
kudosEmojiUnicode
senderName
meetingId
meetingName
retroReflection {
content
}
retroDiscussStageIdx
}
`

const mapMentionedToToast = (
notification: mapMentionedToToast_notification$data,
{atmosphere, history}: OnNextHistoryContext
): Snack | null => {
if (!notification) return null
const {
id: notificationId,
senderName,
meetingName,
kudosEmojiUnicode,
retroReflection,
retroDiscussStageIdx,
meetingId
} = notification
const isAnonymous = !senderName
const authorName = isAnonymous ? 'Someone' : senderName

let locationType = 'their response'
let actionLabel = 'See their response'
let snackbarType = 'responseMentioned'
let actionUrl = `/meet/${meetingId}`

if (retroReflection) {
actionLabel = 'See their reflection'
locationType = 'their reflection'
snackbarType = 'reflectionMentioned'
if (retroDiscussStageIdx) {
actionUrl = `/meet/${meetingId}/discuss/${retroDiscussStageIdx}`
}
}

const message = !kudosEmojiUnicode
? `${authorName} mentioned you in ${locationType} in ${meetingName}`
: `${kudosEmojiUnicode} ${authorName} gave you kudos in ${locationType} in ${meetingName}`

const goThere = () => {
history.push(actionUrl)
}

return {
key: makeNotificationToastKey(notificationId),
autoDismiss: 5,
message,
action: {
label: actionLabel,
callback: goThere
},
onShow: () => {
SendClientSideEvent(atmosphere, 'Snackbar Viewed', {
snackbarType,
kudosEmojiUnicode
})
},
onManualDismiss: () => {
SendClientSideEvent(atmosphere, 'Snackbar Clicked', {
snackbarType,
kudosEmojiUnicode
})
}
}
}

export default mapMentionedToToast
3 changes: 3 additions & 0 deletions packages/client/mutations/toasts/popNotificationToast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import SetNotificationStatusMutation from '../SetNotificationStatusMutation'
import mapDiscussionMentionedToToast from './mapDiscussionMentionedToToast'
import mapResponseMentionedToToast from './mapResponseMentionedToToast'
import mapMentionedToToast from './mapMentionedToToast'
import mapResponseRepliedToToast from './mapResponseRepliedToToast'
import mapTeamsLimitExceededToToast from './mapTeamsLimitExceededToToast'
import mapTeamsLimitReminderToToast from './mapTeamsLimitReminderToToast'
Expand All @@ -20,6 +21,7 @@ const typePicker: Partial<
> = {
DISCUSSION_MENTIONED: mapDiscussionMentionedToToast,
RESPONSE_MENTIONED: mapResponseMentionedToToast,
MENTIONED: mapMentionedToToast,
RESPONSE_REPLIED: mapResponseRepliedToToast,
TEAMS_LIMIT_EXCEEDED: mapTeamsLimitExceededToToast,
TEAMS_LIMIT_REMINDER: mapTeamsLimitReminderToToast,
Expand All @@ -35,6 +37,7 @@ graphql`
id
...mapDiscussionMentionedToToast_notification @relay(mask: false)
...mapResponseMentionedToToast_notification @relay(mask: false)
...mapMentionedToToast_notification @relay(mask: false)
...mapResponseRepliedToToast_notification @relay(mask: false)
...mapTeamsLimitExceededToToast_notification @relay(mask: false)
...mapTeamsLimitReminderToToast_notification @relay(mask: false)
Expand Down
2 changes: 2 additions & 0 deletions packages/server/database/rethinkDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import NotificationMeetingStageTimeLimitEnd from './types/NotificationMeetingSta
import NotificationPaymentRejected from './types/NotificationPaymentRejected'
import NotificationPromoteToBillingLeader from './types/NotificationPromoteToBillingLeader'
import NotificationResponseMentioned from './types/NotificationResponseMentioned'
import NotificationMentioned from './types/NotificationMentioned'
import NotificationTaskInvolves from './types/NotificationTaskInvolves'
import NotificationTeamArchived from './types/NotificationTeamArchived'
import NotificationTeamInvitation from './types/NotificationTeamInvitation'
Expand Down Expand Up @@ -115,6 +116,7 @@ export type RethinkSchema = {
| NotificationTeamInvitation
| NotificationResponseMentioned
| NotificationResponseReplied
| NotificationMentioned
index: 'userId'
}
Organization: {
Expand Down
1 change: 1 addition & 0 deletions packages/server/database/types/Notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type NotificationEnum =
| 'PROMOTE_TO_BILLING_LEADER'
| 'RESPONSE_MENTIONED'
| 'RESPONSE_REPLIED'
| 'MENTIONED'
| 'TASK_INVOLVES'
| 'TEAM_ARCHIVED'
| 'TEAM_INVITATION'
Expand Down
53 changes: 53 additions & 0 deletions packages/server/database/types/NotificationMentioned.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Notification from './Notification'

interface Input {
userId: string
senderName: string | null
senderPicture: string | null
senderUserId: string
meetingName: string
meetingId: string
retroReflectionId?: string | null
retroDiscussStageIdx?: number | null
kudosEmoji?: string | null
kudosEmojiUnicode?: string | null
}

// TODO: replace NotificationResponseMentioned and NotificationResponseReplied with NotificationMentioned
export default class NotificationMentioned extends Notification {
readonly type = 'MENTIONED'
senderName: string | null
senderPicture: string | null
senderUserId: string
meetingName: string
meetingId: string
retroReflectionId?: string | null
retroDiscussStageIdx?: number | null
kudosEmoji?: string | null
kudosEmojiUnicode?: string | null

constructor(input: Input) {
const {
userId,
senderName,
senderPicture,
senderUserId,
meetingName,
meetingId,
retroReflectionId,
retroDiscussStageIdx,
kudosEmoji,
kudosEmojiUnicode
} = input
super({userId, type: 'MENTIONED'})
this.senderName = senderName
this.senderPicture = senderPicture
this.senderUserId = senderUserId
this.meetingName = meetingName
this.meetingId = meetingId
this.retroReflectionId = retroReflectionId
this.kudosEmoji = kudosEmoji
this.kudosEmojiUnicode = kudosEmojiUnicode
this.retroDiscussStageIdx = retroDiscussStageIdx
}
}

0 comments on commit a7f9b5d

Please sign in to comment.