How to pull call logs with an API

  • 2
  • Question
  • Updated 1 year ago
  • Answered
I need to get RC call logs in a csv format every 15 minutes or less to my CRM can someone help with this?
Photo of YG Tech

YG Tech

  • 170 Points 100 badge 2x thumb

Posted 1 year ago

  • 2
Photo of Phong Vu

Phong Vu, Devangelist

  • 3,102 Points 3k badge 2x thumb
Hi,

Are you looking for a contractor to help with the work or are you asking for sample code showing how to implement that? If you need sample code, let me know what programming language do you expect.

+ Phong
Photo of YG Tech

YG Tech

  • 170 Points 100 badge 2x thumb
I was just asking for sample code; really any language would be fine. But if you could get this up and working for me very quickly we might be able to pay for it.
Photo of Phong Vu

Phong Vu, Devangelist

  • 3,102 Points 3k badge 2x thumb
Hi,

Here is the working code in Node JS. 

You need to install the RingCentral SDK for Node JS
npm install ringcentral --save

and install the dotenv so you can use the .env to keep your app and login credentials

Code:
var RC = require('ringcentral')
require('dotenv').load()

var rcsdk = new RC({
  server:process.env.SERVER,
  appKey: process.env.APP_KEY,
  appSecret:process.env.APP_SECRET
})

var platform = rcsdk.platform()

login()
function login() {
  platform.login({
    username:process.env.USERNAME,
    password:process.env.PASSWORD
  })
  .then(function(resp){
      readCallLog()
      setInterval(function(){
        readCallLog()
      }, 60 * 15 * 1000); // repeat reading call-log every 15 mins
  })
  .catch(function(e){
    throw e
  })
}

function readCallLog(){
  var date = new Date()
  var time = date.getTime()
  // 15-min period
  var less15Min = time - (60 * 15 * 1000)
  var from = new Date(less15Min)
  var dateFrom = from.toISOString()
  var dateTo = date.toISOString()

  var params = {}
  // See the API reference for more request parameters
  params['type'] = 'Voice'
  params['view'] = 'Detailed'
  params['dateFrom'] = dateFrom.replace('/', ':')
  params['dateTo'] = dateTo.replace('/', ':')
  console.log(params.dateFrom)
  console.log(params.dateTo)
  platform.get('/account/~/extension/~/call-log', params)
  .then(function(resp){
    var json = resp.json()
    if (json.records.length > 0){
      // Check the API documentation for call log data fields then add whatever you are interested
      var cvs = 'uri,startTime,duration,type,direction,action,result,to_name,from_name,transport'
      for (var record of json.records){
        cvs += "\r\n"
        cvs += record.uri + ','
        cvs += record.startTime + ','
        cvs += record.duration + ','
        cvs += record.type + ','
        cvs += record.direction + ','
        cvs += record.action + ','
        cvs += record.result + ','
        if (record.to.name != undefined)
          cvs += record.to.name + ','
        else
          cvs += 'null,'
          if (record.from.name != undefined)
            cvs += record.from.name + ','
          else
            cvs += 'null,'
        cvs += record.transport
      }
      var fs = require('fs')
      // add to your CRM db
      // for demo, I write the data to a file
      fs.writeFile('call_log_'+dateTo+'.csv', cvs, function(err) {
        if(err)
          console.log(err);
        else
          console.log("call log is saved.");
      })
    }
  })
  .catch(function(e){
    throw e
  })
}

I am here to help you. So no payment :)

Bests,
Phong
Photo of YG Tech

YG Tech

  • 170 Points 100 badge 2x thumb
Thank you so much for this you're Awesome.
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
Hello, this is very helpful.  The code appears to run by displaying the date to console, however a CSV file never saves.
Photo of Phong Vu

Phong Vu, Devangelist

  • 1,936 Points 1k badge 2x thumb
It looks like you don't have any record and that's why it does not enter the if (json.records.length > 0) loop. Otherwise, there might be some restriction in your system that prevent the fs.writeFile function to create a new file.

Please double check and let me know.

Phong
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
I had to reinstall the fs module and now it's working.  For some reason the file is saving with a / in the filename and is invalid.
For example, call_log_2017-10-12T13/18/19.018Z.csv.  Why would the time not use the same - as the date?
Photo of Phong Vu

Phong Vu, Devangelist

  • 1,936 Points 1k badge 2x thumb
Hi Steve,
The file name I chose was just for convenient. And it works on MacOS. You can simply modify it e.g. var filename = dateTo.replace('/', '-'). The most important is the date and time format for the request dateFrom and dateTo parameters. They must be in ISO 8601 format e.g. 2017-10-12T18:07:52.534Z. That's why I replace the / with : 

