Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions schema/auth.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ type AccountNotificationPreferences {
channelReceivedFundsFromWg: NotificationPreference!
newPayoutUpdatedByCouncil: NotificationPreference! #v2
channelFundsWithdrawn: NotificationPreference!
tipVideoCommentCreated: NotificationPreference!

# member notifications: https://www.figma.com/file/yhZpTHdf1sxJx13uRZ71GV/Membership-profile?type=design&node-id=2977-58785&mode=design
channelCreated: NotificationPreference!
Expand Down
25 changes: 24 additions & 1 deletion schema/notifications.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ union NotificationType =
| CreatorTokenRevenueShareStarted
| CreatorTokenRevenueSharePlanned
| CreatorTokenRevenueShareEnded

| TipCommentPostedToVideo # tip video comment created
type ChannelSuspended @variant {
phantom: Int
}
Expand Down Expand Up @@ -587,3 +587,26 @@ type CreatorTokenRevenueShareEnded @variant {
"id of token"
tokenId: String!
}

type TipCommentPostedToVideo @variant {
"video title used for notification text"
videoTitle: String!

"video Id used for link"
videoId: String!

"commenter id for the avatar"
memberId: String!

"commenter handle for text"
memberHandle: String!

"id for the comment used for the link"
commentId: String!

"Tier received for adding a tip to the comment (if any)"
tipTier: CommentTipTier

"Tip included when adding the comment (in HAPI)"
tipAmount: BigInt!
}
4 changes: 4 additions & 0 deletions src/server-extension/resolvers/NotificationResolver/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ export class NotificationResolver {
newPreferences.fundsFromWgReceived,
account.notificationPreferences.fundsFromWgReceived
)
maybeUpdateNotificationPreference(
newPreferences.tipVideoCommentCreated,
account.notificationPreferences.tipVideoCommentCreated
)
await em.save(account)

return toOutputGQL(account.notificationPreferences)
Expand Down
7 changes: 7 additions & 0 deletions src/server-extension/resolvers/NotificationResolver/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ export class AccountNotificationPreferencesInput {
@Field(() => NotificationPreferenceGQL, { nullable: true })
channelFundsWithdrawn: NotificationPreference

@Field(() => NotificationPreferenceGQL, { nullable: true })
tipVideoCommentCreated: NotificationPreference

// member

@Field(() => NotificationPreferenceGQL, { nullable: true })
Expand Down Expand Up @@ -190,6 +193,9 @@ export class AccountNotificationPreferencesOutput
@Field(() => NotificationPreferenceOutput, { nullable: true })
channelFundsWithdrawn: NotificationPreference

@Field(() => NotificationPreferenceOutput, { nullable: true })
tipVideoCommentCreated: NotificationPreference

// member

@Field(() => NotificationPreferenceOutput, { nullable: true })
Expand Down Expand Up @@ -277,5 +283,6 @@ export function toOutputGQL(
fundsFromCouncilReceived: preferences.fundsFromCouncilReceived,
fundsToExternalWalletSent: preferences.fundsToExternalWalletSent,
fundsFromWgReceived: preferences.fundsFromWgReceived,
tipVideoCommentCreated: preferences.tipVideoCommentCreated,
}
}
149 changes: 149 additions & 0 deletions src/tests/integration/notifications.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { IMemberRemarked, MemberRemarked, ReactVideo } from '@joystream/metadata-protobuf'
import { AnyMetadataClass } from '@joystream/metadata-protobuf/types'
import { Store } from '@subsquid/typeorm-store'
Expand All @@ -23,6 +23,7 @@
NotificationType,
OwnedNft,
VideoLiked,
TipCommentPostedToVideo,
} from '../../model'
import { setFeaturedNftsInner } from '../../server-extension/resolvers/AdminResolver'
import { globalEm } from '../../utils/globalEm'
Expand Down Expand Up @@ -339,4 +340,152 @@
checkNotificationEmailDelivery(notificationId))
})
})

