All Apps and Add-ons

Save input settings on dashboard to kv store - State capture

mwdbhyat
Builder

Hi there,

I have a dashboard that is populated with 4 inputs.

  1. Data type drop down with static list - this is the main filter
  2. multivalue selection based on case statement
  3. Text input box
  4. Time range picker

Basically I want a save button so that when a user filters data by data type(input 1) it remembers all the other settings for that data type(all the other input criteria like time, text input etc). So in a list of say 10 data types, each time i click on one of them it will remember the last settings that were saved(Basically state capture) to make it efficient when opening the panel.

Ideally stored in the kv store..
I have checked out this: https://answers.splunk.com/answers/617305/how-can-i-give-the-to-the-users-to-save-their-sele.html

Which gave me a brief understanding of how to do it, but not how to do it with everything based on remembering data input 1's settings.

Anyone know how to accomplish this?

Thanks

1 Solution

splunkian
Path Finder

I like @niketn's original solution, but I figured I'd throw my hat into the ring since it looks like @mwdbhyat might still be facing some issues.

Here is how I would tackle this particular problem. The solution assumes the following:
- The configuration settings are based on a dropdown input's selection
- Multivalue field, text input, and time-range picker should be populate based on the dropdown selection.

The following type of static data is used for demo purposes:
- Time: A specific time range consisting of earliest and latest time.
- Car Type: A specific brand of car e.g. Ford, Chevy, Honda etc. This field is the dropdown. Based on the
car's type, the other values are populated accordingly.
- Car Model: Model of a specific type of car, e.g. Ford F150, or Honda Accord.
- Car Color: Should be self explanatory 🙂

Define a KVstore in 'collections.conf' called 'dashboard_input_settings':

[dashboard_input_settings]
field.car_type = string
field.car_model = string
field.car_color = string
field.time_earliest = string
field.time_latest = string
field.saved_at = string

In the 'collections.conf' car_type, car_model, car_color, time_earliest, time_latest are all based on values
that are provided through the inputs on the dashboard.

The value of 'saved_at' will contain the _time of when the configuration is saved to ensure we can sort and
then dedup older values. This way we wont have duplicate configurations for one 'car_type'.

Define a lookup definition for the KVstore in 'transforms.conf':

[dashboard_input_settings]
collection = dashboard_input_settings
external_type = kvstore
fields_list = _key,car_type,car_model,car_color,time_earliest,time_latest,saved_at

Define the dashboard -- (with tons of comments, yay!):

