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

[backend] add election stage change notifications #4518

Merged
merged 3 commits into from
Sep 6, 2023
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- AlterEnum
-- This migration adds more than one value to an enum.
-- With PostgreSQL versions 11 and earlier, this is not possible
-- in a single migration. This can be worked around by creating
-- multiple migrations, each migration adding only one value to
-- the enum.


ALTER TYPE "NotificationKind" ADD VALUE 'ELECTION_ANNOUNCING_STARTED';
ALTER TYPE "NotificationKind" ADD VALUE 'ELECTION_VOTING_STARTED';
ALTER TYPE "NotificationKind" ADD VALUE 'ELECTION_REVEALING_STARTED';
8 changes: 4 additions & 4 deletions packages/server/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ enum NotificationKind {
// PROPOSAL_DISCUSSION_CONTRIBUTOR

// Referendum
// ELECTION_ANNOUNCING_STARTED
// ELECTION_VOTING_STARTED
// ELECTION_REVEALING_STARTED
// ELECTION_COUNCIL_ELECTED
ELECTION_ANNOUNCING_STARTED
ELECTION_VOTING_STARTED
ELECTION_REVEALING_STARTED


// ------------------
// Entity specific
Expand Down
64 changes: 64 additions & 0 deletions packages/server/src/notifier/model/email/election.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { PIONEER_URL } from '@/common/config'
import { renderPioneerEmail } from '@/common/email-templates/pioneer-email'

import { EmailFromNotificationFn } from './utils'

export const fromElectionAnnouncingStartedNotification: EmailFromNotificationFn = async ({ kind, member }) => {
if (kind !== 'ELECTION_ANNOUNCING_STARTED') {
return
}

return {
subject: '[Pioneer] New election started',
html: renderPioneerEmail({
memberHandle: member.name,
summary: 'New election started.',
text: 'New Joystream council has just been elected and announcing period for the next election has started. Follow the link below to announce your candidacy.',
button: {
label: 'See on Pioneer',
href: `${PIONEER_URL}/#/election`,
},
}),
to: member.email,
}
}

export const fromElectionVotingStartedNotification: EmailFromNotificationFn = async ({ kind, member }) => {
if (kind !== 'ELECTION_VOTING_STARTED') {
return
}

return {
subject: '[Pioneer] Election voting started',
html: renderPioneerEmail({
memberHandle: member.name,
summary: 'Election voting started.',
text: 'Election voting period has just started. Follow the link below to cast your votes.',
button: {
label: 'See on Pioneer',
href: `${PIONEER_URL}/#/election`,
},
}),
to: member.email,
}
}

export const fromElectionRevealingStartedNotification: EmailFromNotificationFn = async ({ kind, member }) => {
if (kind !== 'ELECTION_REVEALING_STARTED') {
return
}

return {
subject: '[Pioneer] Election revealing period started',
html: renderPioneerEmail({
memberHandle: member.name,
summary: 'Election revealing started.',
text: 'Election revealing period has just started. Follow the link below to reveal your votes.',
button: {
label: 'See on Pioneer',
href: `${PIONEER_URL}/#/election`,
},
}),
to: member.email,
}
}
13 changes: 12 additions & 1 deletion packages/server/src/notifier/model/email/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { verbose, error } from 'npmlog'

import { Email, EmailProvider } from '@/common/utils/email'

import {
fromElectionAnnouncingStartedNotification,
fromElectionRevealingStartedNotification,
fromElectionVotingStartedNotification,
} from './election'
import { fromPostAddedNotification, fromThreadCreatedNotification } from './forum'
import { Notification, hasEmailAddress } from './utils'

Expand All @@ -17,7 +22,13 @@ export const createEmailNotifier =
pick(notification, 'id', 'eventId', 'kind', 'entityId')
)

const emailHandlers = [fromPostAddedNotification, fromThreadCreatedNotification]
const emailHandlers = [
fromPostAddedNotification,
fromThreadCreatedNotification,
fromElectionAnnouncingStartedNotification,
fromElectionVotingStartedNotification,
fromElectionRevealingStartedNotification,
]
const emailPromises = emailHandlers.map((handler) => handler(notification))
const emailResults = await Promise.all(emailPromises)

Expand Down
39 changes: 39 additions & 0 deletions packages/server/src/notifier/model/event/election.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { pick } from 'lodash'

