publish event when page has page problems

  • 1
  • Question
  • Updated 1 week ago
  • Answered
I would like to log all page problem errors that happen on our Skuid pages. My original idea was to subscribe to the event of a problem occurring on the page and then create a row in a model and save it with the data. However, I'm not sure if this is an event that I can subscribe to? I didn't see it as a listed event in the page.event api. 

Does anyone know how I can subscribe to problems on a page? Or a better way to log all problems that happen on Skuid pages?
Photo of Sam Becker

Sam Becker

  • 334 Points 250 badge 2x thumb

Posted 1 week ago

  • 1
Photo of Zach McElrath

Zach McElrath, Employee

  • 49,556 Points 20k badge 2x thumb
Hey Sam, that is a really good idea, we should publish events whenever there is an error so that you can deal with them either declaratively or with JavaScript, and so that you can handle them as they happen.

Right now, though, we don't publish any such event.

However, there is a JavaScript API that you could call to get all page problems: skuid.page.getAllProblems()

So you could potentially call this API on a timer from JavaScript and then do what you're saying. Add Inline JavaScript to the page like this:

(function(skuid){
   skuid.$(document.body).one('pageload',function(){
         var problemsModel = skuid.$M("ProblemsModel");

        // Poll every 2 seconds to check for problems
        var INTERVAL_FREQUENCY_IN_SECONDS = 2;
        
        setInterval(function(){
           var problems = skuid.page.getAllProblems();
           // If we have net-new problems since we last checked,
           // wipe out our current problems model and replace its contents
           if (problems.length !== problemsModel.getRows().length) {
              problemsModel.emptyData();
              problemsModel.adoptRows(problems);
           }
        }, INTERVAL_FREQUENCY_IN_SECONDS * 1000);
   });
})(skuid);

This assumes that you have a Model called "ProblemsModel" in your page (e.g. a Ui-Only Model) that has a field in it whose id is "message".
Photo of Sam Becker

Sam Becker

  • 334 Points 250 badge 2x thumb
Thanks Zach! This will work great.

Now, to actually save these to the database I'd use a custom object in Salesforce. However, I'm not sure the best way to let the JS know when to save. I don't want to connect it to any models that may be on the page since I'll be adding this JS as a static resource so I can easily use it on all of our pages . Is there an event that fires when a page is closed to where I can make sure it captures any errors on the page?

Thank you!
Photo of Sam Becker

Sam Becker

  • 334 Points 250 badge 2x thumb
Nevermind! I just subscribed to the 'beforeunload' event and it runs the function to save when someone closes the page. 

Thanks again!
Photo of Zach McElrath

Zach McElrath, Employee

  • 49,556 Points 20k badge 2x thumb
Sam,

If there is any alternative way of architecting this, i'd do that instead of trying to do something when the user leaves a page. Reason being, trying to do something when the user leaves a page is... complicated. Honestly, I think it would be prohibitively difficult to implement this on your own, if you're trying to send data to Salesforce.com. You would have to create a custom Apex REST Service to receive the data, and then expose that Apex REST Service via a Force.com Site... which means that anyone could technically send requests to that URL.

This blog post shows you could conceivably do this with JavaScript and what the considerations are. The biggest problem If all of your users are using modern browsers, navigator.sendBeacon() is available and should theoretically enable you to do this pretty easily. However, if your users are on mobile devices, or they're using older browsers, you'll need to accommodate those scenarios as well. This blog post does a good job of explaining those scenarios: http://usefulangle.com/post/62/javascript-send-data-to-server-on-page-exit-reload-redirect 

The basic approach, though, is to bind to the "unload" event that is fired on window, and then call navigator.sendBeacon()

$(window).on('unload', function() {
   navigator.sendBeacon("<salesforce api url>", JSON.stringify(skuid.page.getAllProblems()));
});

The biggest hurdle is that the Salesforce API URL needs to be reachable publicly, with no cookies / headers. So you might need to send in a page id/name, and possibly a user id / session id if you want to track which users are experiencing problems, in this custom Apex REST Service URL, so that you can correlate page problems to Users / Pages. 

At Skuid, one of your key 2019 objectives is to dramatically increase the amount of logging that we are doing throughout Skuid, and to be able to surface more of that to customers. I think that logging errors and page problems that users are encountering is a big part of that --- so really, we should be providing you with this level of insight, so you don't have to build it yourself. But... that's not where we are today.

So... all that said...

You might want to tweak the code above to create Page Problems in Salesforce as they are encountered, e.g. like this:

