Contributed by Jade Bujeya
My manager said: “I was thinking it might be good to have a pop-up that allows people to leave feedback on our dashboards.”
Like any true-born nerd, I said: “Pick me! Pick me!”
Google Forms are for lesser mortals.
Thus began my mission to add a pop-up extension to a Splunk dashboard. While I’m sure there are other ways to do this, my approach was to use a JavaScript extension which would provide the additional objects and functions I needed to make my pop-up interactive.
So, what is JavaScript and how can I add it to my dashboard?
According to Codecademy: “JavaScript is a programming language that adds dynamic functionality and complex features like interactivity and animation to web pages. Together with HTML and CSS, JavaScript forms the foundation of web development.”
Now web design is a broad and complex topic, worthy of its own blog post, or indeed its own blog. Essentially, HTML creates objects on the screen that you can see, CSS controls how these objects look, and JavaScript controls how they behave.
Theoretically, you can use HTML and Splunk source code to add buttons and popups to a dashboard, however I preferred to use the extensions method due to the potential for greater customisability. This is done by adding script=”my_code_file.js” inside the <form> or <dashboard> tag as seen in the example below:
For extra credit, you can also add stylesheet=”my_style_sheet.css” here to control the design of your JavaScript objects.
How can I create a pop-up in a Dashboard?
After a few internet searches, and with the help of our expansive and enthusiastic community of Splunkers, I quickly found an example I could use as a starting point, which created a ‘hidden’ object that only became visible when a button was clicked. Alternatively, this same functionality could be achieved by using the <change> and <condition> tags offered within the Dashboard Editor interface.
The ‘hidden’ object was a JavaScript constant that contained a long string of HTML describing the popup I wanted. This HTML was appended to the dashboard code, and then made visible when needed using one of several JavaScript functions outside the main constant with dictated functionality.
Amongst other things, such as confirming that field contents were valid, these functions would initiate the running of a search using the ‘outputlookup’ command. In this way, user responses could be added to a predefined lookup, enabling them to be displayed and utilised later.
In later phases of the project, I also built in a feature that would attempt to prevent a user from submitting feedback under someone else’s name. This works by creating a dummy search, which automatically runs when the user opens the popup. While the search produces no results, it does create a record in the _audit index. This record contains the Splunk username of the user who ran the dummy search, which is then added to the new line being created in the lookup as the ‘User’ who is submitting the feedback.
It goes without saying that this is not a foolproof approach, and is one which would likely become unreliable if many users were trying to leave feedback at the same time. Inevitably, there would be a small gap between the creation of the record in the _audit index and its retrieval, which might result in the wrong record from _audit being used to assign the user. However, for the relatively limited number of users in my case, it worked superbly.
So, Extension or Dashboard Editor?
Benefits of my approach:
- I could see and edit the entire pop-up as one block of text using simple and well-supported web design languages.
- I could easily integrate other JavaScript functions to control different aspects of the form’s behaviour and could edit all of it in one single document.
Drawbacks of my approach:
- This method required knowledge and understanding of both HTML and JavaScript.
- There were considerable complexities involved in passing HTML into a JavaScript constant, which would then be pasted as a HTML addendum at the bottom of a Splunk dashboard.
- Testing was a bit arduous, as to ensure Splunk recognises the changes you’ve made to a file, it is necessary to clear both the browser and server caches. While this can certainly be done, I found it faster to solve the problem by adding a constantly changing version number to the file name, so that Splunk would keep reading it as a new file.
- There were some characters (e.g. quotation marks etc.) that I had trouble passing into strings, as they would get confused while being passed from one language to another.
The resulting code used to add a pop-up extension to a Splunk dashboard is displayed below.
Happy Scripting!
require(['splunkjs/mvc',
'splunkjs/mvc/searchmanager',
'splunkjs/mvc/simplexml/ready!'],
// required packages
function(mvc, SearchManager){
var updateCSV = new SearchManager({
id: "updateCSV",
autostart: false,
cache:false,
search : "| makeresults | eval feedback=\"$setMsg$\" | eval dashboard=\"$setDsh$\" | eval name=\"$setName$\"| eval score=\"$setScore$\" | append [| search index=_audit | regex search=\"index=not_an_index sourcetype=not_a_sourcetype\" | fields user] | stats latest(_time) as _time, latest(dashboard) as dashboard, latest(feedback) as feedback, latest(name) as name, latest(score) as score, latest(user) as user | inputlookup append=true feedback.csv | outputlookup feedback.csv"
},{tokens: true});
//Creates function to run a search that creates an event with a series of fields populated by user entered tokens and then saves that event to a lookup file
//See Below: … | append [| search index=_audit | regex search=\"index=not_an_index sourcetype=not_a_sourcetype\" | fields user] | stats latest(_time) as _time, latest(dashboard) as dashboard, latest(feedback) as feedback, latest(name) as name, latest(score) as score, latest(user) as user
// This (^) goes into the _audit index, and retreives the 'user' based on a dummy search that is automatically run with your user context when you click
var markerSearch = new SearchManager({
id: "markerSearch",
autostart: false,
cache:false,
search : "| search index=not_an_index sourcetype=not_a_sourcetype",
},{tokens: false});
//this is the dummy search that creates a record in _audit
let params = (new URL(document.location)).searchParams;
const contents = document.createElement('contents');
// creates the below HTML and adds it to a variable called 'contents' seen below
contents.innerHTML = `
<div class="chat-popup" id="myForm">
<form class="form-container">
<h1>Feedback</h1>
<div style="display: table">
<div style="display: table-cell; padding-left: 10px">
<label for="user">
<b>Name</b>
</label>
<input type="text" name="name" id="name">
</input>
</div>
</div>
<div style="display: table">
<div style="display: table-cell; padding-left: 10px">
<label for="dshbrd">
<b>Good 10 - 1 Bad</b>
</label>
<input type="number" name="score" id="score" min="1" max="10" required>
</input>
</div>
<div style="display: table-cell; padding-left: 10px">
<label for="dshbrd">
<b>Select Dashboard:</b>
</label>
<select id="dshbrd">
<option>DashboardName1</option>
<option>DashboardName2</option>
<option>DashboardName3</option>
<option>DashboardName4</option>
<option>DashboardName5</option>
<option>DashboardName6</option>
<option>DashboardName7</option>
<option>DashboardName8</option>
<option>DashboardName9</option>S
<option>DashboardName10</option>
<option>DashboardName11</option>
</select>
</div>
</div>
<label for="msg">
<b>Message</b>
</label>
<textarea placeholder="Type Feedback..." name="msg" id="msgFeedback" onchange="msgFeedback.value = msgFeedback.value.replace(/"/g, '')" required></textarea>
<span id="validationFeebback"></span>
<button id="sbmtFeedback" type="button" class="btn">Submit</button>
<button id="cnclFeebackPopUP" type="button" class="btn cancel">Close</button>
</form>
</div>
`
$(document).find('.dashboard-body').append('<button id="feedback" class="btn btn-primary">Provide Feedback</button>');
// creates the feedback button and adds it to the screen. As this is a btn type item, the default behaviour is 'show'.
$(document).find('.dashboard-body').append(contents);
// adds contents to the screen. As 'contents' is a chat-popup type item, the default behaviour is 'hidden' (see .css file).
$("#feedback").on("click", function (){
$(document).find('.chat-popup').show();
markerSearch.startSearch();
// runs the above marker (dummy) search
});
$("#cnclFeebackPopUP").on("click", function (){
$(document).find('.chat-popup').hide();
window.location.reload(true);
});
// hides the popup when the user clicks cancel
$("#sbmtFeedback").on("click", function (){
var msg=$('#msgFeedback').val();
var dsh=$('#dshbrd').val();
var name=$('#name').val();
var score=$('#score').val();
if (msg.length<=10 || (msg.length==1 && msg==" ")){
$(document).find("#validationFeebback").text("Invalid Feedback. Must be more then 10 characters").css({'color':'red',});
}
else{
var tokens = splunkjs.mvc.Components.get("default");
tokens.set("setMsg", msg);
tokens.set("setDsh", dsh);
tokens.set("setName", name);
tokens.set("setScore", score)
markerSearch.startSearch();
// runs the above marker search (again, just to be safe)
updateCSV.startSearch();
// runs the search to add the new response to the feedback.csv file
$(document).find("#validationFeebback").text("Your feedback has been submitted..!").css({'color':'green'});
}
});
}
);