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

Send ticket data to redshift #3

Merged
merged 11 commits into from
May 29, 2024
9 changes: 8 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,12 @@
"root": true,
"env": {
"node": true
},
"rules": {
"prefer-const":"off",
"prefer-destructuring":"off",
"@typescript-eslint/restrict-plus-operands":"off",
"prefer-template":"off",
"no-await-in-loop": "off"
}
}
}
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
{
"name": "outbound-access",
"attrs": {
"host": "analytics.vtex.com",
"host": "rc.vtex.com",
"path": "*"
}
},
Expand Down
44 changes: 32 additions & 12 deletions node/clients/redshift.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,47 @@
// saveMessage gets the message data and saves it to our RedShift database.

import { InfraClient, IOContext, InstanceOptions } from '@vtex/api'
import { ExternalClient } from '@vtex/api'
import type { IOContext, InstanceOptions } from '@vtex/api'

const url = 'https://analytics.vtex.com/api/analytics/schemaless-events'
const url = 'https://rc.vtex.com/api/analytics/schemaless-events'
const requestHeaders = {
'Content-Type': 'application/json',
}

export default class RedshiftClient extends InfraClient {
export interface MessageData {
ticketId: string
commentId: number
authorId: number
createdAt: string
containsHelpArticle: boolean
containsDevArticle: boolean
numberOfDocsPortalsUrls: number
docsPortalsUrls: string[]
numberOfArticleUrls: number
articleUrls: string[]
}

export default class RedshiftClient extends ExternalClient {
constructor(ctx: IOContext, options?: InstanceOptions) {
super('https://analytics.vtex.com', ctx, {
super('https://rc.vtex.com', ctx, {
...options,
retries: 2,
headers: {
'Content-Type': 'application/json',
},
headers: requestHeaders,
})
}

public async saveMessage(messageData: Record<string, any>) {
return this.http.post(url, messageData, {
metric: 'save-ticket-message-doc-data',
headers: requestHeaders,
})
public async saveMessage(messageData: MessageData) {
return this.http.post(
url,
{
name: 'support-ticket-messages',
description: 'Information about a support ticket message containing documentation links.',
account: 'vtexhelp',
fields: messageData,
},
{
headers: requestHeaders,
}
)
}
}
7 changes: 5 additions & 2 deletions node/clients/zendesk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ export default class ZendeskClient extends ExternalClient {
public async getComments(ticketId: string) {
const url = `https://vtexhelp1704480599.zendesk.com/api/v2/tickets/${ticketId}/comments.json`

return this.http.get(url, {
return this.http.get(
url,
{
headers: requestHeaders,
})
}
)
}
}
89 changes: 81 additions & 8 deletions node/middlewares/processTicket.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,105 @@
// The processMessage function gets the ticket message from the context and formats its content to be saved in a database later.

import bodyParser from 'co-body'
import { or } from 'ramda'

import type { MessageData } from '../clients/redshift'

// Defining URLs that will be used to parse and process the comment data
// Substrings to look for and to exclude
const helpUrlShort = 'help.vtex.com'
const devUrlShort = 'developers.vtex.com'
// URLs to exclude
const helpUrl = 'https://help.vtex.com'
const devUrl = 'https://developers.vtex.com'
const helpUrlSlash = 'https://help.vtex.com/'
const devUrlSlash = 'https://developers.vtex.com/'
const helpUrlShortSlash = 'help.vtex.com/'
const devUrlShortSlash = 'developers.vtex.com/'

export async function processTicket(
ctx: Context,
next: () => Promise<Record<string, unknown>>
) {
console.info('Running processTicket')

// const requestReceived = ctx.request
const requestBody = await bodyParser(ctx.req)
const ticketId = requestBody.ticketId
const zendeskTicket = requestBody.ticketId

console.info('Ticket ID: ')
console.info(ticketId)
console.info(zendeskTicket)

const zendesk = ctx.clients.zendesk
const redshift = ctx.clients.redshift

const ticketComments = await zendesk.getComments(zendeskTicket)

let allCommentsWithUrls = []
let redshiftResponse: string | void = ''

// Iterate over comments
for (const comment of ticketComments.comments) {
// Get an array of all urls in the comment
const allUrls = comment.html_body
.split('href="')
.slice(1)
.map((x: string) => x.split('">')[0])

let vtexPortalsUrls = []

// Select only urls from our public docs portals
for (const url of allUrls) {
if (or(url.includes(helpUrlShort), url.includes(devUrlShort))) {
vtexPortalsUrls.push(url)
}
}

// Generating new array that does not contain the portals' homepages
const docUrls = vtexPortalsUrls
.filter((url: string) => url !== helpUrl)
.filter((url: string) => url !== devUrl)
.filter((url: string) => url !== helpUrlSlash)
.filter((url: string) => url !== devUrlSlash)
.filter((url: string) => url !== helpUrlShort)
.filter((url: string) => url !== devUrlShort)
.filter((url: string) => url !== helpUrlShortSlash)
.filter((url: string) => url !== devUrlShortSlash)

// Checking to see which portals the articles pertain to
const hasHelpArticle = docUrls.some((url: string) =>
url.includes(helpUrlShort)
)

const hasDevArticle = docUrls.some((url: string) =>
url.includes(devUrlShort)
)

if (vtexPortalsUrls.length > 0) {
// Assemble messageData body that will be saved to Redshift
const messageData: MessageData = {
ticketId: zendeskTicket,
commentId: comment.id,
authorId: comment.author_id,
createdAt: comment.created_at,
containsHelpArticle: hasHelpArticle,
containsDevArticle: hasDevArticle,
numberOfDocsPortalsUrls: vtexPortalsUrls.length,
docsPortalsUrls: vtexPortalsUrls,
numberOfArticleUrls: docUrls.length,
articleUrls: docUrls,
}

const ticketComments = await zendesk.getComments(ticketId)
allCommentsWithUrls.push(messageData)

for (comment of ticketComments.comments) {
const id = comment.id
const urls = comment.html_body.split('href"').split('"')[0]
redshiftResponse = await redshift.saveMessage(messageData)
}
}

ctx.status = 200
ctx.response.body = {
mensagem: 'ticket processado',
message: 'ticket processed',
redShift: redshiftResponse,
docsUrlsData: allCommentsWithUrls,
}

await next()
Expand Down
Loading