Hello all, we recently migrated some of our legacy dashboards to Dashboard Studio and running into issues with text inputs where the value could be optional. In legacy dashboards, this could be handled by adding a wildcard in the search, but in Dashboard Studio the visualizations refuse to run if there is no value, so we added "*" as the default value. Unfortunately, when the user tabs between the text inputs, Splunk doesn't highlight the field, so they sometimes enter text after the asterisk, causing the search to find ZERO values (really Splunk, really?).
My research has not found a simple solution to this issue, so we came up with this. We use a default value of " " for all text inputs. If the user enters anything in a field, Splunk strips the leading space (yay!), but if a blank field is left, Splunk retains the blank space (WTF?). To work around this, we came up with the following search logic to handle the blanks, but at least the dashboard now allows blank inputs as wildcards (just like the legacy dashboards...).
| makeresults
| eval USER_FIRST_NAME=ltrim("$INPUT_USER_FIRST_NAME$"," ") + "*"
| eval USER_LAST_NAME=ltrim("$INPUT_USER_LAST_NAME$"," ") + "*"
| eval USER_ID=ltrim("$INPUT_USER_ID$"," ") + "*"
| map [ | tstats summariesonly=false count from datamodel=test where User_First_Name="$$USER_FIRST_NAME$$" User_Last_Name="$$USER_LAST_NAME$$" User_Id="$$USER_ID$$" by User_First_Name User_Last_Name User_Id Tenant_Code ]
So, here is my actual question. Is there a way in Dashboard Studio to do this properly that I'm just missing?
If not, is there another way to manage this without the "map" function as shown above? Not a fan of using this, but we found no other way to make this work.
Enquiring minds need to know! Thanks in advance.
Hi @BradOH,
In Splunk Enterprise 10.2 and Splunk Cloud Platform 10.1.2507 or later, you can use JSONata expressions to construct predicates. Dashboard Studio's interface with JSONata is not well-documented and may require trial and error. See the expressions object in the source below.
Note that Dashboard Studio trims (removes leading and trailing whitespace) tokens, and empty tokens are undefined. When setting an empty value, use a single space as I've done below; otherwise, Dashboard Studio will interpret the $eval:field2$ token as a string literal instead of dereferencing it. If a space conflicts with your solution, devise a compatible placeholder.
You can adapt previously discussed methods in earlier versions of Splunk, e.g., this simple solution by @ITWhisperer: https://community.splunk.com/t5/Dashboards-Visualizations/Dashboard-studio-How-to-create-search-for-...
JSONata example:
{
"title": "Nullable Token",
"description": "",
"inputs": {
"input_global_trp": {
"options": {
"defaultValue": "-24h@h,now",
"token": "global_time"
},
"title": "Global Time Range",
"type": "input.timerange"
},
"input_nullable": {
"options": {
"defaultValue": "",
"token": "text_field2_nullable"
},
"title": "Nullable Token",
"type": "input.text"
}
},
"defaults": {
"dataSources": {
"ds.search": {
"options": {
"queryParameters": {
"earliest": "$global_time.earliest$",
"latest": "$global_time.latest$"
}
}
},
"ds.spl2": {
"options": {
"queryParameters": {
"earliest": "$global_time.earliest$",
"latest": "$global_time.latest$"
}
}
}
},
"visualizations": {
"global": {
"showProgressBar": true
}
}
},
"visualizations": {
"viz_qwzyWlJP": {
"options": {
"markdown": "### SPL\n`index=foo field1=x $eval:field2$ field3=z`"
},
"type": "splunk.markdown"
}
},
"dataSources": {},
"layout": {
"globalInputs": [
"input_nullable",
"input_global_trp"
],
"layoutDefinitions": {
"layout_1": {
"options": {
"height": 960,
"width": 1440
},
"structure": [
{
"item": "viz_qwzyWlJP",
"position": {
"h": 400,
"w": 1440,
"x": 0,
"y": 0
},
"type": "block"
}
],
"type": "grid"
}
},
"options": {},
"tabs": {
"items": [
{
"label": "New tab",
"layoutId": "layout_1"
}
]
}
},
"expressions": {
"eval": {
"eval_1": {
"name": "field2",
"value": "$exists($text_field2_nullable$) ? 'field2=\"' & $replace($replace($text_field2_nullable$, '\\\\', '\\\\\\\\'), '\"', '\\\\\"') & '\"' : ' '"
}
}
}
}
Thanks, but due to some issues with the Splunk 10 release in our infrastructure, we're staying on 9 for the moment, so that option is not available to us.
I also had a look at the other solution and it doesn't resolve the issue of blank inputs. We're trying to avoid "*" in fields, as these can cause typos, as when tabbing between fields, the cursor appears after the "*", requiring staff to delete and type, then re-add the *, or cursor left then start typing (a small issue yes, but this wasn't required in the legacy dashboards).
So, it appears the only workaround in Splunk 9 is the MAP command as I noted... 😞
Hi @BradOH,
We'll use a solution similar to the referenced post but adapted to Dashboard Studio's quirks.
As a starting point, we'll construct our search programmatically in SPL:
| makeresults
| eval index="foo"
| eval field1="x"
| eval field2="y"
| eval field3="z"
| formatThis returns a single field named seach with the following value:
( ( field1="x" AND field2="y" AND field3="z" AND index="foo" ) )Next, we'll define a pseudo-nullable input token with the default value set to a single space. Recall that Dashboard Studio trims whitespace from token values. While then token will indeed have a default value of a single space, it will display as an empty string. Editing the rendered input will remove the space, but clearing the rendered input will leave the (unrendered) space. In this way, the token is always defined and always satisfies implicit token dependencies:
"input_nullable": {
"options": {
"defaultValue": " ",
"token": "text_field2_nullable"
},
"title": "Nullable Token",
"type": "input.text"
}Next, we'll translate our base search into a data source that dynamically sets field2 and interprets a single space as null value. Note that we're removing field2 from the search when it's "null." This allows the search to return results whether field2 exists in the event or not. If you want to limit search results to events where field2 exists, using "*" in place null(), e.g., field2 is optional:
| makeresults
| eval index="foo"
| eval field1="x"
| eval field2=if($text_field2_nullable|s$==" ", null(), $text_field2_nullable|s$)
| eval field3="z"
| formator if field2 must exist:
| makeresults
| eval index="foo"
| eval field1="x"
| eval field2=if($text_field2_nullable|s$==" ", "*", $text_field2_nullable|s$)
| eval field3="z"
| formatIf the format command doesn't produce the SPL you need, you can use the makeresults command and any SPL to define a field named search and construct any value you'd like using whatever SPL you'd like. You don't need to limit yourself to the eval command. You have the full suite of (safe) default and custom SPL commands and any data in your environment available to you:
| makeresults
| eval search="index=foo field1=x field2=".if($text_field2_nullable|s$==" ", "*", $text_field2_nullable|s$)." field3=z"
| fields - _time(Note that the eval search ... example isn't entirely string-safe. Be mindful of token values that contain embedded whitespace. I'll leave that as an exercise for you.)
The data source must have the "Access search results or metadata" option enabled ("enableSmartSources": true). We'll reference the result of the format command, the "search" result field, in our visualization elements:
"dataSources": {
"ds_WKrsc3Ay": {
"name": "search_nullable_token",
"options": {
"enableSmartSources": true,
"query": "| makeresults \r\n| eval index=\"foo\" \r\n| eval field1=\"x\" \r\n| eval field2=if($text_field2_nullable|s$==\" \", null(), $text_field2_nullable|s$)\r\n| eval field3=\"z\"\r\n| format",
"queryParameters": {
"earliest": "0",
"latest": ""
}
},
"type": "ds.search"
}
}Finally, we'll use the $search_nullable_token:result.search$ token anywhere we need to reference the generated SPL:
"visualizations": {
"viz_breCVkj9": {
"options": {
"markdown": "### SPL\n`$search_nullable_token:result.search$`"
},
"type": "splunk.markdown"
}
}Tying it all together:
{
"title": "Nullable Token (Legacy)",
"description": "",
"inputs": {
"input_global_trp": {
"options": {
"defaultValue": "-24h@h,now",
"token": "global_time"
},
"title": "Global Time Range",
"type": "input.timerange"
},
"input_nullable": {
"options": {
"defaultValue": " ",
"token": "text_field2_nullable"
},
"title": "Nullable Token",
"type": "input.text"
}
},
"defaults": {
"dataSources": {
"ds.search": {
"options": {
"queryParameters": {
"earliest": "$global_time.earliest$",
"latest": "$global_time.latest$"
}
}
},
"ds.spl2": {
"options": {
"queryParameters": {
"earliest": "$global_time.earliest$",
"latest": "$global_time.latest$"
}
}
}
},
"visualizations": {
"global": {
"showProgressBar": true
}
}
},
"visualizations": {
"viz_breCVkj9": {
"options": {
"markdown": "### SPL\n`$search_nullable_token:result.search$`"
},
"type": "splunk.markdown"
}
},
"dataSources": {
"ds_WKrsc3Ay": {
"name": "search_nullable_token",
"options": {
"enableSmartSources": true,
"query": "| makeresults \r\n| eval index=\"foo\" \r\n| eval field1=\"x\" \r\n| eval field2=if($text_field2_nullable|s$==\" \", null(), $text_field2_nullable|s$)\r\n| eval field3=\"z\"\r\n| format",
"queryParameters": {
"earliest": "0",
"latest": ""
}
},
"type": "ds.search"
}
},
"layout": {
"globalInputs": [
"input_nullable",
"input_global_trp"
],
"layoutDefinitions": {
"layout_1": {
"options": {
"height": 960,
"width": 1440
},
"structure": [
{
"item": "viz_breCVkj9",
"position": {
"h": 400,
"w": 1440,
"x": 0,
"y": 0
},
"type": "block"
}
],
"type": "grid"
}
},
"options": {},
"tabs": {
"items": [
{
"label": "New tab",
"layoutId": "layout_1"
}
]
}
}
}
There's some grammar weirdness in there. Oops. At least you know a human wrote it. 😉 I do get a kick out of LLMs showing me my own community answers as references, though, even if the LLM hallucinates a goofball interpretaton.