all files / src/plugin/ freshdesk.js

100% Statements 52/52
93.33% Branches 14/15
100% Functions 28/28
100% Lines 30/30
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143                          28×                                                                   16× 16×         24×                         44×                       52×   52×   52× 72×     52×                         36×   36×   36×   36×   252×   36×                                      
import * as util from '../util'
import _axios from 'axios'
import flatten from 'array-flatten'
 
const fields = ['title', 'description_text', 'body_text', 'tags', 'meta_title', 'meta_description', 'meta_keywords'] // the fields that will be indexed
 
/**
 * The lunr.js builder plugin.
 * Meant to be used like `builder.use(plugin)`
 *
 * @memberof plugin.freshdesk
 * @return {void}
 */
function plugin () {
  this.ref('id')
  fields.forEach(f => this.field(f))
}
 
/**
 * Fetches all the solution articles and forum posts from the designated source.
 *
 * @memberof plugin.freshdesk
 * @param  {string} domain          the domain of the API to make the request to
 * @param  {string} user            the username for the API
 * @param  {string} pass            the password for the API
 * @return {object[]}               a list of processed solution articles, forum topics, and forum comments
 */
async function fetch (
  domain,
  user,
  pass
) {
  const solutions = _axios.create({
    baseURL: `https://${domain}.freshdesk.com/api/v2/solutions/`,
    auth: {
      username: user,
      password: pass
    }
  })
 
  // gets a list of all the articles
  const categories = await get(solutions, 'categories')
  const folders = await getAllItemsIn(categories, solutions, id => `categories/${id}/folders`)
  const articles = await getAllItemsIn(folders, solutions, id => `folders/${id}/articles`)
  const processedArticles = await getAllItemsIn(articles, solutions, id => `articles/${id}`).then(arts => arts.map(a => process(a, 'solution'))) // process each article after fetching it
 
  const posts = _axios.create({
    baseURL: `https://${domain}.freshdesk.com/api/v2/discussions/`,
    auth: {
      username: user,
      password: pass
    }
  })
 
  // gets a list of all the articles
  const discussionCategories = await get(posts, 'categories')
  const forums = await getAllItemsIn(discussionCategories, posts, id => `categories/${id}/forums`).then(ary => ary.filter(t => hasComments(t)))
  const topics = await getAllItemsIn(forums, posts, id => `forums/${id}/topics`).then(ary => ary.filter(t => hasComments(t)))
  const comments = await getAllItemsIn(topics, posts, id => `topics/${id}/comments`).then(cmts => cmts.map(comment => process(comment, 'forum')))
 
  return [...comments, ...processedArticles, ...topics.map(e => process(e, 'forum'))]
}
 
function hasComments (topic) {
  return topic.comments_count > 0
}
 
/**
 * Helper function to get all the items from a list of parent resources.
 *
 * @private
 * @param  {object[]} ary             list of parent resources
 * @param  {axios.Instance} axios     the preconfigured axios instance
 * @param  {Function} buildURL        function to build the endpoint URL relative to the baseURL in the axios instance
 * @return {object[]}                 a list of child resources
 */
async function getAllItemsIn (ary, axios, buildURL) {
  return flatten.depth(await Promise.all(ary.map(({id}) => get(axios, buildURL(id)))), 1)
}
 
/**
 * Helper function to make axios get request.
 *
 * @private
 * @param  {axios.Instance} axios the preconfigured axios instance
 * @param  {string} url     the url to access relative to the baseURL in the axios instance
 * @return {object}         data of axios response
 */
async function get (axios, url) {
  return axios.get(url).then(({data}) => {
    let ret
    const baseURL = axios.defaults.baseURL
 
    if (data.constructor.name === 'Array') {
      ret = data.map(doc => { return {...doc, documentSource: {url: baseURL + url}} })
    } else Eif (data.constructor.name === 'Object') {
      ret = {...data, documentSource: {url: baseURL + url}}
    }
 
    return ret
  })
}
 
/**
 * Modify the documents so that they can be indexed correctly.
 *
 * @memberof plugin.freshdesk
 * @param  {object} doc the document to be processed
 * @param  {?string} source the source of the document
 * @return {object}     the processed document
 */
function process (doc, source = '') {
  const obj = {...doc} // dup the document
 
  obj.tags = obj.tags ? obj.tags.join(' ') : '' // lunr can't index an array of strings so join the tags into 1 string
 
  if (obj.seo_data) Object.keys(obj.seo_data).forEach(k => { obj[k] = obj.seo_data[k] }) // map seo_data to root field so that it can indexed (lunr can't index nested fields)
 
  if (obj.documentSource) obj.documentSource = {...obj.documentSource, type: source}
 
  fields.forEach(f => { obj[f] = obj[f] || '' }) // if any fields are null then it will impact lunr search results
 
  return obj
}
 
/**
 * Checks to see if certain functions are defined.
 *
 * @return {Boolean}
 * @memberOf plugin.freshdesk
 */
function isSupported () {
  return util.definedFunction(Object.keys) && util.definedFunction([].forEach)
}
 
/**
 * Contains functions for interacting with the freshdesk API and processing those responses.
 *
 * @namespace freshdesk
 * @memberOf plugin
 */
export {fields, plugin, process, fetch, isSupported}