<!-- load in the JavaScript file -->
<form script="input_settings.js">
  <!-- 
  On dashboard init, set the check_settings token to true so we can immediately check if the dropdowns value has a configuration
  This token is 'depended' on for the 'check_for_saved_settings_search' to run below
  -->
  <init>
    <set token="check_settings">true</set>
  </init>
  <label>Demo</label>
  <!-- This search saves the configuration to the KVstore. It depends on all the necessary tokens being set before running. -->
  <search id="save_dashboard_settings_search" depends="$save_config$,$time_tok.earliest$,$time_tok.latest$,$car_model_tok$,$car_type_tok$,$car_color_tok$">
    <!-- 
    The search looks up the current values in the KVstore's lookup definition, appends our new values to the bottom, and uses makeresults to 
    fake a new row result which adds a _time field. We set the saved_at to the _time (now), which we then use to sort the results newest to oldest, dedup them
    which removes the oldest results ensuring no duplicate entries so it effectively 'updates' any pre-existing results, or appends a new result if a specific
    'car_type' doesn't exist
    -->
    <query>
    | inputlookup dashboard_input_settings | append [| makeresults | eval saved_at=_time | eval time_earliest="$time_tok.earliest$" | eval time_latest="$time_tok.latest$" | eval car_model="$car_model_tok$" | eval car_type="$car_type_tok$" | eval car_color="$car_color_tok$"] | fields - _time | sort 0 - saved_at | dedup car_type | outputlookup dashboard_input_settings
    </query>
  </search>
  <!-- Check if the currently selected 'car_type' dropdown has a saved configuration in the KVstore. -->
  <search id="check_for_saved_settings_search" depends="$car_type_tok$,$check_settings$">
    <!-- 
    The search checks to see if a specific 'car_type' selected in our dropdown exists in the KVstore. If it does, then ouput the necessary values for
    that particular 'car_type' 
    -->
    <query>
      | inputlookup dashboard_input_settings WHERE car_type="$car_type_tok$" | table car_model, car_color, time_earliest, time_latest
    </query>
    <!-- When the search is finalized, we check if a result is returned or not -->
    <finalized>
      <!-- If one result is returned, then set the necessary tokens -->
      <condition match="'job.resultCount' == 1">
        <set token="form.time_tok.earliest">$result.time_earliest$</set>
        <set token="form.time_tok.latest">$result.time_latest$</set>
        <!-- 'car_model_tok' is not set here as its a multi-valued field, which is handled in the JavaScript -->
        <set token="form.car_color_tok">$result.car_color$</set>
        <unset token="check_settings"></unset>
      </condition>
      <!-- 
      If no results are returned that means we have no configuration defined for the 'car_type' dropdown so we will unset 
      tokens and then set a default time for the timepicker
      -->
      <condition match="'job.resultCount' == 0">
        <set token="form.time_tok.earliest">-24h</set>
        <set token="form.time_tok.latest">now</set>
        <unset token="form.car_model_tok"></unset>
        <unset token="form.car_color_tok"></unset>
        <unset token="check_settings"></unset>
      </condition>
    </finalized>
  </search>
  <!-- Input fields -->
  <fieldset submitButton="false">
    <input id="timepicker" type="time" token="time_tok" searchWhenChanged="true">
      <label>Time Range</label>
      <default>
        <earliest>-24h@h</earliest>
        <latest>now</latest>
      </default>
    </input>
    <input type="dropdown" token="car_type_tok" searchWhenChanged="true">
      <label>Car Type</label>
      <choice value="ford">Ford</choice>
      <choice value="chevy">Chevy</choice>
      <choice value="honda">Honda</choice>
      <choice value="toyota">Toyota</choice>
      <choice value="tesla">Tesla</choice>
      <change>
        <set token="check_settings">true</set>
      </change>
    </input>
    <input type="multiselect" token="car_model_tok" id="car_model_multiselect">
      <label>Car Model</label>
      <choice value="tahoe">Tahoe</choice>
      <choice value="impala">Impala</choice>
      <choice value="escape">Escape</choice>
      <choice value="taurus">Taurus</choice>
      <choice value="f150">F-150</choice>
      <choice value="accord">Accord</choice>
      <choice value="pilot">Pilot</choice>
      <choice value="passport">Passport</choice>
      <choice value="civic">Civic</choice>
      <choice value="rav4">Rav4</choice>
      <choice value="corolla">Corolla</choice>
      <choice value="model3">Model3</choice>
      <choice value="modelS">ModelS</choice>
      <choice value="modelX">modelX</choice>
      <delimiter> </delimiter>
    </input>
    <input type="text" token="car_color_tok">
      <label>Car Color</label>
    </input>
  </fieldset>
</form>

Next, create the JavaScript file called 'input_settings.js' in the appserver/static folder of whatever app you're working in.

