DEBUG = process.env.SMC_TEST_ZENDESK ? false
async = require('async')
fs = require('fs')
path = require('path')
misc = require('smc-util/misc')
theme = require('smc-util/theme')
_ = require('underscore')
{defaults, required} = misc
winston = require 'winston'
winston.remove(winston.transports.Console)
SMC_TEST = process.env.SMC_TEST
if not SMC_TEST
winston.add(winston.transports.Console, {level: 'debug', timestamp:true, colorize:true})
zendesk_password_filename = ->
return (process.env.SMC_ROOT ? '.') + '/data/secrets/zendesk'
fixSessions = (body) ->
body = body.replace(/\?session=([^\s]*)/g, '?session=support')
urlPattern = new RegExp("(http[s]?://[^\\s]*#{theme.DNS}[^\\s]+)", "g")
reSession = /session=([^\s]*)/g
ret = ''
offset = 0
while m = urlPattern.exec(body)
url = m[0]
i = m.index
j = i + url.length
ret += body[offset...i]
q = url.indexOf('?session')
if q >= 0
url = url[0...q]
q = url.indexOf('?')
if q >= 0
url += '&session=support'
else
url += '?session=support'
ret += url
offset = j
ret += body[offset...body.length]
return ret
support = undefined
exports.init_support = (cb) ->
support = new Support cb: (err, s) =>
support = s
cb(err)
exports.get_support = ->
return support
class Support
constructor: (opts={}) ->
opts = defaults opts,
cb : undefined
@dbg = (f) =>
return (m) -> winston.debug("Zendesk.#{f}: #{m}")
dbg = @dbg("constructor")
@_zd = null
async.waterfall([
(cb) =>
dbg("loading zendesk password from disk")
password_file = zendesk_password_filename()
fs.exists password_file, (exists) =>
if exists
fs.readFile password_file, (err, data) =>
if err
cb(err)
else
dbg("read zendesk password from '#{password_file}'")
creds = data.toString().trim().split(':')
cb(null, creds[0], creds[1])
else
dbg("no password file found at #{password_file}")
cb(null, null, null)
(username, password, cb) =>
if username? and password?
zendesk = require('node-zendesk')
zd = zendesk.createClient
username : username,
password : password,
remoteUri : 'https://sagemathcloud.zendesk.com/api/v2'
cb(null, zd)
else
cb(null, null)
], (err, zendesk_client) =>
if err
dbg("error initializing zendesk -- #{misc.to_json(err)}")
else
dbg("successfully initialized zendesk")
@_zd = zendesk_client
opts.cb?(err, @)
)
recent_tickets: (cb) ->
@_zd?.tickets.listRecent (err, statusList, body, responseList, resultList) =>
if (err)
console.log(err)
return
dbg = @dbg("recent_tickets")
dbg(JSON.stringify(body, null, 2, true))
cb?(body)
get_support_tickets: (account_id, cb) ->
dbg = @dbg("get_support_tickets")
dbg("args: #{account_id}")
if not @_zd?
err = "Support ticket backend is not available."
dbg(err)
cb?(err)
return
query_zendesk = (account_id, cb) =>
q = "type:ticket fieldvalue:#{account_id}"
dbg("query = #{q}")
@_zd.search.query q, (err, req, result) =>
if err
cb(err); return
cb(null, result)
process_result = (raw, cb) =>
tickets = []
for r in raw
t = _.pick(r, 'id', 'subject', 'description', 'created_at', 'updated_at', 'status')
t.url = misc.ticket_id_to_ticket_url(t.id)
tickets.push(t)
cb(null, tickets)
async.waterfall([
async.apply(query_zendesk, account_id)
process_result
], (err, tickets) =>
if err
cb?(err)
else
cb?(null, tickets)
)
create_ticket: (opts, cb) ->
opts = defaults opts,
email_address : required
username : undefined
subject : required
body : required
tags : undefined
account_id : undefined
location : undefined
info : {}
dbg = @dbg("create_ticket")
if not @_zd?
err = "Support ticket backend is not available."
dbg(err)
cb?(err)
return
user =
user:
name : if opts.username?.trim?().length > 0 then opts.username else opts.email_address
email : opts.email_address
external_id : opts.account_id ? null
tags = opts.tags ? []
cus_fld_id =
account_id: 31614628
project_id: 30301277
location : 30301287
browser : 31647548
mobile : 31647578
internet : 31665978
hostname : 31665988
course : 31764067
quotas : 31758818
info : 31647558
custom_fields = [
{id: cus_fld_id.account_id, value: opts.account_id ? ''}
{id: cus_fld_id.project_id, value: opts.info.project_id ? ''}
{id: cus_fld_id.location , value: opts.location ? ''}
{id: cus_fld_id.browser , value: opts.info.browser ? 'unknown'}
{id: cus_fld_id.mobile , value: opts.info.mobile ? 'unknown'}
{id: cus_fld_id.internet , value: opts.info.internet ? 'unknown'}
{id: cus_fld_id.hostname , value: opts.info.hostname ? 'unknown'}
{id: cus_fld_id.course , value: opts.info.course ? 'unknown'}
{id: cus_fld_id.quotas , value: opts.info.quotas ? 'unknown'}
]
remaining_info = _.omit(opts.info, _.keys(cus_fld_id))
custom_fields.push(id: cus_fld_id.info, value: JSON.stringify(remaining_info))
body = fixSessions(opts.body)
if opts.location?
url = "https://" + path.join(theme.DNS, opts.location)
body = body + "\n\n#{url}?session=support"
else
body = body + "\n\nNo location provided."
if misc.is_valid_uuid_string(opts.info.course)
body += "\n\nCourse: #{theme.DOMAIN_NAME}/projects/#{opts.info.course}?session=support"
ticket =
ticket:
subject: opts.subject
comment:
body : body
tags : tags
type : "problem"
custom_fields: custom_fields
async.waterfall([
(cb) =>
if DEBUG
cb(null, 1234567890)
else
@_zd.users.request 'POST', ['users', 'create_or_update'], user, (err, req, result) =>
if err
dbg("create_or_update user error: #{misc.to_json(err)}")
try
err = "#{misc.to_json(misc.from_json(err.result))}"
catch
err = "#{err.result}"
cb(err); return
cb(null, result.id)
(requester_id, cb) =>
dbg("create ticket #{misc.to_json(ticket)} with requester_id: #{requester_id}")
ticket.ticket.requester_id = requester_id
if DEBUG
cb(null, Math.floor(Math.random() * 1e6 + 999e7))
else
@_zd.tickets.create ticket, (err, req, result) =>
if (err)
cb(err); return
cb(null, result.id)
(ticket_id, cb) =>
cb(null, ticket_id)
], (err, ticket_id) =>
if err
cb?(err)
else
url = misc.ticket_id_to_ticket_url(ticket_id)
cb?(null, url)
)
if SMC_TEST
exports.fixSessions = fixSessions