From 56fcf955bd8263b83193e83650f903d3b60368eb Mon Sep 17 00:00:00 2001 From: Igor Lesnenko Date: Fri, 1 Dec 2023 17:47:22 +0400 Subject: [PATCH 01/11] feat(kudos): send kudos by text in standups --- .../client/components/ResponseMentioned.tsx | 12 +- .../EmailResponseMentioned.tsx | 1 + .../UpsertTeamPromptResponseMutation.ts | 33 ++- .../toasts/mapResponseMentionedToToast.ts | 12 +- .../types/NotificationResponseMentioned.ts | 5 +- .../helpers/notifications/SlackNotifier.ts | 1 + .../__tests__/getKudosUserIdsFromJson.test.ts | 198 ++++++++++++++++++ .../helpers/getKudosUserIdsFromJson.ts | 29 +++ .../helpers/publishTeamPromptMentions.ts | 19 +- .../mutations/upsertTeamPromptResponse.ts | 51 ++++- .../typeDefs/NotifyResponseMentioned.graphql | 5 + .../typeDefs/upsertTeamPromptResponse.graphql | 5 + .../types/UpsertTeamPromptResponseSuccess.ts | 9 + packages/server/utils/getReactji.ts | 15 ++ 14 files changed, 384 insertions(+), 11 deletions(-) create mode 100644 packages/server/graphql/public/mutations/helpers/__tests__/getKudosUserIdsFromJson.test.ts create mode 100644 packages/server/graphql/public/mutations/helpers/getKudosUserIdsFromJson.ts create mode 100644 packages/server/utils/getReactji.ts diff --git a/packages/client/components/ResponseMentioned.tsx b/packages/client/components/ResponseMentioned.tsx index bbf5289da12..2e0efdcc792 100644 --- a/packages/client/components/ResponseMentioned.tsx +++ b/packages/client/components/ResponseMentioned.tsx @@ -5,6 +5,7 @@ import NotificationAction from '~/components/NotificationAction' import useRouter from '../hooks/useRouter' import {ResponseMentioned_notification$key} from '../__generated__/ResponseMentioned_notification.graphql' import NotificationTemplate from './NotificationTemplate' +import getReactji from '../utils/getReactji' interface Props { notification: ResponseMentioned_notification$key @@ -27,12 +28,13 @@ const ResponseMentioned = (props: Props) => { id name } + kudosEmoji } `, notificationRef ) const {history} = useRouter() - const {meeting, response} = notification + const {meeting, response, kudosEmoji} = notification const {picture: authorPicture, preferredName: authorName} = response.user const {id: meetingId, name: meetingName} = meeting @@ -40,11 +42,17 @@ const ResponseMentioned = (props: Props) => { history.push(`/meet/${meetingId}/responses?responseId=${encodeURIComponent(response.id)}`) } + const unicodeEmoji = kudosEmoji ? getReactji(kudosEmoji).unicode : '' + + const message = kudosEmoji + ? `${unicodeEmoji} ${authorName} mentioned you and gave kudos in their response in ${meetingName}.` + : `${authorName} mentioned you in their response in ${meetingName}.` + // :TODO: (jmtaber129): Show mention preview. return ( } /> diff --git a/packages/client/modules/email/components/EmailNotifications/EmailResponseMentioned.tsx b/packages/client/modules/email/components/EmailNotifications/EmailResponseMentioned.tsx index 4c23c81cc7b..d3deeafd58b 100644 --- a/packages/client/modules/email/components/EmailNotifications/EmailResponseMentioned.tsx +++ b/packages/client/modules/email/components/EmailNotifications/EmailResponseMentioned.tsx @@ -28,6 +28,7 @@ const EmailResponseMentioned = (props: Props) => { id name } + kudosEmoji } `, notificationRef diff --git a/packages/client/mutations/UpsertTeamPromptResponseMutation.ts b/packages/client/mutations/UpsertTeamPromptResponseMutation.ts index dc3aaadaa02..37dac5b9bba 100644 --- a/packages/client/mutations/UpsertTeamPromptResponseMutation.ts +++ b/packages/client/mutations/UpsertTeamPromptResponseMutation.ts @@ -4,6 +4,8 @@ import clientTempId from '~/utils/relay/clientTempId' import {UpsertTeamPromptResponseMutation_meeting$data} from '~/__generated__/UpsertTeamPromptResponseMutation_meeting.graphql' import {LocalHandlers, SharedUpdater, StandardMutation} from '../types/relayMutations' import {UpsertTeamPromptResponseMutation as TUpsertTeamPromptResponseMutation} from '../__generated__/UpsertTeamPromptResponseMutation.graphql' +import getReactji from '../utils/getReactji' +import SendClientSideEvent from '../utils/SendClientSideEvent' graphql` fragment UpsertTeamPromptResponseMutation_meeting on UpsertTeamPromptResponseSuccess { @@ -17,6 +19,12 @@ graphql` createdAt ...TeamPromptResponseEmojis_response } + addedKudoses { + receiverUser { + preferredName + } + emoji + } } ` @@ -97,7 +105,30 @@ const UpsertTeamPromptResponseMutation: StandardMutation< const payload = store.getRootField('upsertTeamPromptResponse') upsertTeamPromptResponseUpdater(payload as any, {atmosphere, store}) }, - onCompleted, + onCompleted: (res, errors) => { + const addedKudoses = res.upsertTeamPromptResponse.addedKudoses + if (addedKudoses?.length && addedKudoses[0]) { + const {unicode} = getReactji(addedKudoses[0].emoji) + atmosphere.eventEmitter.emit('addSnackbar', { + key: 'youGaveKudos', + message: `You gave kudos to ${addedKudoses + .map((kudos) => kudos.receiverUser.preferredName) + .join(', ')} ${unicode}`, + autoDismiss: 5, + onShow: () => { + SendClientSideEvent(atmosphere, 'Snackbar Viewed', { + snackbarType: 'kudosSent' + }) + }, + onManualDismiss: () => { + SendClientSideEvent(atmosphere, 'Snackbar Clicked', { + snackbarType: 'kudosSent' + }) + } + }) + } + onCompleted?.(res, errors) + }, onError }) } diff --git a/packages/client/mutations/toasts/mapResponseMentionedToToast.ts b/packages/client/mutations/toasts/mapResponseMentionedToToast.ts index 676f3ce0ec9..cc637b54ba8 100644 --- a/packages/client/mutations/toasts/mapResponseMentionedToToast.ts +++ b/packages/client/mutations/toasts/mapResponseMentionedToToast.ts @@ -3,6 +3,7 @@ import {Snack} from '../../components/Snackbar' import {OnNextHistoryContext} from '../../types/relayMutations' import {mapResponseMentionedToToast_notification$data} from '../../__generated__/mapResponseMentionedToToast_notification.graphql' import makeNotificationToastKey from './makeNotificationToastKey' +import getReactji from '../../utils/getReactji' graphql` fragment mapResponseMentionedToToast_notification on NotifyResponseMentioned { @@ -17,6 +18,7 @@ graphql` id name } + kudosEmoji } ` @@ -25,17 +27,23 @@ const mapResponseMentionedToToast = ( {history}: OnNextHistoryContext ): Snack | null => { if (!notification) return null - const {id: notificationId, meeting, response} = notification + const {id: notificationId, meeting, response, kudosEmoji} = notification const {preferredName: authorName} = response.user const {id: meetingId, name: meetingName} = meeting + const unicodeEmoji = kudosEmoji ? getReactji(kudosEmoji).unicode : '' + + const message = kudosEmoji + ? `${unicodeEmoji} ${authorName} mentioned you and gave kudos in their response in ${meetingName}.` + : `${authorName} mentioned you in their response in ${meetingName}.` + // :TODO: (jmtaber129): Check if we're already open to the relevant standup response discussion // thread, and do nothing if we are. return { key: makeNotificationToastKey(notificationId), autoDismiss: 10, - message: `${authorName} mentioned you in their response in ${meetingName}.`, + message, action: { label: 'See their response', callback: () => { diff --git a/packages/server/database/types/NotificationResponseMentioned.ts b/packages/server/database/types/NotificationResponseMentioned.ts index 3368b54babb..332671b4888 100644 --- a/packages/server/database/types/NotificationResponseMentioned.ts +++ b/packages/server/database/types/NotificationResponseMentioned.ts @@ -4,17 +4,20 @@ interface Input { responseId: string meetingId: string userId: string + kudosEmoji?: string | null } export default class NotificationResponseMentioned extends Notification { readonly type = 'RESPONSE_MENTIONED' responseId: string meetingId: string + kudosEmoji?: string | null constructor(input: Input) { - const {responseId, meetingId, userId} = input + const {responseId, meetingId, userId, kudosEmoji} = input super({userId, type: 'RESPONSE_MENTIONED'}) this.responseId = responseId this.meetingId = meetingId + this.kudosEmoji = kudosEmoji } } diff --git a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts index 14656a2b938..923239db11e 100644 --- a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts @@ -267,6 +267,7 @@ const getSlackMessageForNotification = async ( const responseId = notification.responseId const response = await dataLoader.get('teamPromptResponses').loadNonNull(responseId) const author = await dataLoader.get('users').loadNonNull(response.userId) + // TODO: if kudos added, change notification text return { responseId, title: `*${author.preferredName}* mentioned you in their response in *${meeting.name}*`, diff --git a/packages/server/graphql/public/mutations/helpers/__tests__/getKudosUserIdsFromJson.test.ts b/packages/server/graphql/public/mutations/helpers/__tests__/getKudosUserIdsFromJson.test.ts new file mode 100644 index 00000000000..3b81c3ef32d --- /dev/null +++ b/packages/server/graphql/public/mutations/helpers/__tests__/getKudosUserIdsFromJson.test.ts @@ -0,0 +1,198 @@ +import {getKudosUserIdsFromJson} from '../getKudosUserIdsFromJson' +import {JSONContent} from '@tiptap/core' + +describe('findMentionsByEmoji', () => { + let doc: JSONContent + + beforeAll(() => { + doc = { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Paragraph1' + } + ] + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Paragraph2' + } + ] + }, + { + type: 'paragraph' + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Paragraph from new line' + } + ] + }, + { + type: 'paragraph' + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Paragraph from new line' + }, + { + type: 'hardBreak' + }, + { + type: 'text', + text: 'and break in the same paragraph' + } + ] + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Just new line with mention ' + }, + { + type: 'mention', + attrs: { + id: 'user_id_1', + label: 'userone' + } + }, + { + type: 'text', + text: ' ❤️' + } + ] + }, + { + type: 'paragraph' + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: '❤️ From new line with mention ' + }, + { + type: 'mention', + attrs: { + id: 'user_id_2', + label: 'usertwo' + } + }, + { + type: 'text', + text: ' ' + }, + { + type: 'hardBreak' + }, + { + type: 'hardBreak' + }, + { + type: 'text', + text: 'This mention ' + }, + { + type: 'mention', + attrs: { + id: 'user_id_3', + label: 'userthree' + } + }, + { + type: 'text', + text: ' should not be included as no heart emoji in paragraph 🌮' + } + ] + }, + { + type: 'paragraph', + content: [ + { + type: 'mention', + attrs: { + id: 'user_id_supermention', + label: 'supermention' + } + }, + { + type: 'text', + text: ' 🌮' + } + ] + }, + { + type: 'paragraph' + }, + { + type: 'paragraph', + content: [ + { + type: 'mention', + attrs: { + id: 'user_id_4', + label: 'userone' + } + }, + { + type: 'text', + text: ' ' + }, + { + type: 'mention', + attrs: { + id: 'user_id_5', + label: 'userfour' + } + }, + { + type: 'text', + text: ' both mentioned ❤️' + } + ] + } + ] + } + }) + + it('returns correct mention user IDs for emoji ❤️', () => { + const emoji = '❤️' + const result = getKudosUserIdsFromJson(doc, emoji) + expect(result).toEqual(['user_id_1', 'user_id_2', 'user_id_4', 'user_id_5']) + }) + + it('returns correct mention user IDs for different emoji (🌮)', () => { + const emoji = '🌮' + const result = getKudosUserIdsFromJson(doc, emoji) + expect(result).toEqual(['user_id_3', 'user_id_supermention']) + }) + + it('returns an empty array for an emoji with no mentions (🔥)', () => { + const emoji = '🔥' + const result = getKudosUserIdsFromJson(doc, emoji) + expect(result).toEqual([]) + }) + + it('does not include duplicate IDs', () => { + const emoji = '❤️' + const result = getKudosUserIdsFromJson(doc, emoji) + const uniqueResult = Array.from(new Set(result)) + expect(result).toEqual(uniqueResult) + }) +}) diff --git a/packages/server/graphql/public/mutations/helpers/getKudosUserIdsFromJson.ts b/packages/server/graphql/public/mutations/helpers/getKudosUserIdsFromJson.ts new file mode 100644 index 00000000000..42e9d08fe41 --- /dev/null +++ b/packages/server/graphql/public/mutations/helpers/getKudosUserIdsFromJson.ts @@ -0,0 +1,29 @@ +import {JSONContent} from '@tiptap/core' + +export const getKudosUserIdsFromJson = (doc: JSONContent, emoji: string): string[] => { + const mentionedIds = new Set() + + if (!doc.content) return [] + for (const paragraph of doc.content) { + if (paragraph.content) { + let emojiFound = false + const tempMentions = new Set() + + for (const node of paragraph.content) { + if (node.type === 'text' && node.text?.includes(emoji)) { + emojiFound = true + } + + if (node.type === 'mention') { + tempMentions.add(node.attrs?.id) + } + } + + if (emojiFound) { + tempMentions.forEach((id) => mentionedIds.add(id)) + } + } + } + + return Array.from(mentionedIds) +} diff --git a/packages/server/graphql/public/mutations/helpers/publishTeamPromptMentions.ts b/packages/server/graphql/public/mutations/helpers/publishTeamPromptMentions.ts index 8c43d1a37b3..2a890965d8e 100644 --- a/packages/server/graphql/public/mutations/helpers/publishTeamPromptMentions.ts +++ b/packages/server/graphql/public/mutations/helpers/publishTeamPromptMentions.ts @@ -17,17 +17,29 @@ const getMentionedUserIdsFromContent = (content: JSONContent): string[] => { const createTeamPromptMentionNotifications = async ( oldResponse: TeamPromptResponse | undefined, - newResponse: TeamPromptResponse + newResponse: TeamPromptResponse, + addedKudoses: + | { + id: number + emoji: string | null + receiverUserId: string + }[] + | null ) => { // Get mentions from previous and new content. const newResponseMentions = getMentionedUserIdsFromContent(newResponse.content) const oldResponseMentions = oldResponse ? getMentionedUserIdsFromContent(oldResponse.content) : [] + const addedKudosesUserIds = addedKudoses?.map((kudos) => kudos.receiverUserId) ?? [] + // Create notifications that should be added. const addedMentions = Array.from( new Set( newResponseMentions.filter( - (mention) => !oldResponseMentions.includes(mention) && newResponse.userId !== mention + (mention) => + (!oldResponseMentions.includes(mention) && newResponse.userId !== mention) || + // Send mention notification anyway in case it is also include kudos + addedKudosesUserIds.includes(mention) ) ) ) @@ -41,7 +53,8 @@ const createTeamPromptMentionNotifications = async ( new NotificationResponseMentioned({ userId: mention, responseId: newResponse.id, - meetingId: newResponse.meetingId + meetingId: newResponse.meetingId, + kudosEmoji: addedKudoses?.find((kudos) => kudos.receiverUserId === mention)?.emoji }) ) diff --git a/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts b/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts index fda331b6839..1dd7a6d7f55 100644 --- a/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts +++ b/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts @@ -12,12 +12,15 @@ import {MutationResolvers} from '../resolverTypes' import publishNotification from './helpers/publishNotification' import createTeamPromptMentionNotifications from './helpers/publishTeamPromptMentions' import {IntegrationNotifier} from '../../mutations/helpers/notifications/IntegrationNotifier' +import {getKudosUserIdsFromJson} from './helpers/getKudosUserIdsFromJson' +import getKysely from '../../../postgres/getKysely' const upsertTeamPromptResponse: MutationResolvers['upsertTeamPromptResponse'] = async ( _source, {teamPromptResponseId: inputTeamPromptResponseId, meetingId, content}, {authToken, dataLoader, socketId: mutatorId} ) => { + const pg = getKysely() const viewerId = getUserId(authToken) const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -75,6 +78,48 @@ const upsertTeamPromptResponse: MutationResolvers['upsertTeamPromptResponse'] = }) ) + const team = await dataLoader.get('teams').loadNonNull(teamId) + // TODO: don't hardcode, convert + const kudosEmoji = '❤️' + + let kudosUserIds = null + if (team.giveKudosWithEmoji && kudosEmoji) { + const oldKudosUserIds = oldTeamPromptResponse + ? getKudosUserIdsFromJson(oldTeamPromptResponse.content, kudosEmoji) + : [] + console.log('oldKudosUserIds', oldKudosUserIds) + const newKudosUserIds = getKudosUserIdsFromJson(contentJSON, kudosEmoji) + console.log('newKudosUserIds', newKudosUserIds) + kudosUserIds = newKudosUserIds.filter( + (userId) => !oldKudosUserIds.includes(userId) && userId !== viewerId + ) + console.log('difference', kudosUserIds) + } + + let insertedKudoses = null + if (kudosUserIds?.length) { + const kudosRows = kudosUserIds.map((userId) => ({ + senderUserId: viewerId, + receiverUserId: userId, + teamId, + emoji: team.kudosEmoji + // TODO: lets store unicode emoji everywhere to avoid conversions everytime + // TODO: Lets link it to teamPromptResponseId + })) + + insertedKudoses = await pg + .insertInto('Kudos') + .values(kudosRows) + .returning(['id', 'receiverUserId', 'emoji']) + .execute() + console.log('insertedKudoses', insertedKudoses) + + insertedKudoses.forEach((kudos) => { + console.log('analytis', kudos.id, kudos.receiverUserId) + analytics.kudosSent(viewerId, teamId, kudos.id, kudos.receiverUserId) + }) + } + dataLoader.get('teamPromptResponses').clear(teamPromptResponseId) const newTeamPromptResponse = await dataLoader @@ -83,13 +128,15 @@ const upsertTeamPromptResponse: MutationResolvers['upsertTeamPromptResponse'] = const notifications = await createTeamPromptMentionNotifications( oldTeamPromptResponse, - newTeamPromptResponse + newTeamPromptResponse, + insertedKudoses ) const data = { meetingId, teamPromptResponseId, - addedNotificationIds: notifications.map((notification) => notification.id) + addedNotificationIds: notifications.map((notification) => notification.id), + addedKudosesIds: insertedKudoses?.map((row) => row.id) } notifications.forEach((notification) => { diff --git a/packages/server/graphql/public/typeDefs/NotifyResponseMentioned.graphql b/packages/server/graphql/public/typeDefs/NotifyResponseMentioned.graphql index cf2bb5e4d5b..ed56a262eaf 100644 --- a/packages/server/graphql/public/typeDefs/NotifyResponseMentioned.graphql +++ b/packages/server/graphql/public/typeDefs/NotifyResponseMentioned.graphql @@ -39,4 +39,9 @@ type NotifyResponseMentioned implements Notification { The meeting the user was mentioned in. """ meeting: TeamPromptMeeting! + + """ + Kudos emoji if mention includes kudos + """ + kudosEmoji: String } diff --git a/packages/server/graphql/public/typeDefs/upsertTeamPromptResponse.graphql b/packages/server/graphql/public/typeDefs/upsertTeamPromptResponse.graphql index 7681dc4cd46..d0cbd048545 100644 --- a/packages/server/graphql/public/typeDefs/upsertTeamPromptResponse.graphql +++ b/packages/server/graphql/public/typeDefs/upsertTeamPromptResponse.graphql @@ -32,6 +32,11 @@ type UpsertTeamPromptResponseSuccess { the updated meeting """ meeting: NewMeeting + + """ + Kudos added with the response + """ + addedKudoses: [Kudos!] } union UpsertTeamPromptResponsePayload = UpsertTeamPromptResponseSuccess | ErrorPayload diff --git a/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts b/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts index cf51634ce03..beebfaa4dfc 100644 --- a/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts +++ b/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts @@ -3,6 +3,7 @@ import {UpsertTeamPromptResponseSuccessResolvers} from '../resolverTypes' export type UpsertTeamPromptResponseSuccessSource = { teamPromptResponseId: string meetingId: string + addedKudosesIds?: number[] } const UpsertTeamPromptResponseSuccess: UpsertTeamPromptResponseSuccessResolvers = { @@ -13,6 +14,14 @@ const UpsertTeamPromptResponseSuccess: UpsertTeamPromptResponseSuccessResolvers meeting: async (source, _args, {dataLoader}) => { const {meetingId} = source return dataLoader.get('newMeetings').load(meetingId) + }, + addedKudoses: async (source, _args, {dataLoader}) => { + const {addedKudosesIds} = source + if (!addedKudosesIds) { + return null + } + + return dataLoader.get('kudoses').loadMany(addedKudosesIds) } } diff --git a/packages/server/utils/getReactji.ts b/packages/server/utils/getReactji.ts new file mode 100644 index 00000000000..c49d3b8de1e --- /dev/null +++ b/packages/server/utils/getReactji.ts @@ -0,0 +1,15 @@ +// import appleEmojis from 'emoji-mart/data/apple.json' +// import {uncompress} from 'emoji-mart/dist-modern/utils/data.js' +// import {unifiedToNative} from 'emoji-mart/dist-modern/utils/index.js' + +// uncompress(appleEmojis) + +// const getReactji = (emoji: string) => { +// const value = appleEmojis.emojis[emoji as keyof typeof appleEmojis.emojis] as any +// return { +// unicode: unifiedToNative(value.unified) || '', +// shortName: value.short_names[0] ?? '' +// } +// } + +// export default getReactji From 8a1f3a09e1d3d633bc74016deb9134da348eea7e Mon Sep 17 00:00:00 2001 From: Igor Lesnenko Date: Fri, 1 Dec 2023 17:53:18 +0400 Subject: [PATCH 02/11] Remove console logs --- .../graphql/public/mutations/upsertTeamPromptResponse.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts b/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts index 1dd7a6d7f55..d44fc3915b0 100644 --- a/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts +++ b/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts @@ -87,13 +87,10 @@ const upsertTeamPromptResponse: MutationResolvers['upsertTeamPromptResponse'] = const oldKudosUserIds = oldTeamPromptResponse ? getKudosUserIdsFromJson(oldTeamPromptResponse.content, kudosEmoji) : [] - console.log('oldKudosUserIds', oldKudosUserIds) const newKudosUserIds = getKudosUserIdsFromJson(contentJSON, kudosEmoji) - console.log('newKudosUserIds', newKudosUserIds) kudosUserIds = newKudosUserIds.filter( (userId) => !oldKudosUserIds.includes(userId) && userId !== viewerId ) - console.log('difference', kudosUserIds) } let insertedKudoses = null @@ -112,10 +109,8 @@ const upsertTeamPromptResponse: MutationResolvers['upsertTeamPromptResponse'] = .values(kudosRows) .returning(['id', 'receiverUserId', 'emoji']) .execute() - console.log('insertedKudoses', insertedKudoses) insertedKudoses.forEach((kudos) => { - console.log('analytis', kudos.id, kudos.receiverUserId) analytics.kudosSent(viewerId, teamId, kudos.id, kudos.receiverUserId) }) } From b0520eb0abcdd887edd28a26bfaf1dc07200ec0b Mon Sep 17 00:00:00 2001 From: Igor Lesnenko Date: Mon, 4 Dec 2023 12:18:28 +0400 Subject: [PATCH 03/11] Fix test --- .../__tests__/getKudosUserIdsFromJson.test.ts | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/packages/server/graphql/public/mutations/helpers/__tests__/getKudosUserIdsFromJson.test.ts b/packages/server/graphql/public/mutations/helpers/__tests__/getKudosUserIdsFromJson.test.ts index 3b81c3ef32d..33f70740766 100644 --- a/packages/server/graphql/public/mutations/helpers/__tests__/getKudosUserIdsFromJson.test.ts +++ b/packages/server/graphql/public/mutations/helpers/__tests__/getKudosUserIdsFromJson.test.ts @@ -85,7 +85,7 @@ describe('findMentionsByEmoji', () => { content: [ { type: 'text', - text: '❤️ From new line with mention ' + text: '❤️ Another mentions ' }, { type: 'mention', @@ -94,30 +94,15 @@ describe('findMentionsByEmoji', () => { label: 'usertwo' } }, - { - type: 'text', - text: ' ' - }, - { - type: 'hardBreak' - }, { type: 'hardBreak' }, - { - type: 'text', - text: 'This mention ' - }, { type: 'mention', attrs: { id: 'user_id_3', label: 'userthree' } - }, - { - type: 'text', - text: ' should not be included as no heart emoji in paragraph 🌮' } ] }, @@ -174,13 +159,13 @@ describe('findMentionsByEmoji', () => { it('returns correct mention user IDs for emoji ❤️', () => { const emoji = '❤️' const result = getKudosUserIdsFromJson(doc, emoji) - expect(result).toEqual(['user_id_1', 'user_id_2', 'user_id_4', 'user_id_5']) + expect(result).toEqual(['user_id_1', 'user_id_2', 'user_id_3', 'user_id_4', 'user_id_5']) }) it('returns correct mention user IDs for different emoji (🌮)', () => { const emoji = '🌮' const result = getKudosUserIdsFromJson(doc, emoji) - expect(result).toEqual(['user_id_3', 'user_id_supermention']) + expect(result).toEqual(['user_id_supermention']) }) it('returns an empty array for an emoji with no mentions (🔥)', () => { From 9eaeb3ba9f21d20cc2675e1db6332f92d40cca37 Mon Sep 17 00:00:00 2001 From: Igor Lesnenko Date: Mon, 4 Dec 2023 15:14:08 +0400 Subject: [PATCH 04/11] Store unicode emoji too --- .../components/KudosReceivedNotification.tsx | 9 ++--- .../client/components/ResponseMentioned.tsx | 11 +++--- .../AddReactjiToReactableMutation.ts | 7 ++-- .../UpsertTeamPromptResponseMutation.ts | 7 ++-- .../toasts/mapKudosReceivedToToast.ts | 8 ++--- .../types/NotificationKudosReceived.ts | 5 ++- .../types/NotificationResponseMentioned.ts | 5 ++- .../public/mutations/addReactjiToReactable.ts | 4 ++- .../helpers/publishTeamPromptMentions.ts | 20 ++++++----- .../mutations/upsertTeamPromptResponse.ts | 15 ++++---- .../graphql/public/typeDefs/Kudos.graphql | 5 +++ .../typeDefs/NotifyKudosReceived.graphql | 5 +++ .../typeDefs/NotifyResponseMentioned.graphql | 5 +++ .../public/types/NotifyKudosReceived.ts | 3 +- ...1701679264301_storeUnicodeEmojiForKudos.ts | 34 +++++++++++++++++++ packages/server/utils/getReactji.ts | 15 -------- 16 files changed, 96 insertions(+), 62 deletions(-) create mode 100644 packages/server/postgres/migrations/1701679264301_storeUnicodeEmojiForKudos.ts delete mode 100644 packages/server/utils/getReactji.ts diff --git a/packages/client/components/KudosReceivedNotification.tsx b/packages/client/components/KudosReceivedNotification.tsx index bf3e96ba0b7..b0dc13e4168 100644 --- a/packages/client/components/KudosReceivedNotification.tsx +++ b/packages/client/components/KudosReceivedNotification.tsx @@ -6,7 +6,6 @@ import {KudosReceivedNotification_notification$key} from '~/__generated__/KudosR 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 @@ -25,15 +24,13 @@ const KudosReceivedNotification = (props: Props) => { picture meetingName meetingId - emoji + emojiUnicode status } `, notificationRef ) - const {type, name, picture, meetingName, emoji, meetingId, status} = notification - - const {unicode} = getReactji(emoji) + const {type, name, picture, meetingName, emojiUnicode, meetingId, status} = notification useEffect(() => { SendClientSideEvent(atmosphere, 'Notification Viewed', { @@ -46,7 +43,7 @@ const KudosReceivedNotification = (props: Props) => { - {unicode} {name} gave you kudos in{' '} + {emojiUnicode} {name} gave you kudos in{' '} {meetingName} diff --git a/packages/client/components/ResponseMentioned.tsx b/packages/client/components/ResponseMentioned.tsx index 2e0efdcc792..6fcf818c5cb 100644 --- a/packages/client/components/ResponseMentioned.tsx +++ b/packages/client/components/ResponseMentioned.tsx @@ -5,7 +5,6 @@ import NotificationAction from '~/components/NotificationAction' import useRouter from '../hooks/useRouter' import {ResponseMentioned_notification$key} from '../__generated__/ResponseMentioned_notification.graphql' import NotificationTemplate from './NotificationTemplate' -import getReactji from '../utils/getReactji' interface Props { notification: ResponseMentioned_notification$key @@ -28,13 +27,13 @@ const ResponseMentioned = (props: Props) => { id name } - kudosEmoji + kudosEmojiUnicode } `, notificationRef ) const {history} = useRouter() - const {meeting, response, kudosEmoji} = notification + const {meeting, response, kudosEmojiUnicode} = notification const {picture: authorPicture, preferredName: authorName} = response.user const {id: meetingId, name: meetingName} = meeting @@ -42,10 +41,8 @@ const ResponseMentioned = (props: Props) => { history.push(`/meet/${meetingId}/responses?responseId=${encodeURIComponent(response.id)}`) } - const unicodeEmoji = kudosEmoji ? getReactji(kudosEmoji).unicode : '' - - const message = kudosEmoji - ? `${unicodeEmoji} ${authorName} mentioned you and gave kudos in their response in ${meetingName}.` + const message = kudosEmojiUnicode + ? `${kudosEmojiUnicode} ${authorName} mentioned you and gave kudos in their response in ${meetingName}.` : `${authorName} mentioned you in their response in ${meetingName}.` // :TODO: (jmtaber129): Show mention preview. diff --git a/packages/client/mutations/AddReactjiToReactableMutation.ts b/packages/client/mutations/AddReactjiToReactableMutation.ts index 160212be5aa..51ee83cfe0c 100644 --- a/packages/client/mutations/AddReactjiToReactableMutation.ts +++ b/packages/client/mutations/AddReactjiToReactableMutation.ts @@ -3,7 +3,6 @@ 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' import SendClientSideEvent from '../utils/SendClientSideEvent' graphql` @@ -41,7 +40,7 @@ const mutation = graphql` } ... on AddReactjiToReactableSuccess { addedKudos { - emoji + emojiUnicode receiverUser { preferredName } @@ -118,10 +117,10 @@ const AddReactjiToReactableMutation: StandardMutation { SendClientSideEvent(atmosphere, 'Snackbar Viewed', { diff --git a/packages/client/mutations/UpsertTeamPromptResponseMutation.ts b/packages/client/mutations/UpsertTeamPromptResponseMutation.ts index 37dac5b9bba..46e757d24a1 100644 --- a/packages/client/mutations/UpsertTeamPromptResponseMutation.ts +++ b/packages/client/mutations/UpsertTeamPromptResponseMutation.ts @@ -4,7 +4,6 @@ import clientTempId from '~/utils/relay/clientTempId' import {UpsertTeamPromptResponseMutation_meeting$data} from '~/__generated__/UpsertTeamPromptResponseMutation_meeting.graphql' import {LocalHandlers, SharedUpdater, StandardMutation} from '../types/relayMutations' import {UpsertTeamPromptResponseMutation as TUpsertTeamPromptResponseMutation} from '../__generated__/UpsertTeamPromptResponseMutation.graphql' -import getReactji from '../utils/getReactji' import SendClientSideEvent from '../utils/SendClientSideEvent' graphql` @@ -23,7 +22,7 @@ graphql` receiverUser { preferredName } - emoji + emojiUnicode } } ` @@ -108,12 +107,12 @@ const UpsertTeamPromptResponseMutation: StandardMutation< onCompleted: (res, errors) => { const addedKudoses = res.upsertTeamPromptResponse.addedKudoses if (addedKudoses?.length && addedKudoses[0]) { - const {unicode} = getReactji(addedKudoses[0].emoji) + const {emojiUnicode} = addedKudoses[0] atmosphere.eventEmitter.emit('addSnackbar', { key: 'youGaveKudos', message: `You gave kudos to ${addedKudoses .map((kudos) => kudos.receiverUser.preferredName) - .join(', ')} ${unicode}`, + .join(', ')} ${emojiUnicode}`, autoDismiss: 5, onShow: () => { SendClientSideEvent(atmosphere, 'Snackbar Viewed', { diff --git a/packages/client/mutations/toasts/mapKudosReceivedToToast.ts b/packages/client/mutations/toasts/mapKudosReceivedToToast.ts index d3660d1b4ec..59e060a237f 100644 --- a/packages/client/mutations/toasts/mapKudosReceivedToToast.ts +++ b/packages/client/mutations/toasts/mapKudosReceivedToToast.ts @@ -4,7 +4,6 @@ import {mapKudosReceivedToToast_notification$data} from '../../__generated__/map 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 { @@ -12,7 +11,7 @@ graphql` name meetingName meetingId - emoji + emojiUnicode } ` @@ -20,13 +19,12 @@ const mapKudosReceivedToToast = ( notification: mapKudosReceivedToToast_notification$data, {atmosphere, history}: OnNextHistoryContext ): Snack => { - const {id: notificationId, meetingName, name, emoji, meetingId} = notification - const {unicode} = getReactji(emoji) + const {id: notificationId, meetingName, name, emojiUnicode, meetingId} = notification return { autoDismiss: 5, showDismissButton: true, key: makeNotificationToastKey(notificationId), - message: `${unicode} ${name} gave you kudos in`, + message: `${emojiUnicode} ${name} gave you kudos in`, action: { label: meetingName, callback: () => { diff --git a/packages/server/database/types/NotificationKudosReceived.ts b/packages/server/database/types/NotificationKudosReceived.ts index 9cdeef4a6b2..b8f8df1806b 100644 --- a/packages/server/database/types/NotificationKudosReceived.ts +++ b/packages/server/database/types/NotificationKudosReceived.ts @@ -8,6 +8,7 @@ interface Input { meetingName: string meetingId: string emoji: string + emojiUnicode: string } export default class NotificationKudosReceived extends Notification { @@ -18,9 +19,10 @@ export default class NotificationKudosReceived extends Notification { meetingName: string meetingId: string emoji: string + emojiUnicode: string constructor(input: Input) { - const {userId, name, picture, senderUserId, meetingName, meetingId, emoji} = input + const {userId, name, picture, senderUserId, meetingName, meetingId, emoji, emojiUnicode} = input super({userId, type: 'KUDOS_RECEIVED'}) this.name = name this.picture = picture @@ -28,5 +30,6 @@ export default class NotificationKudosReceived extends Notification { this.meetingName = meetingName this.meetingId = meetingId this.emoji = emoji + this.emojiUnicode = emojiUnicode } } diff --git a/packages/server/database/types/NotificationResponseMentioned.ts b/packages/server/database/types/NotificationResponseMentioned.ts index 332671b4888..562f40d1321 100644 --- a/packages/server/database/types/NotificationResponseMentioned.ts +++ b/packages/server/database/types/NotificationResponseMentioned.ts @@ -5,6 +5,7 @@ interface Input { meetingId: string userId: string kudosEmoji?: string | null + kudosEmojiUnicode?: string | null } export default class NotificationResponseMentioned extends Notification { @@ -12,12 +13,14 @@ export default class NotificationResponseMentioned extends Notification { responseId: string meetingId: string kudosEmoji?: string | null + kudosEmojiUnicode?: string | null constructor(input: Input) { - const {responseId, meetingId, userId, kudosEmoji} = input + const {responseId, meetingId, userId, kudosEmoji, kudosEmojiUnicode} = input super({userId, type: 'RESPONSE_MENTIONED'}) this.responseId = responseId this.meetingId = meetingId this.kudosEmoji = kudosEmoji + this.kudosEmojiUnicode = kudosEmojiUnicode } } diff --git a/packages/server/graphql/public/mutations/addReactjiToReactable.ts b/packages/server/graphql/public/mutations/addReactjiToReactable.ts index 75f3fd842d1..88820095247 100644 --- a/packages/server/graphql/public/mutations/addReactjiToReactable.ts +++ b/packages/server/graphql/public/mutations/addReactjiToReactable.ts @@ -187,7 +187,8 @@ const addReactjiToReactable: MutationResolvers['addReactjiToReactable'] = async reactableType: reactableType, reactableId: reactableId, teamId, - emoji: team.kudosEmoji + emoji: team.kudosEmoji, + emojiUnicode: team.kudosEmojiUnicode }) .returning('id') .executeTakeFirst())!.id @@ -200,6 +201,7 @@ const addReactjiToReactable: MutationResolvers['addReactjiToReactable'] = async meetingId, meetingName: meeting.name, emoji: team.kudosEmoji, + emojiUnicode: team.kudosEmojiUnicode, name: senderUser.preferredName, picture: senderUser.picture }) diff --git a/packages/server/graphql/public/mutations/helpers/publishTeamPromptMentions.ts b/packages/server/graphql/public/mutations/helpers/publishTeamPromptMentions.ts index 2a890965d8e..b48fd4e37e7 100644 --- a/packages/server/graphql/public/mutations/helpers/publishTeamPromptMentions.ts +++ b/packages/server/graphql/public/mutations/helpers/publishTeamPromptMentions.ts @@ -22,6 +22,7 @@ const createTeamPromptMentionNotifications = async ( | { id: number emoji: string | null + emojiUnicode: string | null receiverUserId: string }[] | null @@ -48,15 +49,16 @@ const createTeamPromptMentionNotifications = async ( return [] } - const notificationsToAdd = addedMentions.map( - (mention) => - new NotificationResponseMentioned({ - userId: mention, - responseId: newResponse.id, - meetingId: newResponse.meetingId, - kudosEmoji: addedKudoses?.find((kudos) => kudos.receiverUserId === mention)?.emoji - }) - ) + const notificationsToAdd = addedMentions.map((mention) => { + const kudos = addedKudoses?.find((kudos) => kudos.receiverUserId === mention) + return new NotificationResponseMentioned({ + userId: mention, + responseId: newResponse.id, + meetingId: newResponse.meetingId, + kudosEmoji: kudos?.emoji, + kudosEmojiUnicode: kudos?.emojiUnicode + }) + }) const r = await getRethink() await r.table('Notification').insert(notificationsToAdd).run() diff --git a/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts b/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts index d44fc3915b0..693efb80158 100644 --- a/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts +++ b/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts @@ -79,15 +79,14 @@ const upsertTeamPromptResponse: MutationResolvers['upsertTeamPromptResponse'] = ) const team = await dataLoader.get('teams').loadNonNull(teamId) - // TODO: don't hardcode, convert - const kudosEmoji = '❤️' + const {kudosEmoji, kudosEmojiUnicode} = team let kudosUserIds = null - if (team.giveKudosWithEmoji && kudosEmoji) { + if (team.giveKudosWithEmoji && kudosEmojiUnicode) { const oldKudosUserIds = oldTeamPromptResponse - ? getKudosUserIdsFromJson(oldTeamPromptResponse.content, kudosEmoji) + ? getKudosUserIdsFromJson(oldTeamPromptResponse.content, kudosEmojiUnicode) : [] - const newKudosUserIds = getKudosUserIdsFromJson(contentJSON, kudosEmoji) + const newKudosUserIds = getKudosUserIdsFromJson(contentJSON, kudosEmojiUnicode) kudosUserIds = newKudosUserIds.filter( (userId) => !oldKudosUserIds.includes(userId) && userId !== viewerId ) @@ -99,15 +98,15 @@ const upsertTeamPromptResponse: MutationResolvers['upsertTeamPromptResponse'] = senderUserId: viewerId, receiverUserId: userId, teamId, - emoji: team.kudosEmoji - // TODO: lets store unicode emoji everywhere to avoid conversions everytime + emoji: kudosEmoji, + emojiUnicode: kudosEmojiUnicode // TODO: Lets link it to teamPromptResponseId })) insertedKudoses = await pg .insertInto('Kudos') .values(kudosRows) - .returning(['id', 'receiverUserId', 'emoji']) + .returning(['id', 'receiverUserId', 'emoji', 'emojiUnicode']) .execute() insertedKudoses.forEach((kudos) => { diff --git a/packages/server/graphql/public/typeDefs/Kudos.graphql b/packages/server/graphql/public/typeDefs/Kudos.graphql index 80ea9f5385c..89f72c7805e 100644 --- a/packages/server/graphql/public/typeDefs/Kudos.graphql +++ b/packages/server/graphql/public/typeDefs/Kudos.graphql @@ -21,4 +21,9 @@ type Kudos { emoji name """ emoji: String! + + """ + emoji unicode character + """ + emojiUnicode: String! } diff --git a/packages/server/graphql/public/typeDefs/NotifyKudosReceived.graphql b/packages/server/graphql/public/typeDefs/NotifyKudosReceived.graphql index 71035ba3289..b1df328ef79 100644 --- a/packages/server/graphql/public/typeDefs/NotifyKudosReceived.graphql +++ b/packages/server/graphql/public/typeDefs/NotifyKudosReceived.graphql @@ -44,4 +44,9 @@ type NotifyKudosReceived implements Notification { Kudos emoji """ emoji: String! + + """ + Kudos emoji unicode + """ + emojiUnicode: String! } diff --git a/packages/server/graphql/public/typeDefs/NotifyResponseMentioned.graphql b/packages/server/graphql/public/typeDefs/NotifyResponseMentioned.graphql index ed56a262eaf..6af90db36ef 100644 --- a/packages/server/graphql/public/typeDefs/NotifyResponseMentioned.graphql +++ b/packages/server/graphql/public/typeDefs/NotifyResponseMentioned.graphql @@ -44,4 +44,9 @@ type NotifyResponseMentioned implements Notification { Kudos emoji if mention includes kudos """ kudosEmoji: String + + """ + kudos emoji unicode character + """ + kudosEmojiUnicode: String } diff --git a/packages/server/graphql/public/types/NotifyKudosReceived.ts b/packages/server/graphql/public/types/NotifyKudosReceived.ts index cbf2232a7bb..a26a1ff78a8 100644 --- a/packages/server/graphql/public/types/NotifyKudosReceived.ts +++ b/packages/server/graphql/public/types/NotifyKudosReceived.ts @@ -1,7 +1,8 @@ import {NotifyKudosReceivedResolvers} from '../resolverTypes' const NotifyKudosReceived: NotifyKudosReceivedResolvers = { - __isTypeOf: ({type}) => type === 'KUDOS_RECEIVED' + __isTypeOf: ({type}) => type === 'KUDOS_RECEIVED', + emojiUnicode: ({emojiUnicode}) => emojiUnicode ?? '❤️' } export default NotifyKudosReceived diff --git a/packages/server/postgres/migrations/1701679264301_storeUnicodeEmojiForKudos.ts b/packages/server/postgres/migrations/1701679264301_storeUnicodeEmojiForKudos.ts new file mode 100644 index 00000000000..e0710e27e60 --- /dev/null +++ b/packages/server/postgres/migrations/1701679264301_storeUnicodeEmojiForKudos.ts @@ -0,0 +1,34 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DO $$ + BEGIN + ALTER TABLE "Team" + ADD COLUMN IF NOT EXISTS "kudosEmojiUnicode" VARCHAR(100) NOT NULL DEFAULT '❤️'; + ALTER TABLE "Kudos" + ADD COLUMN IF NOT EXISTS "emojiUnicode" VARCHAR(100) NOT NULL DEFAULT '❤️'; + END + $$; + `) + await client.end() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DO $$ + BEGIN + ALTER TABLE "Team" + DROP COLUMN "kudosEmojiUnicode"; + ALTER TABLE "Kudos" + DROP COLUMN "emojiUnicode"; + END + $$; + `) + await client.end() +} diff --git a/packages/server/utils/getReactji.ts b/packages/server/utils/getReactji.ts deleted file mode 100644 index c49d3b8de1e..00000000000 --- a/packages/server/utils/getReactji.ts +++ /dev/null @@ -1,15 +0,0 @@ -// import appleEmojis from 'emoji-mart/data/apple.json' -// import {uncompress} from 'emoji-mart/dist-modern/utils/data.js' -// import {unifiedToNative} from 'emoji-mart/dist-modern/utils/index.js' - -// uncompress(appleEmojis) - -// const getReactji = (emoji: string) => { -// const value = appleEmojis.emojis[emoji as keyof typeof appleEmojis.emojis] as any -// return { -// unicode: unifiedToNative(value.unified) || '', -// shortName: value.short_names[0] ?? '' -// } -// } - -// export default getReactji From c69445d981ab11e89bf745af8e96d87a5580f7b7 Mon Sep 17 00:00:00 2001 From: Igor Lesnenko Date: Tue, 5 Dec 2023 14:54:44 +0400 Subject: [PATCH 05/11] Link teamPromptResponseId --- .../mutations/upsertTeamPromptResponse.ts | 4 +-- ...1701769672206_kudosTeamPromptResponseId.ts | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 packages/server/postgres/migrations/1701769672206_kudosTeamPromptResponseId.ts diff --git a/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts b/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts index 693efb80158..92a1b226b28 100644 --- a/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts +++ b/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts @@ -99,8 +99,8 @@ const upsertTeamPromptResponse: MutationResolvers['upsertTeamPromptResponse'] = receiverUserId: userId, teamId, emoji: kudosEmoji, - emojiUnicode: kudosEmojiUnicode - // TODO: Lets link it to teamPromptResponseId + emojiUnicode: kudosEmojiUnicode, + teamPromptResponseId: TeamPromptResponseId.split(teamPromptResponseId) })) insertedKudoses = await pg diff --git a/packages/server/postgres/migrations/1701769672206_kudosTeamPromptResponseId.ts b/packages/server/postgres/migrations/1701769672206_kudosTeamPromptResponseId.ts new file mode 100644 index 00000000000..dc2cb4e381a --- /dev/null +++ b/packages/server/postgres/migrations/1701769672206_kudosTeamPromptResponseId.ts @@ -0,0 +1,29 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + ALTER TABLE "Kudos" + DROP CONSTRAINT IF EXISTS "fk_teamPromptResponseId"; + + ALTER TABLE "Kudos" + ADD COLUMN IF NOT EXISTS "teamPromptResponseId" INT, + ADD CONSTRAINT "fk_teamPromptResponseId" + FOREIGN KEY("teamPromptResponseId") + REFERENCES "TeamPromptResponse"("id") + ON DELETE CASCADE; + `) + await client.end() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + ALTER TABLE "Kudos" + DROP COLUMN IF EXISTS "teamPromptResponseId"; + `) + await client.end() +} From e09969de3a4374db8ad73c7f50ea734a959bb80d Mon Sep 17 00:00:00 2001 From: Igor Lesnenko Date: Tue, 5 Dec 2023 16:01:02 +0400 Subject: [PATCH 06/11] Update slack notification --- .../mutations/helpers/notifications/SlackNotifier.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts index 923239db11e..7a3f6b70ee9 100644 --- a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts @@ -267,10 +267,12 @@ const getSlackMessageForNotification = async ( const responseId = notification.responseId const response = await dataLoader.get('teamPromptResponses').loadNonNull(responseId) const author = await dataLoader.get('users').loadNonNull(response.userId) - // TODO: if kudos added, change notification text + const title = notification.kudosEmojiUnicode + ? `${notification.kudosEmojiUnicode} *${author.preferredName}* mentioned you and gave kudos in their response in *${meeting.name}*` + : `*${author.preferredName}* mentioned you in their response in *${meeting.name}*` return { responseId, - title: `*${author.preferredName}* mentioned you in their response in *${meeting.name}*`, + title, buttonText: 'See their response' } } From 92601b970ce3451f3e740ef38026a528c599b617 Mon Sep 17 00:00:00 2001 From: Igor Lesnenko Date: Tue, 5 Dec 2023 20:19:43 +0400 Subject: [PATCH 07/11] Update email notification --- .../EmailNotifications/EmailResponseMentioned.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/client/modules/email/components/EmailNotifications/EmailResponseMentioned.tsx b/packages/client/modules/email/components/EmailNotifications/EmailResponseMentioned.tsx index d3deeafd58b..dec6b3acf0c 100644 --- a/packages/client/modules/email/components/EmailNotifications/EmailResponseMentioned.tsx +++ b/packages/client/modules/email/components/EmailNotifications/EmailResponseMentioned.tsx @@ -28,12 +28,12 @@ const EmailResponseMentioned = (props: Props) => { id name } - kudosEmoji + kudosEmojiUnicode } `, notificationRef ) - const {meeting, response} = notification + const {meeting, response, kudosEmojiUnicode} = notification const {rasterPicture: authorPicture, preferredName: authorName} = response.user const {id: meetingId, name: meetingName} = meeting @@ -47,11 +47,15 @@ const EmailResponseMentioned = (props: Props) => { } }) + const message = kudosEmojiUnicode + ? `${kudosEmojiUnicode} ${authorName} mentioned you and gave kudos in their response in ${meetingName}.` + : `${authorName} mentioned you in their response in ${meetingName}.` + // :TODO: (jmtaber129): Show mention preview. return ( Date: Wed, 6 Dec 2023 15:39:37 +0400 Subject: [PATCH 08/11] Mention notification analytics --- .../client/components/ResponseMentioned.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/client/components/ResponseMentioned.tsx b/packages/client/components/ResponseMentioned.tsx index 6fcf818c5cb..e948edc9388 100644 --- a/packages/client/components/ResponseMentioned.tsx +++ b/packages/client/components/ResponseMentioned.tsx @@ -1,10 +1,12 @@ import graphql from 'babel-plugin-relay/macro' -import React from 'react' +import React, {useEffect} from 'react' import {useFragment} from 'react-relay' import NotificationAction from '~/components/NotificationAction' import useRouter from '../hooks/useRouter' import {ResponseMentioned_notification$key} from '../__generated__/ResponseMentioned_notification.graphql' import NotificationTemplate from './NotificationTemplate' +import SendClientSideEvent from '../utils/SendClientSideEvent' +import useAtmosphere from '~/hooks/useAtmosphere' interface Props { notification: ResponseMentioned_notification$key @@ -27,15 +29,26 @@ const ResponseMentioned = (props: Props) => { id name } + type + status kudosEmojiUnicode } `, notificationRef ) const {history} = useRouter() - const {meeting, response, kudosEmojiUnicode} = notification + const atmosphere = useAtmosphere() + const {meeting, response, kudosEmojiUnicode, type, status} = notification const {picture: authorPicture, preferredName: authorName} = response.user + useEffect(() => { + SendClientSideEvent(atmosphere, 'Notification Viewed', { + notificationType: type, + notificationStatus: status, + kudosEmojiUnicode + }) + }, []) + const {id: meetingId, name: meetingName} = meeting const goThere = () => { history.push(`/meet/${meetingId}/responses?responseId=${encodeURIComponent(response.id)}`) From cce3c1eb28d5816a587807554d9e1841dfb57b25 Mon Sep 17 00:00:00 2001 From: Igor Lesnenko Date: Wed, 6 Dec 2023 17:51:13 +0400 Subject: [PATCH 09/11] response mentioned toast analytics --- .../toasts/mapResponseMentionedToToast.ts | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/client/mutations/toasts/mapResponseMentionedToToast.ts b/packages/client/mutations/toasts/mapResponseMentionedToToast.ts index cc637b54ba8..b9d2e5cecd1 100644 --- a/packages/client/mutations/toasts/mapResponseMentionedToToast.ts +++ b/packages/client/mutations/toasts/mapResponseMentionedToToast.ts @@ -3,7 +3,7 @@ import {Snack} from '../../components/Snackbar' import {OnNextHistoryContext} from '../../types/relayMutations' import {mapResponseMentionedToToast_notification$data} from '../../__generated__/mapResponseMentionedToToast_notification.graphql' import makeNotificationToastKey from './makeNotificationToastKey' -import getReactji from '../../utils/getReactji' +import SendClientSideEvent from '../../utils/SendClientSideEvent' graphql` fragment mapResponseMentionedToToast_notification on NotifyResponseMentioned { @@ -18,23 +18,21 @@ graphql` id name } - kudosEmoji + kudosEmojiUnicode } ` const mapResponseMentionedToToast = ( notification: mapResponseMentionedToToast_notification$data, - {history}: OnNextHistoryContext + {atmosphere, history}: OnNextHistoryContext ): Snack | null => { if (!notification) return null - const {id: notificationId, meeting, response, kudosEmoji} = notification + const {id: notificationId, meeting, response, kudosEmojiUnicode} = notification const {preferredName: authorName} = response.user const {id: meetingId, name: meetingName} = meeting - const unicodeEmoji = kudosEmoji ? getReactji(kudosEmoji).unicode : '' - - const message = kudosEmoji - ? `${unicodeEmoji} ${authorName} mentioned you and gave kudos in their response in ${meetingName}.` + const message = kudosEmojiUnicode + ? `${kudosEmojiUnicode} ${authorName} mentioned you and gave kudos in their response in ${meetingName}.` : `${authorName} mentioned you in their response in ${meetingName}.` // :TODO: (jmtaber129): Check if we're already open to the relevant standup response discussion @@ -49,6 +47,18 @@ const mapResponseMentionedToToast = ( callback: () => { history.push(`/meet/${meetingId}/responses?responseId=${encodeURIComponent(response.id)}`) } + }, + onShow: () => { + SendClientSideEvent(atmosphere, 'Snackbar Viewed', { + snackbarType: 'responseMentioned', + kudosEmojiUnicode + }) + }, + onManualDismiss: () => { + SendClientSideEvent(atmosphere, 'Snackbar Clicked', { + snackbarType: 'responseMentioned', + kudosEmojiUnicode + }) } } } From fc59e232630beae21d52daeaa0e1f19efe18a4a1 Mon Sep 17 00:00:00 2001 From: Igor Lesnenko Date: Wed, 20 Dec 2023 02:04:08 +0400 Subject: [PATCH 10/11] isValid --- .../graphql/public/types/UpsertTeamPromptResponseSuccess.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts b/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts index beebfaa4dfc..283b97c5e18 100644 --- a/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts +++ b/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts @@ -1,4 +1,5 @@ import {UpsertTeamPromptResponseSuccessResolvers} from '../resolverTypes' +import isValid from '../../../graphql/isValid' export type UpsertTeamPromptResponseSuccessSource = { teamPromptResponseId: string @@ -21,7 +22,7 @@ const UpsertTeamPromptResponseSuccess: UpsertTeamPromptResponseSuccessResolvers return null } - return dataLoader.get('kudoses').loadMany(addedKudosesIds) + return (await dataLoader.get('kudoses').loadMany(addedKudosesIds)).filter(isValid) } } From b16b0d16dad768eb4f3ee43fe10cfe40423de68e Mon Sep 17 00:00:00 2001 From: Igor Lesnenko Date: Wed, 20 Dec 2023 02:24:09 +0400 Subject: [PATCH 11/11] Add types --- .../mutations/upsertTeamPromptResponse.ts | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts b/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts index 92a1b226b28..633725df476 100644 --- a/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts +++ b/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts @@ -81,37 +81,42 @@ const upsertTeamPromptResponse: MutationResolvers['upsertTeamPromptResponse'] = const team = await dataLoader.get('teams').loadNonNull(teamId) const {kudosEmoji, kudosEmojiUnicode} = team - let kudosUserIds = null + let insertedKudoses: + | { + id: number + receiverUserId: string + emoji: string | null + emojiUnicode: string + }[] + | null = null if (team.giveKudosWithEmoji && kudosEmojiUnicode) { const oldKudosUserIds = oldTeamPromptResponse ? getKudosUserIdsFromJson(oldTeamPromptResponse.content, kudosEmojiUnicode) : [] const newKudosUserIds = getKudosUserIdsFromJson(contentJSON, kudosEmojiUnicode) - kudosUserIds = newKudosUserIds.filter( + const kudosUserIds = newKudosUserIds.filter( (userId) => !oldKudosUserIds.includes(userId) && userId !== viewerId ) - } - - let insertedKudoses = null - if (kudosUserIds?.length) { - const kudosRows = kudosUserIds.map((userId) => ({ - senderUserId: viewerId, - receiverUserId: userId, - teamId, - emoji: kudosEmoji, - emojiUnicode: kudosEmojiUnicode, - teamPromptResponseId: TeamPromptResponseId.split(teamPromptResponseId) - })) - - insertedKudoses = await pg - .insertInto('Kudos') - .values(kudosRows) - .returning(['id', 'receiverUserId', 'emoji', 'emojiUnicode']) - .execute() - - insertedKudoses.forEach((kudos) => { - analytics.kudosSent(viewerId, teamId, kudos.id, kudos.receiverUserId) - }) + if (kudosUserIds.length) { + const kudosRows = kudosUserIds.map((userId) => ({ + senderUserId: viewerId, + receiverUserId: userId, + teamId, + emoji: kudosEmoji, + emojiUnicode: kudosEmojiUnicode, + teamPromptResponseId: TeamPromptResponseId.split(teamPromptResponseId) + })) + + insertedKudoses = await pg + .insertInto('Kudos') + .values(kudosRows) + .returning(['id', 'receiverUserId', 'emoji', 'emojiUnicode']) + .execute() + + insertedKudoses.forEach((kudos) => { + analytics.kudosSent(viewerId, teamId, kudos.id, kudos.receiverUserId) + }) + } } dataLoader.get('teamPromptResponses').clear(teamPromptResponseId)