require(
    [
        'jquery',
        'splunkjs/mvc'
    ],
    function ($, mvc) {

        // Create an instance of the save button
        const save_config_button = $(`<button id="saveConfigButton" class="btn btn-primary">Save Configuration</button>`);

        // Success message indicating config was saved
        const success_message = $(`
        <div id="successMessage" style="padding: 10px; background: #5cc05c; color: #FFFFFF; display: none">
            Successfully saved the configuration!
        </div>`);

        // Instance of submitted tokens otherwise known as $form.tok$ in dashboard
        const submitted_tokens = mvc.Components.get('submitted');

        // Instance of the multiselect -- we need this to populate it from the KV store when we retrieve the settings
        const car_model_multiselect = mvc.Components.get('car_model_multiselect');

        // Instances of searches in our dashboard referenced by their ids
        const save_dashboard_settings_search = mvc.Components.get('save_dashboard_settings_search');
        const check_for_saved_settings_search = mvc.Components.get('check_for_saved_settings_search');

       /*
        * Get the results of a matching setting in the KV. This search is called on the dashboard init event
        * as well as when the multi-valued field 'car_model_multiselect' is selected
        */
        const check_for_saved_settings_results = check_for_saved_settings_search.data("results", {count: 0});

        // Append the button to the dashboard
        $('.dashboard-form-globalfieldset').append(save_config_button);

        // Handle the click event on the save_config_button
        save_config_button.on('click', function () {
            // Disable it while the save action is occurring
            save_config_button.prop('disabled', true);
            // Change the text of the button to indicate its saving
            save_config_button.text('Saving...');
            /*
            Sets the save_config token so that the 'save_dashboard_settings_search' runs -- this is
            referenced in the depends of the 'save_dashboard_settings_search' e.g. depends="$save_config$"
            */
            submitted_tokens.set({'save_config': true});
            // Now run the search to save the settings
            save_dashboard_settings_search.startSearch();
        });

        // Listen for the search to complete
        save_dashboard_settings_search.on('search:done', function () {
            // Re-enable the save button
            save_config_button.prop('disabled', false);
            // Change the text back to default
            save_config_button.text('Save Configuration');
            // Prepend the success message
            $('.dashboard-form-globalfieldset').prepend(success_message);
            // Success message hidden by default - fade it in over 1 second, show for 3 seconds, then fade out 1 sec.
            success_message.fadeIn(1000).delay(3000).fadeOut(1000);
            // Unset the 'save_config' token so the save config search doesn't unintentionally run
            submitted_tokens.unset('save_config');
        });

        /*
        * Wait for the check_for_saved_settings_results search to get data
        */
        check_for_saved_settings_results.on("data", function () {
            // Get the results of data
            let rows = check_for_saved_settings_results.data().rows;
            // Loops through the results
            rows.forEach(function (row) {
                // Turn result into an array
                let car_models = row[0].split(' ');
                // Populate the multi-select with the value of all the models
                car_model_multiselect.val(car_models);
            });

        });

    });

There is also a full demo app for this on Github here: https://github.com/deviansg/splunk_dashboard_input_settings in case you'd like to just have a working version to base everything off of.

View solution in original post

woodcock
Esteemed Legend

Why is this necessary? The token values are stored as URIs on your URL so you can just bookmark any set of values at any time and return to them via bookmark. Isn't that good enough?

0 Karma

jeffland
SplunkTrust
SplunkTrust

Storing user settings on the server can serve different purposes than having them on the client. You could find arguments in favor of one or the other, I don't see how either method is superior. If having them on the client is "good enough" for you, then by all means do that - I'm still thankful for the effort @splunkian has put in.

0 Karma

splunkian
Path Finder

I like @niketn's original solution, but I figured I'd throw my hat into the ring since it looks like @mwdbhyat might still be facing some issues.

Here is how I would tackle this particular problem. The solution assumes the following:
- The configuration settings are based on a dropdown input's selection
- Multivalue field, text input, and time-range picker should be populate based on the dropdown selection.

The following type of static data is used for demo purposes:
- Time: A specific time range consisting of earliest and latest time.
- Car Type: A specific brand of car e.g. Ford, Chevy, Honda etc. This field is the dropdown. Based on the
car's type, the other values are populated accordingly.
- Car Model: Model of a specific type of car, e.g. Ford F150, or Honda Accord.
- Car Color: Should be self explanatory 🙂

Define a KVstore in 'collections.conf' called 'dashboard_input_settings':

