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(kudos): display notification when kudos received #9199

Merged
merged 28 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8773dee
chore(kudos): add kudos team settings
igorlesnenko Nov 9, 2023
0427b07
store emoji id
igorlesnenko Nov 9, 2023
598c526
chore(kudos): add kudos record when adding emoji reaction
igorlesnenko Nov 10, 2023
9778c04
Fix snackbar
igorlesnenko Nov 13, 2023
83f6490
quick fix for types
igorlesnenko Nov 14, 2023
e66353e
Dynamic emoji
igorlesnenko Nov 14, 2023
29720e2
del console log
igorlesnenko Nov 14, 2023
91bb210
feat(kudos): display notification when kudos received
igorlesnenko Nov 16, 2023
53bf03b
Remove comment
igorlesnenko Nov 16, 2023
673ac2a
Update notification text
igorlesnenko Nov 17, 2023
b69ae92
Remove unused kudos field
igorlesnenko Nov 21, 2023
c5abfd4
expose senderUser
igorlesnenko Nov 21, 2023
abb27b6
Fix reactable type
igorlesnenko Nov 21, 2023
82dcec4
Fix table name
igorlesnenko Nov 21, 2023
1d5a2dd
remove unused fields
igorlesnenko Nov 22, 2023
6d91805
Don't check feature flag
igorlesnenko Nov 22, 2023
411d13d
Fix analytics
igorlesnenko Nov 28, 2023
d7c9052
Add snackbar events
igorlesnenko Nov 28, 2023
5c0a450
fix typescript
igorlesnenko Nov 28, 2023
fb2ceb5
Merge branch 'kudos-add-kudoses-with-reactji' of github.com:ParabolIn…
igorlesnenko Nov 28, 2023
61e78bf
Fix type
igorlesnenko Nov 28, 2023
7df1911
Fix type
igorlesnenko Nov 28, 2023
8d14989
add notification status analytics
igorlesnenko Nov 28, 2023
50d28f3
Fix type
igorlesnenko Nov 28, 2023
7144fae
fix type
igorlesnenko Nov 28, 2023
b927334
Fix table name caused by wrong merge
igorlesnenko Nov 28, 2023
f736929
add snackbar clicked event to kudosReceived snackbar
igorlesnenko Nov 28, 2023
cc942c8
Merge branch 'master' into kudos-receiver-notification
igorlesnenko Nov 29, 2023
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
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', {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 This seems to send a client event for every kudos notification in the dropdown every time the user opens the notification dropdown menu.

Would it be more helpful to only send the client event if it's unread?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it similar to other notifications. @tghanken what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine as is. It could be helpful to add "isRead" ads a boolean parameter if it's easy!

Copy link
Contributor Author

@igorlesnenko igorlesnenko Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have notification status, CLICKED, READ etc. As an option, I can add it as is

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
Loading