Using many dynamic components in a Splunk Web Framework Dashboard pretty much guarantees the heavy use of tokens. Tokens are like global variables in a programming language. They store stateful information as strings, and the components of a dashboard can be dependent on the token values. Suggested reading on tokens is here.

Tokens provide users the ability to drive drilldowns, hide / show HTML elements, share information between searches, etc.

As a developer, you need to know how your tokens are populating and if the token-dependent features work as expected. This is especially critical if you are using base searches to populate many elements of a dashboard.

Everything that follows is available for further examination in the Layer8Insight App for Splunk.

Base Search Inspection

Base searches (used correctly) make dashboards more efficient by limiting the number of queries executed by the Splunk Search Head. The point is to get the data once, and then process / inspect the data in numerous ways in order to populate a dashboard.

The standard way to use base searches in Splunk dashboards was to define them in the top of the dashboard as standalone, non-HTML elements -- behind the scenes drivers of the dashboards. The results of the base search are obtained from the assigned ID of the base search (the simpler way), or you could find the class object of the base search and extract the Splunk search ID or SID (the more complex way). Regardless of how you get the data, defining the a search at the top of the dashboard means you are limited in how you can inspect the search results when the dashboard runs -- you have to use the Job inspector.

The recommendation to define base searches at the top of a dashboard is only a recommendation, not a requirement. Any top-level search in a dashboard can be a base search whose events / data can be referenced elsewhere in the dashboard. Add in tokens, and you now have a handy way to inspect and debug base searches in a live dashboard.

Achieving this first requires moving a base search into an HTML element of a dashboard, like a table. Next, decide where you want to put the base search table in order to make debugging easy, e.g., near the dashboard elements that use the data. Lastly, we need a token to control when the debug view of the base search is enabled.

Here is an example of what the Simple XML looks like. Note the input checkbox "show_debug". This is our debug token controller, i.e., click the checkbox to turn on debugging. The token "$show_debug$" is used with the "depends" field to make the base search results visible only when the "$show_debug$" token is set, i.e., when the checkbox is checked. Unchecking the checkbox will hide the base search table.

Using this approach, you can use the depends="$show_debug$" setting on any HTML element of the dashboard (e.g., rows, panels, inputs, tables, charts, etc.) to toggle its visibility based on the debug checkbox setting.

    <form>
      ...
      <fieldset align="center" autoRun="true" submitButton="false">
        ...
        <input id="show_debug" searchWhenChanged="true" token="show_debug" type="checkbox">
          <label>Advanced Debug</label>
          <choice value="true">Enable</choice>
        </input>
      </fieldset>
      <row>
        <panel>
         <title>Useful Panel</title>
          <table depends="$show_debug$">
            <search id="base_search_example">
              <query>
                <YOUR_BASE_SEARCH>
              </query>
            </search>
          </table>
          <chart>
            <search base="base_search_example">
              <query>
                <DO_SOMETHING_TO_BASE_SEARCH_DATA_AND_REPORT>
              </query>
            </search>
          </chart>
        </panel>
      </row>
      ...
    </form>

Flexible Token Debugging

Now we can see how our base searches are populating, but that is only a small piece of debugging a dashboard. When you have a fair amount of tokens or any complex manipulation of tokens in a dashboard, understanding the state of tokens becomes very important.

Luckily, the Splunk team has already helped us all out with a token debugger. If you download the Splunk Example Dashboard app and go to the Token Viewer dashboard, you will see a table that allows one to inspect the values for any tokens defined in a dashboard. This token table can be added to any dashboard by copying the "showtokens.js" file from the Splunk app into your app's "appserver/static" folder. You must update the top-level "form" element of the dashboard's XML like the following segment to tell it to pull in this file.

    <form script="showtokens.js">

Reloading your dashboard should now reveal the token table at the bottom of the dashboard. Hooray!!!

Now, this is great for development, but not super-clean for production use. Let us now extend the dashboard's debugging checkbox to control the visibility of the token table.

First, you must now extend your dashboard with a custom JavaScript file. This means creating a new JavaScript file ("app.js" in this example) and placing it in your "appserver/static" folder. Now add the file to the "form" element like we did earlier.

    <form script="showtokens.js,app.js">

Here is an example file that will toggle the token table based on the debug checkbox. When debug is enabled, the token debugging table element (HTML ID of the table from showtokens.js is "show-tokens") is made visible. When debug is disabled, the token debugging table is hidden. Note, the logic will detect if the token table is present or not, so you can add or remove the "showtokens.js" file from the dashboard at any time without introducing errors.

    require([
        "underscore",
        "jquery",
        "splunkjs/mvc",
        "splunkjs/ready!",
        "splunkjs/mvc/simplexml/ready!",
    ], function(_, $, mvc,) {

      // Returns value of token 'name' in token model 'model'
      function getToken(name, model) {
        if (typeof name === 'undefined') {
          return undefined;
        }
        var tokens = mvc.Components.get((typeof model === 'undefined') ? 'default' : model);
        if (tokens) {
          return tokens.get(name);
        }
        return undefined;
      }

      // Return boolean answer to if 'checkval' matches 'newval'
      function checkTokenValue(checkval, newval) {
        return (newval === checkval ? true : false);
      }

      // Toggles visibility of HTML elements of a dashboard
      function hideElement(eleID, hide) {
        if (typeof eleID === 'undefined' || eleID.length === 0) {
          return;
        }

        var e = (eleID[0] === '#' ? eleID : '#' + eleID);
        if ($(e).length) {
          if (!!hide) {
            $(e).attr('style', 'visibility: hidden !important;');
          } else {
            $(e).attr('style', 'visibility: visible !important;');
          }
        }
      }

      // Toggle debug elements based on debug token setting
      // Returns boolean indicating if debug is enabled
      function toggleDebugSettings(name, value) {
        if (typeof name === 'undefined') {
          return;
        }

        var debug_enabled = checkTokenValue('true', value);
        hideElement('show-tokens', !debug_enabled);
        return debug_enabled;
      }

      var defaultTokenModel = mvc.Components.get('default');

      toggleDebugSettings(mvc, 'show_debug', getToken(mvc, 'show_debug'));

      defaultTokenModel.on("change:show_debug", function(model, value, options) {
        toggleDebugSettings(mvc, 'show_debug', value);
      });
    });