[dashboard_input_settings]
field.car_type = string
field.car_model = string
field.car_color = string
field.time_earliest = string
field.time_latest = string
field.saved_at = string

In the 'collections.conf' car_type, car_model, car_color, time_earliest, time_latest are all based on values
that are provided through the inputs on the dashboard.

The value of 'saved_at' will contain the _time of when the configuration is saved to ensure we can sort and
then dedup older values. This way we wont have duplicate configurations for one 'car_type'.

Define a lookup definition for the KVstore in 'transforms.conf':

[dashboard_input_settings]
collection = dashboard_input_settings
external_type = kvstore
fields_list = _key,car_type,car_model,car_color,time_earliest,time_latest,saved_at

Define the dashboard -- (with tons of comments, yay!):

<!-- load in the JavaScript file -->
<form script="input_settings.js">
  <!-- 
  On dashboard init, set the check_settings token to true so we can immediately check if the dropdowns value has a configuration
  This token is 'depended' on for the 'check_for_saved_settings_search' to run below
  -->
  <init>
    <set token="check_settings">true</set>
  </init>
  <label>Demo</label>
  <!-- This search saves the configuration to the KVstore. It depends on all the necessary tokens being set before running. -->
  <search id="save_dashboard_settings_search" depends="$save_config$,$time_tok.earliest$,$time_tok.latest$,$car_model_tok$,$car_type_tok$,$car_color_tok$">
    <!-- 
    The search looks up the current values in the KVstore's lookup definition, appends our new values to the bottom, and uses makeresults to 
    fake a new row result which adds a _time field. We set the saved_at to the _time (now), which we then use to sort the results newest to oldest, dedup them
    which removes the oldest results ensuring no duplicate entries so it effectively 'updates' any pre-existing results, or appends a new result if a specific
    'car_type' doesn't exist
    -->
    <query>
    | inputlookup dashboard_input_settings | append [| makeresults | eval saved_at=_time | eval time_earliest="$time_tok.earliest$" | eval time_latest="$time_tok.latest$" | eval car_model="$car_model_tok$" | eval car_type="$car_type_tok$" | eval car_color="$car_color_tok$"] | fields - _time | sort 0 - saved_at | dedup car_type | outputlookup dashboard_input_settings
    </query>
  </search>
  <!-- Check if the currently selected 'car_type' dropdown has a saved configuration in the KVstore. -->
  <search id="check_for_saved_settings_search" depends="$car_type_tok$,$check_settings$">
    <!-- 
    The search checks to see if a specific 'car_type' selected in our dropdown exists in the KVstore. If it does, then ouput the necessary values for
    that particular 'car_type' 
    -->
    <query>
      | inputlookup dashboard_input_settings WHERE car_type="$car_type_tok$" | table car_model, car_color, time_earliest, time_latest
    </query>
    <!-- When the search is finalized, we check if a result is returned or not -->
    <finalized>
      <!-- If one result is returned, then set the necessary tokens -->
      <condition match="'job.resultCount' == 1">
        <set token="form.time_tok.earliest">$result.time_earliest$</set>
        <set token="form.time_tok.latest">$result.time_latest$</set>
        <!-- 'car_model_tok' is not set here as its a multi-valued field, which is handled in the JavaScript -->
        <set token="form.car_color_tok">$result.car_color$</set>
        <unset token="check_settings"></unset>
      </condition>
      <!-- 
      If no results are returned that means we have no configuration defined for the 'car_type' dropdown so we will unset 
      tokens and then set a default time for the timepicker
      -->
      <condition match="'job.resultCount' == 0">
        <set token="form.time_tok.earliest">-24h</set>
        <set token="form.time_tok.latest">now</set>
        <unset token="form.car_model_tok"></unset>
        <unset token="form.car_color_tok"></unset>
        <unset token="check_settings"></unset>
      </condition>
    </finalized>
  </search>
  <!-- Input fields -->
  <fieldset submitButton="false">
    <input id="timepicker" type="time" token="time_tok" searchWhenChanged="true">
      <label>Time Range</label>
      <default>
        <earliest>-24h@h</earliest>
        <latest>now</latest>
      </default>
    </input>
    <input type="dropdown" token="car_type_tok" searchWhenChanged="true">
      <label>Car Type</label>
      <choice value="ford">Ford</choice>
      <choice value="chevy">Chevy</choice>
      <choice value="honda">Honda</choice>
      <choice value="toyota">Toyota</choice>
      <choice value="tesla">Tesla</choice>
      <change>
        <set token="check_settings">true</set>
      </change>
    </input>
    <input type="multiselect" token="car_model_tok" id="car_model_multiselect">
      <label>Car Model</label>
      <choice value="tahoe">Tahoe</choice>
      <choice value="impala">Impala</choice>
      <choice value="escape">Escape</choice>
      <choice value="taurus">Taurus</choice>
      <choice value="f150">F-150</choice>
      <choice value="accord">Accord</choice>
      <choice value="pilot">Pilot</choice>
      <choice value="passport">Passport</choice>
      <choice value="civic">Civic</choice>
      <choice value="rav4">Rav4</choice>
      <choice value="corolla">Corolla</choice>
      <choice value="model3">Model3</choice>
      <choice value="modelS">ModelS</choice>
      <choice value="modelX">modelX</choice>
      <delimiter> </delimiter>
    </input>
    <input type="text" token="car_color_tok">
      <label>Car Color</label>
    </input>
  </fieldset>
