Skip to content

Commit

Permalink
feat(kudos): display notification when kudos received (#9199)
Browse files Browse the repository at this point in the history
* chore(kudos): add kudos team settings

* store emoji id

* chore(kudos): add kudos record when adding emoji reaction

* Fix snackbar

* quick fix for types

* Dynamic emoji

* del console log

* feat(kudos): display notification when kudos received

* Remove comment

* Update notification text

* Remove unused kudos field

* expose senderUser

* Fix reactable type

* Fix table name

* remove unused fields

* Don't check feature flag

* Fix analytics

* Add snackbar events

* fix typescript

* Fix type

* Fix type

* add notification status analytics

* Fix type

* fix type

* Fix table name caused by wrong merge

* add snackbar clicked event to kudosReceived snackbar
  • Loading branch information
igorlesnenko committed Nov 29, 2023
1 parent cecdbe4 commit 8f0e72f
Show file tree
Hide file tree
Showing 12 changed files with 228 additions and 3 deletions.
61 changes: 61 additions & 0 deletions packages/client/components/KudosReceivedNotification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import graphql from 'babel-plugin-relay/macro'
import {Link} from 'react-router-dom'
import React, {useEffect} from 'react'
import {useFragment} from 'react-relay'
import {KudosReceivedNotification_notification$key} from '~/__generated__/KudosReceivedNotification_notification.graphql'
import NotificationTemplate from './NotificationTemplate'
import useAtmosphere from '../hooks/useAtmosphere'
import SendClientSideEvent from '../utils/SendClientSideEvent'
import getReactji from '~/utils/getReactji'

interface Props {
notification: KudosReceivedNotification_notification$key
}

const KudosReceivedNotification = (props: Props) => {
const atmosphere = useAtmosphere()
const {notification: notificationRef} = props
const notification = useFragment(
graphql`
fragment KudosReceivedNotification_notification on NotifyKudosReceived {
...NotificationTemplate_notification
id
type
name
picture
meetingName
meetingId
emoji
status
}
`,
notificationRef
)
const {type, name, picture, meetingName, emoji, meetingId, status} = notification

const {unicode} = getReactji(emoji)

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

return (
<NotificationTemplate
message={
<>
{unicode} {name} gave you kudos in{' '}
<Link to={`/meet/${meetingId}`} className='font-semibold text-sky-500 underline'>
{meetingName}
</Link>
</>
}
notification={notification}
avatar={picture}
/>
)
}

export default KudosReceivedNotification
4 changes: 4 additions & 0 deletions packages/client/components/NotificationPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ const typePicker: Record<NotificationEnum, LazyExoticPreload<any>> = {
),
RESPONSE_REPLIED: lazyPreload(
() => import(/* webpackChunkName: 'ResponseReplied' */ './ResponseReplied')
),
KUDOS_RECEIVED: lazyPreload(
() => import(/* webpackChunkName: 'KudosReceivedNotification' */ './KudosReceivedNotification')
)
}

Expand Down Expand Up @@ -81,6 +84,7 @@ const NotificationPicker = (props: Props) => {
...TeamsLimitReminderNotification_notification
...PromptToJoinOrgNotification_notification
...RequestToJoinOrgNotification_notification
...KudosReceivedNotification_notification
}
`,
notificationRef
Expand Down
2 changes: 1 addition & 1 deletion packages/client/components/NotificationTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import NotificationRow from './NotificationRow'

interface Props {
avatar?: string
message: string
message: ReactNode
notification: NotificationTemplate_notification$key
action?: ReactNode
children?: ReactNode
Expand Down
49 changes: 49 additions & 0 deletions packages/client/mutations/toasts/mapKudosReceivedToToast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import graphql from 'babel-plugin-relay/macro'
import {Snack} from '../../components/Snackbar'
import {mapKudosReceivedToToast_notification$data} from '../../__generated__/mapKudosReceivedToToast_notification.graphql'
import makeNotificationToastKey from './makeNotificationToastKey'
import {OnNextHistoryContext} from '../../types/relayMutations'
import SendClientSideEvent from '../../utils/SendClientSideEvent'
import getReactji from '~/utils/getReactji'

graphql`
fragment mapKudosReceivedToToast_notification on NotifyKudosReceived {
id
name
meetingName
meetingId
emoji
}
`

const mapKudosReceivedToToast = (
notification: mapKudosReceivedToToast_notification$data,
{atmosphere, history}: OnNextHistoryContext
): Snack => {
const {id: notificationId, meetingName, name, emoji, meetingId} = notification
const {unicode} = getReactji(emoji)
return {
autoDismiss: 5,
showDismissButton: true,
key: makeNotificationToastKey(notificationId),
message: `${unicode} ${name} gave you kudos in`,
action: {
label: meetingName,
callback: () => {
history.push(`/meet/${meetingId}`)
}
},
onShow: () => {
SendClientSideEvent(atmosphere, 'Snackbar Viewed', {
snackbarType: 'kudosReceived'
})
},
onManualDismiss: () => {
SendClientSideEvent(atmosphere, 'Snackbar Clicked', {
snackbarType: 'kudosReceived'
})
}
}
}

export default mapKudosReceivedToToast
5 changes: 4 additions & 1 deletion packages/client/mutations/toasts/popNotificationToast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import mapTeamsLimitExceededToToast from './mapTeamsLimitExceededToToast'
import mapTeamsLimitReminderToToast from './mapTeamsLimitReminderToToast'
import mapPromptToJoinOrgToToast from './mapPromptToJoinOrgToToast'
import mapRequestToJoinOrgToToast from './mapRequestToJoinOrgToToast'
import mapKudosReceivedToToast from './mapKudosReceivedToToast'

const typePicker: Partial<
Record<NotificationEnum, (notification: any, context: OnNextHistoryContext) => Snack | null>
Expand All @@ -23,7 +24,8 @@ const typePicker: Partial<
TEAMS_LIMIT_EXCEEDED: mapTeamsLimitExceededToToast,
TEAMS_LIMIT_REMINDER: mapTeamsLimitReminderToToast,
PROMPT_TO_JOIN_ORG: mapPromptToJoinOrgToToast,
REQUEST_TO_JOIN_ORG: mapRequestToJoinOrgToToast
REQUEST_TO_JOIN_ORG: mapRequestToJoinOrgToToast,
KUDOS_RECEIVED: mapKudosReceivedToToast
}

graphql`
Expand All @@ -38,6 +40,7 @@ graphql`
...mapTeamsLimitReminderToToast_notification @relay(mask: false)
...mapPromptToJoinOrgToToast_notification @relay(mask: false)
...mapRequestToJoinOrgToToast_notification @relay(mask: false)
...mapKudosReceivedToToast_notification @relay(mask: false)
}
}
`
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 @@ -16,6 +16,7 @@ export type NotificationEnum =
| 'TEAMS_LIMIT_REMINDER'
| 'PROMPT_TO_JOIN_ORG'
| 'REQUEST_TO_JOIN_ORG'
| 'KUDOS_RECEIVED'