(function(skuid){
   skuid.$(document.body).one('pageload',function(){
        
        var LOG_OBJECT = "My_Log_Object__c";
        var MSG_FIELD = "Problem_Message__c";
        var USERNAME_FIELD = "Username__c";
        var PAGE_NAME_FIELD = "Page_Name__c";
        var logsModel = new skuid.model.Model({
            dataSource: skuid.dataSource.get("salesforce"),
            objectName: LOG_OBJECT,
            fields: [
                { id: "Id" },
                { id: MSG_FIELD },
                { id: USERNAME_FIELD },
                { id: PAGE_NAME_FIELD }
            ],
            conditions: [
                { field: USERNAME_FIELD, value: skuid.utils.userInfo.userName },
                { field: PAGE_NAME_FIELD, value: skuid.page.name }
            ],
            doQuery: false,
        });
        logsModel.initialize();

        // Poll every 5 seconds to check for problems
        var INTERVAL_FREQUENCY_IN_SECONDS = 5;
        
        logsModel.load().then(function() {
            setInterval(function(){
               var problems = skuid.page.getAllProblems();
               // If we have net-new problems since we last checked,
               // wipe out our current problems model and replace its contents
               if (problems.length) {
                  problems.forEach(function(problem) {
                     var logRow = logsModel.createRow();
                     logsModel.updateRow(logRow, MSG_FIELD, problem.message);
                  });
                  logsModel.save().then(function(saveResult) {
                      // If everything is saved successfully, clear out problems list
                      if (saveResult.totalsuccess) {
                          var pageComponents = skuid.component.getByType("skuidpage");
                          if (pageComponents.length) {
                              if (pageComponents[0] && pageComponents[0].removeProblems) {
                                  pageComponents[0].removeProblems();
                              }
                          }
                      }
                      // Otherwise, empty the logs model so we will try to save again later
                      logsModel.emptyData();
                  });
                  
               }
            }, INTERVAL_FREQUENCY_IN_SECONDS * 1000); 
        });
   });
})(skuid);



You will need to change the various constants to the names of the object / fields for your log storage object in salesforce. But this should give you the general idea.
Photo of Sam Becker

Sam Becker

  • 334 Points 250 badge 2x thumb
Awesome. Thanks again! 
Photo of Sam Becker

Sam Becker

  • 334 Points 250 badge 2x thumb
Curious why you are creating a row and then updating the row. I tried creating a row, but the fields didn't get filled in so I'm guessing that's why, but wondering why it doesn't work with just createRow()?
Photo of Zach McElrath

Zach McElrath, Employee

  • 49,556 Points 20k badge 2x thumb
Yeah createRow() right now does give you a way to prepopulate field values as part of the create step but it's a bit more cumbersome. It's like this:

logsModel.createRow({
   additionalConditions: [
       { field: MSG_FIELD, value: problem.message }
   ]
});

This way's actually more efficient, but it's a bit more wordy and less intuitive.
Photo of Sam Becker

Sam Becker

  • 334 Points 250 badge 2x thumb
For some reason when I do it with the snippet above and use my custom object. It saves, but the message and page fields don't save. I modified it to use the my field api names and made the page field a lookup to page  id instead of the name of the page.

I checked permissions on the fields and its all allowed. And if I add the model declarativly it works, but when I try to create the model through a snippet it doesn't save any of the field values. Here's my slightly modified code. 

(function(skuid){
   skuid.$(document.body).one('pageload',function(){
        
        var LOG_OBJECT = "Skuid_Page_Error__c";
        var MSG_FIELD = "Message__c";
        var PAGE_NAME_FIELD = "Page__c";
        var logsModel = new skuid.model.Model({
            dataSource: skuid.dataSource.get("salesforce"),
            objectName: LOG_OBJECT,
            fields: [
                { id: "Id" },
                { id: MSG_FIELD },
                { id: PAGE_NAME_FIELD }
            ],
            conditions: [
                { field: PAGE_NAME_FIELD, value: skuid.page.id }
            ],
            doQuery: false,
        });
        logsModel.initialize();

        // Poll every 5 seconds to check for problems
        var INTERVAL_FREQUENCY_IN_SECONDS = 5;
        
        logsModel.load().then(function() {
            setInterval(function(){
               var problems = skuid.page.getAllProblems();
               // If we have net-new problems since we last checked,
               // wipe out our current problems model and replace its contents
               if (problems.length) {
                  problems.forEach(function(problem) {
                     var logRow = logsModel.createRow();
                     logsModel.updateRow(logRow, MSG_FIELD, problem.message);
                  });
                  logsModel.save().then(function(saveResult) {
                      // If everything is saved successfully, clear out problems list
                      if (saveResult.totalsuccess) {
                          var pageComponents = skuid.component.getByType("skuidpage");
                          if (pageComponents.length) {
                              if (pageComponents[0] && pageComponents[0].removeProblems) {
                                  pageComponents[0].removeProblems();
                              }
                          }
                      }
                      // Otherwise, empty the logs model so we will try to save again later
                      logsModel.emptyData();
                  });
                  
               }
            }, INTERVAL_FREQUENCY_IN_SECONDS * 1000); 
        });
   });
})(skuid);

and field api names 


any idea what would be causing this?

Photo of Zach McElrath

Zach McElrath, Employee

  • 49,556 Points 20k badge 2x thumb
one thing to confirm --- the page you are doing this on does have another Model on the Salesforce Data Source, correct?
Photo of Sam Becker

Sam Becker

  • 334 Points 250 badge 2x thumb
Yep, I've tried it on 2 different pages. Both have models pointing to a different object in Salesforce.
Photo of Sam Becker

Sam Becker

  • 334 Points 250 badge 2x thumb
The message field value even shows up if I do a console log right after the logRow is created.