</form>

Next, create the JavaScript file called 'input_settings.js' in the appserver/static folder of whatever app you're working in.

require(
    [
        'jquery',
        'splunkjs/mvc'
    ],
    function ($, mvc) {

        // Create an instance of the save button
        const save_config_button = $(`<button id="saveConfigButton" class="btn btn-primary">Save Configuration</button>`);

        // Success message indicating config was saved
        const success_message = $(`
        <div id="successMessage" style="padding: 10px; background: #5cc05c; color: #FFFFFF; display: none">
            Successfully saved the configuration!
        </div>`);

        // Instance of submitted tokens otherwise known as $form.tok$ in dashboard
        const submitted_tokens = mvc.Components.get('submitted');

        // Instance of the multiselect -- we need this to populate it from the KV store when we retrieve the settings
        const car_model_multiselect = mvc.Components.get('car_model_multiselect');

        // Instances of searches in our dashboard referenced by their ids
        const save_dashboard_settings_search = mvc.Components.get('save_dashboard_settings_search');
        const check_for_saved_settings_search = mvc.Components.get('check_for_saved_settings_search');

       /*
        * Get the results of a matching setting in the KV. This search is called on the dashboard init event
        * as well as when the multi-valued field 'car_model_multiselect' is selected
        */
        const check_for_saved_settings_results = check_for_saved_settings_search.data("results", {count: 0});

        // Append the button to the dashboard
        $('.dashboard-form-globalfieldset').append(save_config_button);

        // Handle the click event on the save_config_button
        save_config_button.on('click', function () {
            // Disable it while the save action is occurring
            save_config_button.prop('disabled', true);
            // Change the text of the button to indicate its saving
            save_config_button.text('Saving...');
            /*
            Sets the save_config token so that the 'save_dashboard_settings_search' runs -- this is
            referenced in the depends of the 'save_dashboard_settings_search' e.g. depends="$save_config$"
            */
            submitted_tokens.set({'save_config': true});
            // Now run the search to save the settings
            save_dashboard_settings_search.startSearch();
        });

        // Listen for the search to complete
        save_dashboard_settings_search.on('search:done', function () {
            // Re-enable the save button
            save_config_button.prop('disabled', false);
            // Change the text back to default
            save_config_button.text('Save Configuration');
            // Prepend the success message
            $('.dashboard-form-globalfieldset').prepend(success_message);
            // Success message hidden by default - fade it in over 1 second, show for 3 seconds, then fade out 1 sec.
            success_message.fadeIn(1000).delay(3000).fadeOut(1000);
            // Unset the 'save_config' token so the save config search doesn't unintentionally run
            submitted_tokens.unset('save_config');
        });

        /*
        * Wait for the check_for_saved_settings_results search to get data
        */
        check_for_saved_settings_results.on("data", function () {
            // Get the results of data
            let rows = check_for_saved_settings_results.data().rows;
            // Loops through the results
            rows.forEach(function (row) {
                // Turn result into an array
                let car_models = row[0].split(' ');
                // Populate the multi-select with the value of all the models
                car_model_multiselect.val(car_models);
            });

        });

    });