export interface NotificationInput {
type: NotificationEnum
Expand Down
32 changes: 32 additions & 0 deletions packages/server/database/types/NotificationKudosReceived.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Notification from './Notification'

interface Input {
userId: string
name: string
picture: string
senderUserId: string
meetingName: string
meetingId: string
emoji: string
}

export default class NotificationKudosReceived extends Notification {
readonly type = 'KUDOS_RECEIVED'
name: string
picture: string
senderUserId: string
meetingName: string
meetingId: string
emoji: string

constructor(input: Input) {
const {userId, name, picture, senderUserId, meetingName, meetingId, emoji} = input
super({userId, type: 'KUDOS_RECEIVED'})
this.name = name
this.picture = picture
this.senderUserId = senderUserId
this.meetingName = meetingName
this.meetingId = meetingId
this.emoji = emoji
}
}
1 change: 1 addition & 0 deletions packages/server/graphql/private/typeDefs/_legacy.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1658,6 +1658,7 @@ enum NotificationEnum {
TEAMS_LIMIT_REMINDER
PROMPT_TO_JOIN_ORG
REQUEST_TO_JOIN_ORG
KUDOS_RECEIVED
}

"""
Expand Down
18 changes: 18 additions & 0 deletions packages/server/graphql/public/mutations/addReactjiToReactable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {AnyMeeting} from '../../../postgres/types/Meeting'
import MeetingRetrospective from '../../../database/types/MeetingRetrospective'
import {TeamPromptResponse} from '../../../postgres/queries/getTeamPromptResponsesByIds'
import {MutationResolvers} from '../resolverTypes'
import NotificationKudosReceived from '../../../database/types/NotificationKudosReceived'
import publishNotification from './helpers/publishNotification'

const rethinkTableLookup = {
COMMENT: 'Comment',
Expand Down Expand Up @@ -190,6 +192,22 @@ const addReactjiToReactable: MutationResolvers['addReactjiToReactable'] = async
.returning('id')
.executeTakeFirst())!.id

const senderUser = await dataLoader.get('users').loadNonNull(viewerId)

const notificationsToInsert = new NotificationKudosReceived({
userId: reactableCreatorId,
senderUserId: viewerId,
meetingId,
meetingName: meeting.name,
emoji: team.kudosEmoji,
name: senderUser.preferredName,
picture: senderUser.picture
})

await r.table('Notification').insert(notificationsToInsert).run()

publishNotification(notificationsToInsert, subOptions)

analytics.kudosSent(viewerId, teamId, addedKudosId, reactableCreatorId)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
type NotifyKudosReceived implements Notification {
"""
A shortid for the notification
"""
id: ID!

"""
UNREAD if new, READ if viewer has seen it, CLICKED if viewed clicked it
"""
status: NotificationStatusEnum!

"""
The datetime to activate the notification & send it to the client
"""
createdAt: DateTime!
type: NotificationEnum!

"""
The userId that should see this notification
"""
userId: ID!

"""
Sender name
"""
name: String!

"""
Sender picture
"""
picture: URL!

"""
Meeting name
"""
meetingName: String!

"""
Meeting id
"""
meetingId: String!

"""
Kudos emoji
"""
emoji: String!
}
7 changes: 7 additions & 0 deletions packages/server/graphql/public/types/NotifyKudosReceived.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {NotifyKudosReceivedResolvers} from '../resolverTypes'

const NotifyKudosReceived: NotifyKudosReceivedResolvers = {
__isTypeOf: ({type}) => type === 'KUDOS_RECEIVED'
}

export default NotifyKudosReceived
4 changes: 3 additions & 1 deletion packages/server/graphql/types/NotificationEnum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type NotificationEnumType =
| 'TEAMS_LIMIT_REMINDER'
| 'PROMPT_TO_JOIN_ORG'
| 'REQUEST_TO_JOIN_ORG'
| 'KUDOS_RECEIVED'

const NotificationEnum = new GraphQLEnumType({
name: 'NotificationEnum',
Expand All @@ -27,7 +28,8 @@ const NotificationEnum = new GraphQLEnumType({
TEAMS_LIMIT_EXCEEDED: {},
TEAMS_LIMIT_REMINDER: {},
PROMPT_TO_JOIN_ORG: {},
REQUEST_TO_JOIN_ORG: {}
REQUEST_TO_JOIN_ORG: {},
KUDOS_RECEIVED: {}
}
})

Expand Down

0 comments on commit 8f0e72f

Please sign in to comment.