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 10 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
2 changes: 2 additions & 0 deletions codegen.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"JiraIssue": "./types/JiraIssue#JiraIssueSource",
"JiraRemoteProject": "../types/JiraRemoteProject#JiraRemoteProjectSource",
"MeetingSeries": "../../postgres/types/MeetingSeries#MeetingSeries",
"Kudos": "../../postgres/types/Kudos#Kudos",
"MeetingTemplate": "../../database/types/MeetingTemplate#default",
"NewMeeting": "../../postgres/types/Meeting#AnyMeeting",
"NewMeetingPhase": "../../database/types/GenericMeetingPhase #default as GenericMeetingPhaseDB",
Expand Down Expand Up @@ -129,6 +130,7 @@
"UpdatedNotification": "./types/AddedNotification#UpdatedNotificationSource",
"UpgradeToTeamTierSuccess": "./types/UpgradeToTeamTierSuccess#UpgradeToTeamTierSuccessSource",
"UpsertTeamPromptResponseSuccess": "./types/UpsertTeamPromptResponseSuccess#UpsertTeamPromptResponseSuccessSource",
"AddReactjiToReactableSuccess": "./types/AddReactjiToReactableSuccess#AddReactjiToReactableSuccessSource",
"User": "../../postgres/types/IUser#default as IUser",
"UserLogInPayload": "./types/UserLogInPayload#UserLogInPayloadSource"
}
Expand Down
59 changes: 59 additions & 0 deletions packages/client/components/KudosReceivedNotification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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
}
`,
notificationRef
)
const {type, name, picture, meetingName, emoji, meetingId} = 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
})
}, [])

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: string | ReactNode
igorlesnenko marked this conversation as resolved.
Show resolved Hide resolved
notification: NotificationTemplate_notification$key
action?: ReactNode
children?: ReactNode
Expand Down
23 changes: 22 additions & 1 deletion packages/client/mutations/AddReactjiToReactableMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {commitMutation} from 'react-relay'
import createProxyRecord from '~/utils/relay/createProxyRecord'
import {StandardMutation} from '../types/relayMutations'
import {AddReactjiToReactableMutation as TAddReactjiToReactableMutation} from '../__generated__/AddReactjiToReactableMutation.graphql'
import getReactji from '~/utils/getReactji'

graphql`
fragment AddReactjiToReactableMutation_meeting on AddReactjiToReactableSuccess {
Expand Down Expand Up @@ -37,6 +38,14 @@ const mutation = graphql`
message
}
}
... on AddReactjiToReactableSuccess {
addedKudos {
emoji
receiverUser {
preferredName
}
}
}
...AddReactjiToReactableMutation_meeting @relay(mask: false)
}
}
Expand Down Expand Up @@ -104,7 +113,19 @@ const AddReactjiToReactableMutation: StandardMutation<TAddReactjiToReactableMuta
}
}
},
onCompleted,
onCompleted: (res, errors) => {
const {isRemove} = variables
const addedKudos = res.addReactjiToReactable.addedKudos
if (!isRemove && addedKudos) {
const {unicode} = getReactji(addedKudos.emoji)
atmosphere.eventEmitter.emit('addSnackbar', {
key: 'youGaveKudos',
message: `You gave kudos to ${addedKudos.receiverUser.preferredName} ${unicode}`,
autoDismiss: 5
})
}
onCompleted(res, errors)
},
onError
})
}
Expand Down
44 changes: 44 additions & 0 deletions packages/client/mutations/toasts/mapKudosReceivedToToast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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'
})
}
}
}

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
}
}
8 changes: 8 additions & 0 deletions packages/server/database/types/Team.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ interface Input {
qualAIMeetingsCount?: number
isOnboardTeam?: boolean
isOneOnOneTeam?: boolean
giveKudosWithEmoji?: boolean
kudosEmoji?: string
updatedAt?: Date
}

Expand All @@ -34,6 +36,8 @@ export default class Team {
orgId: string
isOnboardTeam: boolean
isOneOnOneTeam?: boolean
giveKudosWithEmoji: boolean
kudosEmoji: string
qualAIMeetingsCount: number
updatedAt: Date
constructor(input: Input) {
Expand All @@ -45,6 +49,8 @@ export default class Team {
isArchived,
isOnboardTeam,
isOneOnOneTeam,
giveKudosWithEmoji,
kudosEmoji,
lastMeetingType,
isPaid,
name,
Expand All @@ -65,6 +71,8 @@ export default class Team {
this.isArchived = isArchived ?? false
this.isOnboardTeam = isOnboardTeam ?? false
this.isOneOnOneTeam = isOneOnOneTeam ?? false
this.giveKudosWithEmoji = giveKudosWithEmoji ?? true
this.kudosEmoji = kudosEmoji ?? 'heart'
this.isPaid = isPaid ?? true
this.qualAIMeetingsCount = qualAIMeetingsCount ?? 0
}
Expand Down
2 changes: 2 additions & 0 deletions packages/server/dataloader/primaryKeyLoaderMakers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import getTeamsByIds from '../postgres/queries/getTeamsByIds'
import getTemplateRefsByIds from '../postgres/queries/getTemplateRefsByIds'
import getTemplateScaleRefsByIds from '../postgres/queries/getTemplateScaleRefsByIds'
import {getUsersByIds} from '../postgres/queries/getUsersByIds'
import {getKudosesByIds} from '../postgres/queries/getKudosesByIds'
import getMeetingTemplatesByIds from '../postgres/queries/getMeetingTemplatesByIds'
import {primaryKeyLoaderMaker} from './primaryKeyLoaderMaker'

Expand All @@ -18,3 +19,4 @@ export const teamPromptResponses = primaryKeyLoaderMaker(getTeamPromptResponsesB
export const meetingSeries = primaryKeyLoaderMaker(getMeetingSeriesByIds)
export const meetingTemplates = primaryKeyLoaderMaker(getMeetingTemplatesByIds)
export const domainJoinRequests = primaryKeyLoaderMaker(getDomainJoinRequestsByIds)
export const kudoses = primaryKeyLoaderMaker(getKudosesByIds)
Loading
Loading