import {
ElectionAnnouncingStartedEventFieldsFragmentDoc,
ElectionRevealingStartedFieldsFragmentDoc,
ElectionVotingStartedEventFieldsFragmentDoc,
useFragment,
} from '@/common/queries'

import { NotifEventFromQNEvent } from './utils'

export const fromElectionAnnouncingStartedEvent: NotifEventFromQNEvent<'AnnouncingPeriodStartedEvent'> = async (
event,
buildEvents
) => {
const announcingPeriodStartedEvent = useFragment(ElectionAnnouncingStartedEventFieldsFragmentDoc, event)
const eventData = pick(announcingPeriodStartedEvent, 'inBlock', 'id')
return buildEvents(eventData, eventData.id, ({ generalEvent }) => [
generalEvent('ELECTION_ANNOUNCING_STARTED', 'ANY'),
])
}

export const fromElectionVotingStartedEvent: NotifEventFromQNEvent<'VotingPeriodStartedEvent'> = async (
event,
buildEvents
) => {
const votingPeriodStartedEvent = useFragment(ElectionVotingStartedEventFieldsFragmentDoc, event)
const eventData = pick(votingPeriodStartedEvent, 'inBlock', 'id')
return buildEvents(eventData, eventData.id, ({ generalEvent }) => [generalEvent('ELECTION_VOTING_STARTED', 'ANY')])
}

export const fromElectionRevealingStartedEvent: NotifEventFromQNEvent<'RevealingStageStartedEvent'> = async (
event,
buildEvents
) => {
const revealingPeriodStartedEvent = useFragment(ElectionRevealingStartedFieldsFragmentDoc, event)
const eventData = pick(revealingPeriodStartedEvent, 'inBlock', 'id')
return buildEvents(eventData, eventData.id, ({ generalEvent }) => [generalEvent('ELECTION_REVEALING_STARTED', 'ANY')])
}
21 changes: 15 additions & 6 deletions packages/server/src/notifier/model/event/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { match } from 'ts-pattern'

import { GetNotificationEventsQuery } from '@/common/queries'

import {
fromElectionAnnouncingStartedEvent,
fromElectionRevealingStartedEvent,
fromElectionVotingStartedEvent,
} from './election'
import { fromPostAddedEvent, fromThreadCreatedEvent } from './forum'
import { NotificationEvent } from './utils'
import { buildEvents } from './utils/buildEvent'
Expand All @@ -18,11 +25,13 @@ export const toNotificationEvents =
const event = anyEvent as ImplementedQNEvent
const build = buildEvents(allMemberIds, event)