There is also a full demo app for this on Github here: https://github.com/deviansg/splunk_dashboard_input_settings in case you'd like to just have a working version to base everything off of.

mwdbhyat
Builder

Both solutions worked for me in the end - but this is a crazy amount of effort you put in to the answer so thanks for that, and exactly what I needed.

0 Karma

niketn
Legend

@mwdbhyat do you have an idea of KV Store or do you want community to create the KV Store for this use case?

If you have already tried implementing KV Store and something is not working as expected please let us know.

I think as per your description instead of saving values Per Dashboard... Per User, you want to do it Per Dashboard... Per 1st Input value. This would mean in the KV Store the Key would be dashboard name + 1st input value. If the same already exists in the KV Store you will update the other values otherwise you will insert.

Refer to Splunk Dev example for creation of KV Store (the same can also be done without HTML dashboard using Simple XML JS Extension and CSS Extension): http://dev.splunk.com/view/webframework-tutorials/SP-CAAAEZT

____________________________________________
| makeresults | eval message= "Happy Splunking!!!"
0 Karma

mwdbhyat
Builder

@niketn thanks for getting back to me! I have implemented a version with the HTML link above but the client would also like an option of not using HMTL, hence using the above method described..

I am using your dashboard as per my above mention in the dashboard and reconfigured it to do what I want(I just replaced the tokens and added a tail - | inputlookup "$env:app$_$env:user$".csv where tokLogLevel="$tokLogLevel$" |tail 1
| table tokTime_earliest tokTime_latest tokLogLevel )

So now if i select loglevel error with a 4h time range and hit save, it saves it so that if i then select something else and decide to come back to loglevel=error etc, it will recall the saved settings - job almost done.

The problem im facing now is that that save button JS seems to be stuck in an endless loop - so after I hit save it keeps "listening" and records everything i select on the dashboard to the lookup file. EG, it is not saving per click - its in a constant saving state until i refresh the page. Do you have any idea how i would stop that behaviour so that it only saves when I click on the save button?

Thanks!

0 Karma

niketn
Legend

@mwdbhyat if your solution worked fine with html dashboard in Splunk, you can use the same concept in Simple XML JS extension where you can invoke the REST API call to read/write or update KV Store on button click. (KV Store would be better that lookup file).

____________________________________________
| makeresults | eval message= "Happy Splunking!!!"
0 Karma

manish_singh_77
Builder

You can use tokens between multiple input types so that when you select the first data type the next input results will auto populate on the previous token and so on you can pass the values. Let me know if it helps!

0 Karma

niketn
Legend

@manish_singh_777 I have converted your answer to comment, as I dont think it caters to the issue posted in the question.

____________________________________________
| makeresults | eval message= "Happy Splunking!!!"
0 Karma
Get Updates on the Splunk Community!

Introducing the 2024 SplunkTrust!

Hello, Splunk Community! We are beyond thrilled to announce our newest group of SplunkTrust members!  The ...

Introducing the 2024 Splunk MVPs!

We are excited to announce the 2024 cohort of the Splunk MVP program. Splunk MVPs are passionate members of ...

Splunk Custom Visualizations App End of Life

The Splunk Custom Visualizations apps End of Life for SimpleXML will reach end of support on Dec 21, 2024, ...