Skip to content

Commit

Permalink
feat(condo): DOMA-8417 updated notifications to telegram (#4447)
Browse files Browse the repository at this point in the history
* feat(condo): DOMA-8417 updated notifications to telegram

* feat(condo): DOMA-8417 removed files count from ticket notifications in telegram

* feat(condo): DOMA-8417 added logger

* feat(condo): DOMA-8417 updated types

* test(condo): DOMA-8417 added tests. Some fixes

* fix(condo): DOMA-8417 fixed after review
  • Loading branch information
Alllex202 authored Mar 6, 2024
1 parent 83b425a commit 7adabb2
Show file tree
Hide file tree
Showing 14 changed files with 295 additions and 118 deletions.
7 changes: 5 additions & 2 deletions apps/condo/domains/notification/constants/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,11 @@ const MESSAGE_META = {
ticketUnit: { defaultValue: '', required: true },
ticketCreatedAt: { defaultValue: '', required: true },
ticketDetails: { defaultValue: '', required: true },
ticketClassifier: { defaultValue: '', required: true },
userId: { defaultValue: '', required: true },
url: { defaultValue: '', required: true },
organizationId: { defaultValue: '', required: true },
organizationName: { defaultValue: '', required: true },
details: { defaultValue: '', required: true },
},
telegramMeta: { required: false },
},
Expand All @@ -139,11 +139,14 @@ const MESSAGE_META = {
ticketStatus: { defaultValue: '', required: true },
ticketAddress: { defaultValue: '', required: true },
ticketUnit: { defaultValue: '', required: true },
ticketDetails: { defaultValue: '', required: true },
ticketClassifier: { defaultValue: '', required: true },
userId: { defaultValue: '', required: true },
userName: { defaultValue: '', required: true },
commentId: { defaultValue: '', required: true },
commentContent: { defaultValue: '', required: true },
commentCreatedAt: { defaultValue: '', required: true },
commentType: { defaultValue: '', required: true },
commentTypeMessage: { defaultValue: '', required: true },
url: { defaultValue: '', required: true },
organizationId: { defaultValue: '', required: true },
organizationName: { defaultValue: '', required: true },
Expand Down
161 changes: 106 additions & 55 deletions apps/condo/domains/ticket/tasks/sendTicketCommentCreatedNotifications.js
Original file line number Diff line number Diff line change
@@ -1,78 +1,129 @@
const dayjs = require('dayjs')
const { get } = require('lodash')
const { v4: uuid } = require('uuid')

const conf = require('@open-condo/config')
const { getLogger } = require('@open-condo/keystone/logging')
const { getByCondition, getSchemaCtx, getById } = require('@open-condo/keystone/schema')
const { i18n } = require('@open-condo/locales/loader')

const { COUNTRIES } = require('@condo/domains/common/constants/countries')
const { setLocaleForKeystoneContext } = require('@condo/domains/common/utils/serverSchema/setLocaleForKeystoneContext')
const { TICKET_COMMENT_CREATED_TYPE } = require('@condo/domains/notification/constants/constants')
const { sendMessage } = require('@condo/domains/notification/utils/serverSchema')
const { ORGANIZATION_COMMENT_TYPE, RESIDENT_COMMENT_TYPE } = require('@condo/domains/ticket/constants')
const { buildFullClassifierName } = require('@condo/domains/ticket/utils')
const { TicketClassifier } = require('@condo/domains/ticket/utils/serverSchema')
const { getUsersAvailableToReadTicketByPropertyScope } = require('@condo/domains/ticket/utils/serverSchema/propertyScope')
const { STAFF, RESIDENT } = require('@condo/domains/user/constants/common')


const appLogger = getLogger('condo')
const taskLogger = appLogger.child({ module: 'tasks/sendTicketCommentCreatedNotifications' })

const getCommentTypeMessage = (commentType, commentAuthorType, lang) => {
const CommentToResidentMessage = i18n(`notification.messages.${TICKET_COMMENT_CREATED_TYPE}.telegram.ticketType.toResident`, { locale: lang })
const CommentFromResidentMessage = i18n(`notification.messages.${TICKET_COMMENT_CREATED_TYPE}.telegram.ticketType.fromResident`, { locale: lang })
const ResidentCommentMessage = i18n(`notification.messages.${TICKET_COMMENT_CREATED_TYPE}.telegram.ticketType.resident`, { locale: lang })
const OrganizationCommentMessage = i18n(`notification.messages.${TICKET_COMMENT_CREATED_TYPE}.telegram.ticketType.organization`, { locale: lang })

if (commentType === ORGANIZATION_COMMENT_TYPE) {
return OrganizationCommentMessage
} else if (commentType === RESIDENT_COMMENT_TYPE) {
if (commentAuthorType === STAFF) {
return CommentToResidentMessage
} else if (commentAuthorType === RESIDENT) {
return CommentFromResidentMessage
} else {
return ResidentCommentMessage
}
}

return ''
}

const EMPTY_CONTENT = '—'

/**
* Sends notifications after ticket comment created
*/
const sendTicketCommentCreatedNotifications = async (commentId, ticketId) => {
const { keystone: context } = getSchemaCtx('Ticket')
const createdComment = await getById('TicketComment', commentId)
const commentAuthor = await getById('User', createdComment.user)
const ticket = await getById('Ticket', ticketId)
const ticketOrganizationId = get(ticket, 'organization')
const ticketStatus = await getById('TicketStatus', ticket.status)
const ticketUrl = `${conf.SERVER_URL}/ticket/${ticketId}`

const organization = await getByCondition('Organization', {
id: ticketOrganizationId,
deletedAt: null,
})
const lang = get(COUNTRIES, [organization.country, 'locale'], conf.DEFAULT_LOCALE)

const OpenTicketMessage = i18n(`notification.messages.${TICKET_COMMENT_CREATED_TYPE}.telegram.openTicket`, { locale: lang })
const TicketStatusName = i18n(`ticket.status.${ticketStatus.type}.name`, { locale: lang })
const TicketUnitType = i18n(`field.UnitType.prefix.${ticket.unitType}`, { locale: lang }).toLowerCase()

const users = await getUsersAvailableToReadTicketByPropertyScope({
ticketOrganizationId: ticket.organization,
ticketPropertyId: ticket.property,
ticketExecutorId: ticket.executor,
ticketAssigneeId: ticket.executor,
ticketCategoryClassifierId: ticket.categoryClassifier,
})
const usersWithoutSender = users.filter(userId => userId !== createdComment.user)

for (const employeeUserId of usersWithoutSender) {
await sendMessage(context, {
lang,
to: { user: { id: employeeUserId } },
type: TICKET_COMMENT_CREATED_TYPE,
meta: {
dv: 1,
data: {
organizationId: organization.id,
organizationName: organization.name,
commentId,
commentContent: createdComment.content,
commentType: createdComment.type,
commentCreatedAt: dayjs(createdComment.createdAt).format('YYYY-MM-DD HH:mm'),
ticketId,
ticketNumber: ticket.number,
ticketStatus: TicketStatusName,
ticketAddress: ticket.propertyAddress,
ticketUnit: ticket.unitName ? `${TicketUnitType} ${ticket.unitName}` : EMPTY_CONTENT,
userId: employeeUserId,
userName: commentAuthor.name,
url: ticketUrl,
},
telegramMeta: {
inlineKeyboard: [[{ text: OpenTicketMessage, url: ticketUrl }]],
const taskId = uuid()
try {
const { keystone: context } = getSchemaCtx('Ticket')

const ticket = await getById('Ticket', ticketId)
const organization = await getByCondition('Organization', {
id: get(ticket, 'organization', null),
deletedAt: null,
})
const organizationCountry = get(organization, 'country', conf.DEFAULT_LOCALE)
const lang = get(COUNTRIES, [organizationCountry, 'locale'], conf.DEFAULT_LOCALE)

setLocaleForKeystoneContext(context, lang)

const createdComment = await getById('TicketComment', commentId)
const commentAuthor = await getById('User', createdComment.user)
const commentAuthorType = get(commentAuthor, 'type', STAFF)
const commentType = get(createdComment, 'type', ORGANIZATION_COMMENT_TYPE)
const ticketStatus = await getById('TicketStatus', ticket.status)
const ticketUrl = `${conf.SERVER_URL}/ticket/${ticketId}`
const classifier = await TicketClassifier.getOne(context, { id: ticket.classifier })

const OpenTicketMessage = i18n(`notification.messages.${TICKET_COMMENT_CREATED_TYPE}.telegram.openTicket`, { locale: lang })
const TicketStatusName = i18n(`ticket.status.${ticketStatus.type}.name`, { locale: lang })
const TicketUnitType = i18n(`field.UnitType.prefix.${ticket.unitType}`, { locale: lang }).toLowerCase()
const CommentTypeMessage = getCommentTypeMessage(commentType, commentAuthorType, lang)

const ticketClassifier = buildFullClassifierName(classifier)

const users = await getUsersAvailableToReadTicketByPropertyScope({
ticketOrganizationId: ticket.organization,
ticketPropertyId: ticket.property,
ticketExecutorId: ticket.executor,
ticketAssigneeId: ticket.executor,
ticketCategoryClassifierId: ticket.categoryClassifier,
})
const usersWithoutCommentAuthor = users.filter(userId => userId !== createdComment.user)

for (const employeeUserId of usersWithoutCommentAuthor) {
await sendMessage(context, {
lang,
to: { user: { id: employeeUserId } },
type: TICKET_COMMENT_CREATED_TYPE,
meta: {
dv: 1,
data: {
organizationId: organization.id,
organizationName: organization.name,
commentId,
commentContent: createdComment.content || EMPTY_CONTENT,
commentType: createdComment.type,
commentTypeMessage: CommentTypeMessage,
commentCreatedAt: dayjs(createdComment.createdAt).format('YYYY-MM-DD HH:mm'),
ticketId,
ticketDetails: ticket.details,
ticketClassifier,
ticketNumber: ticket.number,
ticketStatus: TicketStatusName,
ticketAddress: ticket.propertyAddress,
ticketUnit: ticket.unitName ? `${TicketUnitType} ${ticket.unitName}` : EMPTY_CONTENT,
userId: employeeUserId,
url: ticketUrl,
},
telegramMeta: {
inlineKeyboard: [[{ text: OpenTicketMessage, url: ticketUrl }]],
},
},
},
sender: { dv: 1, fingerprint: 'send-notifications' },
organization: { id: organization.id },
sender: { dv: 1, fingerprint: 'send-notifications' },
organization: { id: organization.id },
})
}
} catch (error) {
taskLogger.error({
msg: 'Failed to send notifications about created comment on ticket',
data: { taskId, commentId, ticketId },
error,
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

const index = require('@app/condo/index')
const { faker } = require('@faker-js/faker')
const dayjs = require('dayjs')

const conf = require('@open-condo/config')
const { setFakeClientMode, makeLoggedInAdminClient, waitFor } = require('@open-condo/keystone/test.utils')

const { TICKET_COMMENT_CREATED_TYPE } = require('@condo/domains/notification/constants/constants')
Expand Down Expand Up @@ -90,6 +92,34 @@ describe('sendTicketCommentCreatedNotifications', () => {
expect(messages[0].status).toEqual('sent')
expect(messages[0].processingMeta.messageContext.telegramChatId).toEqual(telegramChatId)
expect(messages[0].processingMeta.messageContext.userId).toEqual(employeeClient.user.id)

expect(messages[0]).toHaveProperty('meta', expect.objectContaining({
dv: 1,
data: expect.objectContaining({
organizationId: organization.id,
organizationName: organization.name,
commentId: ticketComment.id,
commentContent: ticketComment.content,
commentType: ticketComment.type,
commentTypeMessage: expect.stringContaining(''),
commentCreatedAt: dayjs(ticketComment.createdAt).format('YYYY-MM-DD HH:mm'),
ticketId: ticket.id,
ticketDetails: ticket.details,
ticketClassifier: expect.stringContaining(''),
ticketNumber: ticket.number,
ticketStatus: expect.stringContaining(''),
ticketAddress: ticket.propertyAddress,
ticketUnit: expect.stringContaining(ticket.unitName),
userId: employeeClient.user.id,
url: `${conf.SERVER_URL}/ticket/${ticket.id}`,
}),
telegramMeta: expect.objectContaining({
inlineKeyboard: [[expect.objectContaining({
text: expect.stringContaining(''),
url: `${conf.SERVER_URL}/ticket/${ticket.id}`,
})]],
}),
}))
})
})

Expand Down
103 changes: 63 additions & 40 deletions apps/condo/domains/ticket/tasks/sendTicketCreatedNotifications.js
Original file line number Diff line number Diff line change
@@ -1,64 +1,87 @@
const dayjs = require('dayjs')
const { v4: uuid } = require('uuid')

const conf = require('@open-condo/config')
const { getLogger } = require('@open-condo/keystone/logging')
const { getSchemaCtx, getById } = require('@open-condo/keystone/schema')
const { i18n } = require('@open-condo/locales/loader')

const { setLocaleForKeystoneContext } = require('@condo/domains/common/utils/serverSchema/setLocaleForKeystoneContext')
const { TICKET_CREATED_TYPE } = require('@condo/domains/notification/constants/constants')
const { sendMessage } = require('@condo/domains/notification/utils/serverSchema')
const { buildFullClassifierName } = require('@condo/domains/ticket/utils')
const { TicketClassifier } = require('@condo/domains/ticket/utils/serverSchema')
const { getUsersAvailableToReadTicketByPropertyScope } = require('@condo/domains/ticket/utils/serverSchema/propertyScope')


const appLogger = getLogger('condo')
const taskLogger = appLogger.child({ module: 'tasks/sendTicketCreatedNotifications' })

const EMPTY_CONTENT = '—'

/**
* Sends notifications after ticket created
*/
const sendTicketCreatedNotifications = async (ticketId, lang, organizationId, organizationName) => {
const { keystone: context } = getSchemaCtx('Ticket')
const createdTicket = await getById('Ticket', ticketId)
const ticketStatus = await getById('TicketStatus', createdTicket.status)
const ticketStatusName = i18n(`ticket.status.${ticketStatus.type}.name`, { locale: lang })
const ticketUnitType = i18n(`field.UnitType.prefix.${createdTicket.unitType}`, { locale: lang }).toLowerCase()
const ticketUrl = `${conf.SERVER_URL}/ticket/${ticketId}`
const taskId = uuid()
try {
const { keystone: context } = getSchemaCtx('Ticket')
setLocaleForKeystoneContext(context, lang)

const OpenTicketMessage = i18n(`notification.messages.${TICKET_CREATED_TYPE}.telegram.openTicket`, { locale: lang })
const createdTicket = await getById('Ticket', ticketId)
const ticketStatus = await getById('TicketStatus', createdTicket.status)
const ticketUrl = `${conf.SERVER_URL}/ticket/${ticketId}`
const classifier = await TicketClassifier.getOne(context, { id: createdTicket.classifier })

const users = await getUsersAvailableToReadTicketByPropertyScope({
ticketOrganizationId: createdTicket.organization,
ticketPropertyId: createdTicket.property,
ticketExecutorId: createdTicket.executor,
ticketAssigneeId: createdTicket.executor,
ticketCategoryClassifierId: createdTicket.categoryClassifier,
})
const usersWithoutAuthor = users.filter(userId => createdTicket.createdBy !== userId)
const ticketStatusName = i18n(`ticket.status.${ticketStatus.type}.name`, { locale: lang })
const ticketUnitType = i18n(`field.UnitType.prefix.${createdTicket.unitType}`, { locale: lang }).toLowerCase()
const OpenTicketMessage = i18n(`notification.messages.${TICKET_CREATED_TYPE}.telegram.openTicket`, { locale: lang })

for (const employeeUserId of usersWithoutAuthor) {
await sendMessage(context, {
lang,
to: { user: { id: employeeUserId } },
type: TICKET_CREATED_TYPE,
meta: {
dv: 1,
data: {
organizationId: organizationId,
organizationName: organizationName,
ticketId,
ticketNumber: createdTicket.number,
ticketStatus: ticketStatusName,
ticketAddress: createdTicket.propertyAddress,
ticketUnit: createdTicket.unitName ? `${ticketUnitType} ${createdTicket.unitName}` : EMPTY_CONTENT,
ticketCreatedAt: dayjs(createdTicket.createdAt).format('YYYY-MM-DD HH:mm'),
ticketDetails: createdTicket.details,
userId: employeeUserId,
url: ticketUrl,
},
telegramMeta: {
inlineKeyboard: [[{ text: OpenTicketMessage, url: ticketUrl }]],
const ticketClassifier = buildFullClassifierName(classifier)

const users = await getUsersAvailableToReadTicketByPropertyScope({
ticketOrganizationId: createdTicket.organization,
ticketPropertyId: createdTicket.property,
ticketExecutorId: createdTicket.executor,
ticketAssigneeId: createdTicket.executor,
ticketCategoryClassifierId: createdTicket.categoryClassifier,
})
const usersWithoutAuthor = users.filter(userId => createdTicket.createdBy !== userId)

for (const employeeUserId of usersWithoutAuthor) {
await sendMessage(context, {
lang,
to: { user: { id: employeeUserId } },
type: TICKET_CREATED_TYPE,
meta: {
dv: 1,
data: {
organizationId: organizationId,
organizationName: organizationName,
ticketId,
ticketClassifier,
ticketNumber: createdTicket.number,
ticketStatus: ticketStatusName,
ticketAddress: createdTicket.propertyAddress,
ticketUnit: createdTicket.unitName ? `${ticketUnitType} ${createdTicket.unitName}` : EMPTY_CONTENT,
ticketCreatedAt: dayjs(createdTicket.createdAt).format('YYYY-MM-DD HH:mm'),
ticketDetails: createdTicket.details,
userId: employeeUserId,
url: ticketUrl,
},
telegramMeta: {
inlineKeyboard: [[{ text: OpenTicketMessage, url: ticketUrl }]],
},
},
},
sender: { dv: 1, fingerprint: 'send-notifications' },
organization: { id: organizationId },
sender: { dv: 1, fingerprint: 'send-notifications' },
organization: { id: organizationId },
})
}
} catch (error) {
taskLogger.error({
msg: 'Failed to send notifications about created ticket',
data: { taskId, ticketId },
error,
})
}
}
Expand Down
Loading

0 comments on commit 7adabb2

Please sign in to comment.