switch (event.__typename) {
case 'PostAddedEvent':
return fromPostAddedEvent(event, build)
const notifEvent = match(event)
.with({ __typename: 'PostAddedEvent' }, (e) => fromPostAddedEvent(e, build))
.with({ __typename: 'ThreadCreatedEvent' }, (e) => fromThreadCreatedEvent(e, build))
.with({ __typename: 'AnnouncingPeriodStartedEvent' }, (e) => fromElectionAnnouncingStartedEvent(e, build))
.with({ __typename: 'VotingPeriodStartedEvent' }, (e) => fromElectionVotingStartedEvent(e, build))
.with({ __typename: 'RevealingStageStartedEvent' }, (e) => fromElectionRevealingStartedEvent(e, build))
.exhaustive()

case 'ThreadCreatedEvent':
return fromThreadCreatedEvent(event, build)
}
return notifEvent
}
2 changes: 1 addition & 1 deletion packages/server/src/notifier/model/event/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ export type BuildEvents = (
export type NotifEventFromQNEvent<T extends ImplementedQNEvent['__typename']> = (
event: QNEvent<T>,
buildEvents: BuildEvents
) => NotificationEvent | Promise<NotificationEvent>
) => Promise<NotificationEvent>
16 changes: 7 additions & 9 deletions packages/server/src/notifier/model/subscriptionKinds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const GeneralSubscriptionKind = extract(
'FORUM_THREAD_CONTRIBUTOR',

'FORUM_THREAD_ALL',
'FORUM_THREAD_MENTION'
'FORUM_THREAD_MENTION',

// 'PROPOSAL_CREATED_ALL',
// 'PROPOSAL_STATUS_ALL',
Expand All @@ -36,10 +36,9 @@ export const GeneralSubscriptionKind = extract(
// 'PROPOSAL_DISCUSSION_CREATOR',
// 'PROPOSAL_DISCUSSION_CONTRIBUTOR',

// 'ELECTION_ANNOUNCING_STARTED',
// 'ELECTION_VOTING_STARTED',
// 'ELECTION_REVEALING_STARTED',
// 'ELECTION_COUNCIL_ELECTED'
'ELECTION_ANNOUNCING_STARTED',
'ELECTION_VOTING_STARTED',
'ELECTION_REVEALING_STARTED'
)

export const isDefaultSubscription = (type: GeneralSubscriptionKind): boolean => defaultSubscriptions.includes(type)
kdembler marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -55,8 +54,7 @@ const defaultSubscriptions: GeneralSubscriptionKind[] = [
// 'PROPOSAL_DISCUSSION_MENTION',
// 'PROPOSAL_DISCUSSION_CREATOR',
// 'PROPOSAL_DISCUSSION_CONTRIBUTOR',
// 'ELECTION_ANNOUNCING_STARTED',
// 'ELECTION_VOTING_STARTED',
// 'ELECTION_REVEALING_STARTED',
// 'ELECTION_COUNCIL_ELECTED',
'ELECTION_ANNOUNCING_STARTED',
'ELECTION_VOTING_STARTED',
'ELECTION_REVEALING_STARTED',
]
30 changes: 30 additions & 0 deletions packages/server/src/notifier/queries/events.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@ fragment ThreadCreatedEventFields on ThreadCreatedEvent {
text
}

fragment ElectionAnnouncingStartedEventFields on AnnouncingPeriodStartedEvent {
__typename
id
inBlock
}

fragment ElectionVotingStartedEventFields on VotingPeriodStartedEvent {
__typename
id
inBlock
}

fragment ElectionRevealingStartedFields on RevealingStageStartedEvent {
__typename
id
inBlock
}

# fragment ProposalDiscussionPostCreatedEventFields on ProposalDiscussionPostCreatedEvent {
# __typename
# id
Expand Down Expand Up @@ -60,6 +78,9 @@ query GetNotificationEvents($from: Int, $exclude: [ID!]) {
type_in: [
PostAddedEvent
ThreadCreatedEvent
AnnouncingPeriodStartedEvent
VotingPeriodStartedEvent
RevealingStageStartedEvent
# PostTextUpdatedEvent
]
inBlock_gte: $from
Expand All @@ -73,6 +94,15 @@ query GetNotificationEvents($from: Int, $exclude: [ID!]) {
... on ThreadCreatedEvent {
...ThreadCreatedEventFields
}
... on AnnouncingPeriodStartedEvent {
...ElectionAnnouncingStartedEventFields
}
... on VotingPeriodStartedEvent {
...ElectionVotingStartedEventFields
}
... on RevealingStageStartedEvent {
...ElectionRevealingStartedFields
}
# ... on ProposalDiscussionPostCreatedEvent {
# ...ProposalDiscussionPostCreatedEventFields
# }
Expand Down
35 changes: 35 additions & 0 deletions packages/server/test/_mocks/notifier/events/election.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { maskFragment } from '@test/_mocks/utils'

import {
ElectionAnnouncingStartedEventFieldsFragment,
ElectionRevealingStartedFieldsFragment,
ElectionVotingStartedEventFieldsFragment,
GetNotificationEventsQuery,
} from '@/common/queries'

export const electionAnnouncingEvent = (id: string): GetNotificationEventsQuery['events'][0] =>
maskFragment(
'ElectionAnnouncingStartedEventFields',
'AnnouncingPeriodStartedEvent'
)<ElectionAnnouncingStartedEventFieldsFragment>({
id,
inBlock: 1,
})

export const electionVotingEvent = (id: string): GetNotificationEventsQuery['events'][0] =>
maskFragment(
'ElectionVotingStartedEventFields',
'VotingPeriodStartedEvent'
)<ElectionVotingStartedEventFieldsFragment>({
id,
inBlock: 1,
})

export const electionRevealingEvent = (id: string): GetNotificationEventsQuery['events'][0] =>
maskFragment(
'ElectionRevealingStartedFields',
'RevealingStageStartedEvent'
)<ElectionRevealingStartedFieldsFragment>({
id,
inBlock: 1,
})
Loading