params['dateFrom'] = dateFrom.replace('/', ':')
params['dateTo'] = dateTo.replace('/', ':')
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
I should have mentioned, I tried adding the replace function with
var filename = dateTo.replace('/','-')
fs.writeFile('call-Log'+filename+'.csv', cvs, function(err) 

I've also tried with regex
var filename = dateTo.replace(/\//g,'-')

The date is always saved with - and the time with /.  Even if I don't do either replace function, it still saves with this format.
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
I was able to get it to display the time without the / using this code.  It's probably more complicated than it needs to be, but it's working.  Thanks again for your help with this!

var today = new Date();
var date = today.getFullYear()+''+(today.getMonth()+1)+''+today.getDate()
var time = today.getHours() + '' + today.getMinutes() + '' + today.getSeconds()
var dateTime = date+' '+time

fs.writeFile('RingCentralCallLog'+dateTime+'.csv', cvs, function(err) {
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
+Phong, how would you pull the last 24 hours call log vs last 15 minutes?  Since time is in milliseconds, I can't widen it to day without crashing it.
Photo of Phong Vu

Phong Vu, Devangelist

  • 1,936 Points 1k badge 2x thumb
If you want the last 24hrs, simply drop the dateFrom and dateTo parameters so the default one will be used and that default is set for 24hrs (https://developer.ringcentral.com/api-docs/latest/index.html#!#RefUserCallLog.html)

If you want other than the default, like this one set for 15-min period, then adjust the dateFrom time. E.g.

// 2-hr period
var less2hrs = time - (60 * 120 * 1000) // where time is the current time
var from = new Date(less2hrs)

Also, remember to change the setInterval to call readCallLog() every 2 hrs.

setInterval(function(){
        readCallLog()
      }, 60 * 120 * 1000); // repeat reading call-log every 2 hrs


+ Phong
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
Unfortunately the max time period allowed is 39 minutes, anything higher crashes.  It must have something to do with the fact that it's working in milliseconds.

Also it appears the params dateFrom and dateTo are required because it will not run without them.  I think it wants those specific ISOString values as inputs.
Photo of Phong Vu

Phong Vu, Devangelist

  • 1,936 Points 1k badge 2x thumb
That's weird, this works for me with any number

var lessTime = time - (60 * 15 * 1000) // 15 mins
var lessTime = time - (60 * 40 * 1000) // 40 mins
var lessTime = time - (60 * 60 * 1000) // 60 mins

Works without the dates too
//params['dateFrom'] = dateFrom.replace('/', ':')
//params['dateTo'] = dateTo.replace('/', ':')

The millisecond is just for creating a Date object to define the fromDate.

Make sure the dateFrom must be older date/time than the dateTo.

Can you give details of how it crashes and what is the error if any.
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
It's worth mentioning I am hitting the production API https://platform.ringcentral.com
Here is what I get when I take a working script and comment out the two params.
undefined
undefined
(node:8868) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property 'name' of undefined
(node:8868) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

And here's what I get when the time is set to a higher number.
2017-10-12T00:05:55.757Z
2017-10-13T00:05:55.757Z
(node:8939) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property 'name' of undefined
(node:8939) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
(Edited)
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
Good morning, I resolved the errors by changing the last line in your code from
.catch(function(e){
    throw e
  })

TO
.catch((error) => {
assert.isNotOk(error,'Promise error')
done()
})

It appears there is a default limit of 100 records.  If I set perPage to 1000, is it possible to pull multiple pages in one request?
Photo of Phong Vu

Phong Vu, Devangelist

  • 1,936 Points 1k badge 2x thumb
Oh wow. Congrats!

Surely, my snippet code does not handle errors. For the page, you can set the perPage to a higher number as you did. I am not sure if you can pull multiple pages in one request. You can try putting the page in an array [1,2,3] and try to see if it works. Probably not.

+ Phong
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
Hello, thanks for sticking with me.  The error handling above only masked the true issue.  After troubleshooting further, the error TypeError: Cannot read property 'name' of undefined is referring to record.from.name.  If I shorten the timeframe to 15 minutes, it works fine but if I expand the time beyond a certain point, it throws the error.  So there's some records in there that it can't read.  Any ideas?
Photo of Phong Vu

Phong Vu, Devangelist

  • 1,936 Points 1k badge 2x thumb
I think it is because some records out of that 15-min period does not have a 'name'  object in the 'from' or the 'to' object. And maybe your strict code does not like the check if (record.to.name != undefined)

Can you try this code

if (record.to.hasOwnProperty('name'))
  cvs += record.to.name + ','
else
  cvs += 'null,'
if (record.from.hasOwnProperty('name'))
  cvs += record.from.name + ','
else
  cvs += 'null,'

Let me know.
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
I enabled one record at a time and record.to.phoneNumber and record.to.name are populated 'null' where there was no phone number or name.  It was previously inserting 'undefined'. 

But when I enable record.from.phoneNumber, or record.from.name i get:
TypeError: Cannot read property 'hasOwnProperty' of undefined

Do you think it could be bad data?
Photo of Phong Vu

Phong Vu, Devangelist

  • 1,936 Points 1k badge 2x thumb
Can you just print each record data and see if the from is completely missing. By the way, have a look at the api reference to see more details of the response. I think name and phone number is not always present in the from. It can contain just the extension number.
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
I used Postman to pull the JSON body and you are correct some records don't contain a name, location, or both.  So how can we change your parse code to just skip or ignore those records if they don't exist?
Photo of Phong Vu

Phong Vu, Devangelist

  • 1,936 Points 1k badge 2x thumb
This should work
if (record.hasOwnProperty('from')){
if (record.from.hasOwnProperty('name'))
...
}

Or simply
if (record.from !== undefined){
If (record.from.name !== undefine)
...
}

Curious, whatbtype of call log is that? Why the from object does not exist?
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
Thank you, this got it working!

if (record.hasOwnProperty('to')){
if (record.to.hasOwnProperty('phoneNumber'))
cvs += record.to.phoneNumber + ','
}

if (record.hasOwnProperty('from')){
if (record.from.hasOwnProperty('phoneNumber'))
cvs += record.from.phoneNumber + ','
}

if (record.hasOwnProperty('from')){
if (record.from.hasOwnProperty('name'))
cvs += record.from.name + ','
}

if (record.hasOwnProperty('from')){
if (record.to.hasOwnProperty('name'))
cvs += record.to.name + ','
}

This is just the standard /restapi/v1.0/account/~/call-log using the 'detailed' parameter.
It appears not all the data is available for every call.  Also, randomly(1 in 1000) the record.to.name includes a comma in the response which is why I put it at the end because it adds another column.

I've posted the working javascript and Postman collection here
https://github.com/steveermish/RingCentral-REST-API
Photo of Phong Vu

Phong Vu, Devangelist

  • 1,936 Points 1k badge 2x thumb
Why don't you just replace a comma "," with a dot "." or just a space " "?
Also, add the else after if condition and add an empty column or filled with null ",null," so your csv data will be parsed easily later.
(Edited)
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
Because I need the comma as the delimiter to load into the database.  I'll add null at the end, that's a good idea.
Photo of Phong Vu

Phong Vu, Devangelist

  • 1,936 Points 1k badge 2x thumb
Saying replace a comma with a dot I meant the comma inside the name or any content. E.g. if the name is "+1 (123) 222-3456, John Smith", you can replace with "+1 (123) 222-3456. John Smith"
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
I need the comma between record.from.phoneNumber and record.from.name in order to keep the columns separated in the file.  What's happening is only under record.to.name, a name randomly downloads as john,smith where every other record is john smith.
Photo of Phong Vu

Phong Vu, Devangelist

  • 1,936 Points 1k badge 2x thumb
That is what I meant. Sometimes I saw a phone number in the name field so I just put the example there. In your case, if the name is "John,Smith" then you should replace the comma "," with a space e.g. record.from.name.replace(",", " ") before adding it to your csv file.
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
Oh I understand now.  Yes that's what I needed to know, thank you so much.  Here is the proper code.

if (record.hasOwnProperty('from')) {
if (record.from.hasOwnProperty('location'))
cvs += record.from.location.replace(",", " ") + ','
else cvs += ','
}
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
+Phong, do you know how I can write the file without the header?
Photo of Phong Vu

Phong Vu, Devangelist

  • 1,936 Points 1k badge 2x thumb
Do you mean this header?
var cvs = 'uri,startTime,duration,type,direction,action,result,to_name,from_name,transport'

If yes, then simply specify an empty string
var cvs = ''
but if you want to append new data to the existing file without the header, then use appendFile function
fs.appendFile('file.csv', 'data to append', function (err) {
  if (err) throw err;
  console.log('Saved!'); 
});

Does this help?
+ Phong
(Edited)
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
Thanks, yes I mean the header.  When using var cvs='' it just creates a blank first row.  I need the header to be removed completely.  Is that possible?
(Edited)
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
Hi Phong, do you know how to just remove the header row completely?
Photo of Phong Vu

Phong Vu, Devangelist

  • 1,936 Points 1k badge 2x thumb
I showed you above, just specify an empty string var cvs=''; before appending data row to it. Also move the cvs += "\r\n" to the end of each loop and don't add it to the last row.
Photo of Steve Ermish

Steve Ermish

  • 282 Points 250 badge 2x thumb
Ok thank you, once I moved the \r\n to the end, it removed the first row.  After pulling logs for a few days, I noticed the CSV column data is misaligned 1 to 2 of every 1000 rows.  Any ideas what could cause that?
Photo of Phong Vu

Phong Vu, Devangelist

  • 1,936 Points 1k badge 2x thumb
You have to check what field is missing. Double check all your if () blocks must have an else, and make sure you add a "," to separate every field, be it having a value or not. Also double check your code where you escape commas "," in the value.
Photo of YG Tech

YG Tech

  • 170 Points 100 badge 2x thumb
Thank you all for the help we are working to implement this into our CRM. Thank you again for your help.
Photo of Don Jackson

Don Jackson

  • 160 Points 100 badge 2x thumb
 Phong Vu, this is working great, but it only pulls down 100 records maximum. I increase the time to more than 15 minutes and I sometimes have more than 100 calls in the time period. How do set the number of records to pull?
Photo of Phong Vu

Phong Vu, Devangelist

  • 2,596 Points 2k badge 2x thumb
Hi Don,

You can set the perPage value to > 100. The default value is 100.
https://developer.ringcentral.com/api-docs/latest/index.html#!#RefUserCallLog.html

Cheers,
Phong