describe('👉Tip Comment Posted To Video', () => {
let nextNotificationIdPre: number
let notificationId: string
const block = { timestamp: 123456 } as any
const indexInBlock = 1
const extrinsicHash = '0x1234567890abcdef'
const commentId = backwardCompatibleMetaID(block, indexInBlock)
const videoId = '1'
const tipAmount = BigInt(100000)
let video: Video
let event: any
const metadataMessage: IMemberRemarked = {
createComment: {
videoId: Long.fromNumber(1),
parentCommentId: null,
body: 'test',
},
}
before(async () => {
nextNotificationIdPre = await getNextNotificationId(overlay, true)

Choose a reason for hiding this comment

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

This will no longer work in Orion v5, as the notification ids are randomly generated, please see the adjustments to notifications tests that I made here: https://github.com/Joystream/orion/pull/365/files#diff-4ecb12693f9548f4c9bf50d647cf34ac9bd0f648f61151c72cda0c2d2fbc139a

notificationId = RUNTIME_NOTIFICATION_ID_TAG + '-' + nextNotificationIdPre.toString()
video = await em
.getRepository(Video)
.findOneOrFail({ where: { id: videoId }, relations: { channel: true } })
event = {
isV2001: true,
asV2001: [
'2',
metadataToBytes(MemberRemarked, metadataMessage),
[video.channel.rewardAccount, tipAmount],
], // avoid comment author == creator
}
})
it('should process comment to video and deposit notification', async () => {
await processMemberRemarkedEvent({
overlay,
block,
indexInBlock,
extrinsicHash,
event,
})

const nextNotificationId = await getNextNotificationId(overlay, true)
notification = (await overlay
.getRepository(Notification)
.getByIdOrFail(notificationId)) as Notification | null

it('notification type is comment posted to video', () => {
expect(notification).not.to.be.null
expect(notification!.notificationType.isTypeOf).to.equal('TipCommentPostedToVideo')
})
it('notification data for comment posted to video should be ok', () => {
const notificationData = notification!.notificationType as TipCommentPostedToVideo
expect(notificationData.videoId).to.equal('1')
expect(notificationData.commentId).to.equal(commentId)
expect(notificationData.memberHandle).to.equal('handle-2')
expect(notificationData.videoTitle).to.equal('test-video-1')
expect(notificationData.tipAmount).to.equal(tipAmount)
})

it('general notification creation setting should be as default', () => {
expect(notification!.status.isTypeOf).to.equal('Unread')
expect(notification!.inApp).to.be.true
expect(nextNotificationId.toString()).to.equal((nextNotificationIdPre + 1).toString())
expect(notification!.recipient.isTypeOf).to.equal('ChannelRecipient')
})
it('notification email entity should be correctly deposited on overlay', async () => {
const notificationEmailDelivery = (await overlay
.getRepository(NotificationEmailDelivery)
.getOneByRelation('notificationId', notificationId)) as NotificationEmailDelivery | null
expect(notificationEmailDelivery).not.to.be.null
expect(notificationEmailDelivery!.discard).to.be.false
expect(notificationEmailDelivery!.attempts).to.be.empty
})
})
describe('👉 Reply To Comment', () => {
let nextNotificationIdPre: number
let notificationId: string
const block = { timestamp: 123457 } as any
const indexInBlock = 1
const metadataMessage = {
createComment: {
videoId: Long.fromNumber(1),
parentCommentId: commentId,
body: 'reply test',
},
}
const event = {
isV2001: true,
asV2001: ['3', metadataToBytes(MemberRemarked, metadataMessage!), undefined],
} as any

before(async () => {
nextNotificationIdPre = await getNextNotificationId(overlay, true)
notificationId = RUNTIME_NOTIFICATION_ID_TAG + '-' + nextNotificationIdPre.toString()

await processMemberRemarkedEvent({
overlay,
block,
indexInBlock,
extrinsicHash,
event,
})
})

describe('should process reply to comment and deposit notification', () => {
let nextNotificationId: number
before(async () => {
nextNotificationId = await getNextNotificationId(overlay, true)
notification = (await overlay
.getRepository(Notification)
.getByIdOrFail(notificationId)) as Notification | null
})

it('notification type is reply to comment', () => {
expect(notification).not.to.be.null
expect(notification!.notificationType.isTypeOf).to.equal('CommentReply')
expect(notification?.accountId).to.equal('2')
})
it('notification data for comment reply should be ok', () => {
const notificationData = notification!.notificationType as CommentReply
expect(notificationData.videoId).to.equal('1')
expect(notificationData.memberHandle).to.equal('handle-3')
expect(notificationData.commentId).to.equal(backwardCompatibleMetaID(block, indexInBlock))
expect(notificationData.videoTitle).to.equal('test-video-1')
expect(notification!.recipient.isTypeOf).to.equal('MemberRecipient')
expect((notification!.recipient as MemberRecipient).membership).to.equal(
'2',
'member recipient should be parent comment author'
)
})
it('general notification creation setting should be as default', () => {
expect(notification!.status.isTypeOf).to.equal('Unread')
expect(notification!.inApp).to.be.true
expect(nextNotificationId.toString()).to.equal((nextNotificationIdPre + 1).toString())
})
it('notification email entity should be correctly deposited on overlay', async () => {
const notificationEmailDelivery = (await overlay
.getRepository(NotificationEmailDelivery)
.getOneByRelation('notificationId', notificationId)) as NotificationEmailDelivery | null
expect(notificationEmailDelivery).not.to.be.null
expect(notificationEmailDelivery!.discard).to.be.false
expect(notificationEmailDelivery!.attempts).to.be.empty
})
})
})
})
})
17 changes: 15 additions & 2 deletions src/utils/notification/notificationsData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,15 +174,28 @@ export const getNotificationData = async (
}
}
case 'CommentPostedToVideo': {
const { videoId, videoTitle, memberId, memberHandle } = notificationType
const { videoId, videoTitle, memberId, memberHandle, comentId } = notificationType

Choose a reason for hiding this comment

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

Suggested change
const { videoId, videoTitle, memberId, memberHandle, comentId } = notificationType
const { videoId, videoTitle, memberId, memberHandle, commentId } = notificationType

return {
icon: await getNotificationIcon(em, 'follow'),
link: await getNotificationLink(em, 'nft-page', [videoId]),
link: await getNotificationLink(em, 'video-page', [videoId, comentId]),

Choose a reason for hiding this comment

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

Suggested change
link: await getNotificationLink(em, 'video-page', [videoId, comentId]),
link: await getNotificationLink(em, 'video-page', [videoId, commentId]),

avatar: await getNotificationAvatar(em, 'membershipId', memberId),
text: `💬 ${memberHandle} left a comment on your video: “${videoTitle}”`,
subject: `💬 ${memberHandle} left a comment on your video: “${videoTitle}”`,
}
}
case 'TipCommentPostedToVideo': {
const { videoId, videoTitle, memberId, memberHandle, commentId, tipAmount } = notificationType
return {
icon: await getNotificationIcon(em, 'follow'),
link: await getNotificationLink(em, 'video-page', [videoId, commentId]),
avatar: await getNotificationAvatar(em, 'membershipId', memberId),
text: `💬 You received (${convertHapiToUSD(tipAmount) ?? '-'}$) ${formatJOY(
tipAmount
)} JOY from ${memberHandle} under your video: “${videoTitle}”`,
// You received {tipAmount} JOY tip from {memberHandle} under your video: “{videoTitle}”
subject: `💬 ${memberHandle} left a comment on your video: “${videoTitle}”`,
}
}
case 'VideoLiked': {
const { videoId, videoTitle, memberId, memberHandle } = notificationType
return {
Expand Down
Loading