Adding an Audit Trail to a Knack App

I have been asked several times about logging activity and data changes within Knack applications and decided, after another prompt, to look into this and attempt to create something useful which I could share with the community and potentially apply to customer solutions.


Creating the User Log Object


The first part of this process is to create an object into which we can record the data:



If you're reading this article then I am going to assume you will understand from the above how to create the fields - the only point I would make is that the Login Date/Time should default to the current data and time (unless you prefer to set this time in the Javascript code of course!!).


Create a Page to view Log records with an Add Option


To make a secure audit log, we need to create a page with an 'add record' option. You should make this page secured but accessible to ALL logged in users (so that any user's updates can be recorded). It's your choice whether to make this page visible in the app - most likely not though - you can then add a view page for admins if you want. Make sure that the Add record page contains all the fields from the Object that are going to be updated as a part of the audit process:


Creating Code for the Log


If you've looked at Knack's developer documentation, you will have seen that there are two ways you can code updates of data - Object Based and View Based. For this purpose (and may others actually) you should use View Based code to do this (which is why we needed to create an Add Record view above). The reason for this is simple - Object Based code potentially exposes your app's API Key to the outside world which is a BAD idea.


I'm going to provide code in this post which will log either Scene renders (you opened a page) or View Submits (you saved something with a Submit button). If you want to extend this to include inline table edits or Action Link updates then this should also be possible. Here's the code:


//To use this code in your own app, create a table like the User Log in the blog post

//together with the associated scenes and then modify the

//following to match the scene, view and field numbers generated.

//In this app:

//scene_149 is the scene containing the Add User Log form

//view_226 is the Add User Log form

//field_252 is the field linking to the User in the User log object

//field_254 is the Scene Name field used when looging scene renders

//field_257 is the Type field containing Render or Submit in this example

//field_255 is the View name (used when logging submits)

//field_259 is the field into which we are storing the record's content (as JSON)


//this will store a record every time a scene is rendered (in selected list or use .all)

$(document).on('knack-scene-render.scene_70.scene_76', function(event, scene) {

var user = Knack.getUserAttributes().id;

if (user) {

$.ajax({

url: 'https://api.knack.com/v1/pages/scene_149/views/view_226/records',

type: 'POST',

async: false,

headers: {'X-Knack-Application-Id': 'xxxxxxxxxxxxxxxxxxxxxxxxx', 'X-Knack-REST-API-Key': 'knack', 'Authorization': Knack.getUserToken(), 'Content-Type': 'application/json'},

data: JSON.stringify({'field_252': [user], 'field_254': scene.name, 'field_257': 'Render'}),

success: function (response) {

console.log("Record created");

},

error: function(response){

console.log(response);

}

});

}

});


//this code will log any form submits (you could select the views using view_xxx.view_yyy)

$(document).on('knack-form-submit.any', function(event, view, record) {

var user = Knack.getUserAttributes().id;

if (user) {

$.ajax({

url: 'https://api.knack.com/v1/pages/scene_149/views/view_226/records',

type: 'POST',

async: false,

headers: {'X-Knack-Application-Id': 'xxxxxxxxxxxxxxxxxxxxxxxxx', 'X-Knack-REST-API-Key': 'knack', 'Authorization': Knack.getUserToken(), 'Content-Type': 'application/json'},

data: JSON.stringify({'field_252': [user], 'field_255': view.name, 'field_256': record.id, 'field_257': 'Submit', 'field_259': JSON.stringify(record)}),

success: function (response) {

console.log("Record created");

},

error: function(response){

console.log(response);

}

});

}

});


You should be able to paste this code directly into your Knack Javascript window. Having done this you will need to make the following changes:


  • Replace xxxxxxxxxxxxxxxxxxxxxxxxx with your App ID which you can find on the API tab of the API & Code window

  • Using the comments in the code as a guide, replace the scene, view and field IDs to match those for your own Add User Log form and User Log fields ( which you can find by looking at the URL in your browser when editing a scene, view or field).

  • I have set the two sections of the logging code up differently - in the first section, dealing with scenes rendered, my version only triggers for scene_70 and scene_76. If you want all scenes then replace these with .all or your own list of scenes. I have taken the opposite approach in the part logging submits - it logs any submit from any view.

  • You may have noticed the if test 'if(user) {' in both sections - this tests to see if there's a user logged in and only audits if there is. You can change this if you want but you won't be able to record who made a change obviously.

  • I have the submit process login record contents with 'field_259': JSON.stringify(record) which currently stores data in a long string - if I can find a method to tidy this up I'll re-visit this post and update it - any ideas most welcome!!


Using the Data


Depending on how you set up your table (filter options etc) you should now be able to show all changes to any record (filter by record ID), show all activity for any user - you could even add a field on the User / Account record to show the date of their latest activity.


Some Thoughts


My first thought here is that I would much prefer it if these features were standard in Knack! Of course the main word of caution here, though, is to consider the volume of data that this process is going to create (using up your record allowance). If you do implement this I would suggest a regular purge of the data (using the Builder delete features) for this reason.


I hope you've found this useful and look forward to seeing any comments or suggestions!


Find out more about Knack

0 views

© 2019 by Kirkness Associate Ltd