Dashboards & Visualizations

Table under table row expasion automatically inherits drilldown from parent table

azulgrana
Path Finder

Hi there!

I have a use case where I need to put a table under a Table row expansion and I need to have the ability to drilldown from both tables (parent & child). My XML + JS code works Ok, but I'm realizing that my child table somehow inherits the drilldown settings from the parent table. I was able to reproduce my issue using the Table Row Expansion code from the Splunk Examples App.

This is how my table looks like

alt text

Let's say that when you click on "mongod" I would like open a link to google.com while if you click on a value under "_raw" I would like to open a link to yahoo.com. Here's my run anywhere code

<dashboard script="custom_table_row_expansion.js">
        <label>Table Row Expansion</label>
        <description>Show more information on click of expansion.</description>
        <row>
            <panel>
                <table id="expand_with_events">
                    <search>
                        <query>index=_internal | stats count by sourcetype</query>
                        <earliest>-24h</earliest>
                        <latest>now</latest>
                    </search>
        <option name="drilldown">cell</option>
        <option name="percentagesRow">false</option>
        <option name="totalsRow">false</option>
        <drilldown>
          <condition field="sourcetype">
            <link target="_blank">https://www.google.com</link>
          </condition>
        </drilldown>
        </table>
      </panel>
        </row>
</dashboard>

require([
        'splunkjs/mvc/tableview',
        'splunkjs/mvc/chartview',
        'splunkjs/mvc/searchmanager',
        'splunkjs/mvc',
        'underscore',
        'splunkjs/mvc/simplexml/ready!'],function(
        TableView,
        ChartView,
        SearchManager,
        mvc,
        _
        ){
        var EventSearchBasedRowExpansionRenderer = TableView.BaseRowExpansionRenderer.extend({
            initialize: function(args) {
                // initialize will run once, so we will set up a search and a chart to be reused.
                this._searchManager = new SearchManager({
                    id: 'details-search-manager',
                    preview: false
                });
                this._TableView = new TableView({
                    id: 'TestTable',
                    managerid: 'details-search-manager',
                    drilldown: 'cell'
                });
            },
            canRender: function(rowData) {
                // Since more than one row expansion renderer can be registered we let each decide if they can handle that
                // data
                // Here we will always handle it.
                return true;
            },
            render: function($container, rowData) {
                // rowData contains information about the row that is expanded.  We can see the cells, fields, and values
                // We will find the sourcetype cell to use its value
                var sourcetypeCell = _(rowData.cells).find(function (cell) {
                   return cell.field === 'sourcetype';
                });
                //update the search with the sourcetype that we are interested in
                this._searchManager.set({ search: 'index=_internal sourcetype=' + sourcetypeCell.value + ''});
                // $container is the jquery object where we can put out content.
                // In this case we will render our chart and add it to the $container
                $container.append(this._TableView.render().el);
            }
        });

        var tableElement = mvc.Components.getInstance("expand_with_events");
        tableElement.getVisualization(function(tableView) {
            // Add custom cell renderer, the table will re-render automatically.
            tableView.addRowExpansionRenderer(new EventSearchBasedRowExpansionRenderer());
        });

        tableclick = splunkjs.mvc.Components.get("TestTable");
        tableclick.on("click", function (e) {
        e.preventDefault(); // Prevent redirecting to the Search app
                  window.open('www.yahoo.com','_blank');

        });

    });

So here's what happens ... if I click on a value under "sourcetype" the drilldown works fine and it takes me to google.com, but if I click on a value under "_raw" it automatically opens two tab on google.com + 1 tab on yahoo.com (See example below, don't worry about the page not found error)

alt text

If I switch the child object from Table to a Pie chart everything works fine , so the issue seems to be related to a child table under a Table Row Expansion situation.

Is this a bug? a design thing? what am I missing here? BTW I tested this on 7.2.1 and 8.x and got the same issue.

TIA!

0 Karma

azulgrana
Path Finder

I think my workaround would be create a new dashboard for all the Child data and then iframe the child data.

Here's the XML for the new Child data

Table Row Expansion Child

<input type="text" token="prmSourceType">
  <label>SourceType</label>
</input>


<panel>
  <table>
    <search>
      <query>index=_internal sourcetype=$prmSourceType|s$ | table _raw</query>
      <earliest>-24h@h</earliest>
      <latest>now</latest>
    </search>
    <option name="drilldown">cell</option>
    <option name="refresh.display">progressbar</option>
    <drilldown>
      <link target="_blank">www.yahoo.com</link>
    </drilldown>
  </table>
</panel>

and here's the new JS code

require([
        'splunkjs/mvc/tableview',
        'splunkjs/mvc/chartview',
        'splunkjs/mvc/searchmanager',
        'splunkjs/mvc',
        'underscore',
        'splunkjs/mvc/simplexml/ready!'],function(
        TableView,
        ChartView,
        SearchManager,
        mvc,
        _
        ){
        var EventSearchBasedRowExpansionRenderer = TableView.BaseRowExpansionRenderer.extend({
            initialize: function(args) {
                // initialize will run once, so we will set up a search and a chart to be reused.
            },
            canRender: function(rowData) {
                return true;
            },
            render: function($container, rowData) {

                var SourceType_txt = _(rowData.cells).find(function (cell) {
                    return cell.field === 'sourcetype';
                 });

                $container.append('<iframe src="/app/XYZ/table_row_expansion_child?hideChrome=true&amp;hideEdit=true&amp;hideTitle=true&amp;form.prmSourceType='+ SourceType_txt.value + '" width="100%" height="400" border="0" frameborder="0"/>');
            }
        });

        var tableElement = mvc.Components.getInstance("expand_with_events");
        tableElement.getVisualization(function(tableView) {
            // Add custom cell renderer, the table will re-render automatically.
            tableView.addRowExpansionRenderer(new EventSearchBasedRowExpansionRenderer());
        });
    });

Did a quick test and both drilldowns work Ok.

niketn
Legend

@azulgrana, the fix would be to use JS for drilldown for both Parent Table and extended Child Table. However, I noticed, issue with drilldown on child table as some of the times it is not even detecting that the Child table row has been clicked. Moreover the drilldown value from Parent to Child becomes incorrect.

In any case following is the approach I tried

Step 1: Remove the <drilldown> section from expand_with_events table and
Step 2: Add the following code for handling drilldown of the parent table as well as child table. Where isChildDrilldown and isParentDrilldownInsideChild are the semaphore values to detect which table was clicked and which drilldown prevented.

     var tableElement = mvc.Components.getInstance("expand_with_events");
     tableElement.getVisualization(function(tableView) {
         // Add custom cell renderer, the table will re-render automatically.
         tableView.addRowExpansionRenderer(new EventSearchBasedRowExpansionRenderer());
     });
     tableElement.on("click:cell",function(e){
         e.preventDefault(); // Prevent redirecting to the Search app
         if(isChildDrilldown==1 && (isParentDrilldownInsideChild==0 || isParentDrilldownInsideChild==1)){
            isParentDrilldownInsideChild=isParentDrilldownInsideChild+1;
            if(isParentDrilldownInsideChild==2){
                isParentDrilldownInsideChild=0;
                isChildDrilldown=0;
            }
         }else if(isChildDrilldown==0){
             window.open('https://www.google.com','_blank');
         }else{
             isChildDrilldown=0;
             isParentDrilldownInsideChild=0;
         }
     });
     tableclick = mvc.Components.getInstance("TestTable");
     tableclick.on("click:cell", function (e) {
         e.preventDefault(); // Prevent redirecting to the Search app
         window.open('https://www.yahoo.com','_blank');
         isChildDrilldown=1;
     });
____________________________________________
| makeresults | eval message= "Happy Splunking!!!"
0 Karma

niketn
Legend

Updated Simple XML and JS

<form script="custom_table_row_expansion.js">
  <label>Table With Row Expansion</label>
  <description>Show more information on click of expansion.</description>
  <fieldset submitButton="false">
    <input type="time" token="tokTime" searchWhenChanged="true">
      <label></label>
      <default>
        <earliest>-24h@h</earliest>
        <latest>now</latest>
      </default>
    </input>
  </fieldset>
  <row>
    <panel>
      <table id="expand_with_events">
        <search>
          <query>index=_internal | stats count by sourcetype</query>
          <earliest>$tokTime.earliest$</earliest>
          <latest>$tokTime.latest$</latest>
        </search>
        <option name="refresh.display">progressbar</option>
      </table>
    </panel>
  </row>
</form>

Updated JS custom_table_row_expansion.js

require([
    'splunkjs/mvc/tableview',
    'splunkjs/mvc/chartview',
    'splunkjs/mvc/searchmanager',
    'splunkjs/mvc',
    'underscore',
    'splunkjs/mvc/simplexml/ready!'],function(
    TableView,
    ChartView,
    SearchManager,
    mvc,
    _
    ){
         console.log("Inside Table Row Expansion");
         var isChildDrilldown=0;
         var isParentDrilldownInsideChild=0;
         var EventSearchBasedRowExpansionRenderer = TableView.BaseRowExpansionRenderer.extend({
             initialize: function(args) {
                 // initialize will run once, so we will set up a search and a chart to be reused.
                 this._searchManager = new SearchManager({
                     id: 'details-search-manager',
                     earliest_time: "$tokTime.earliest$",
                     latest_time: "$tokTime.latest$",
                     preview: false,
                     tokenDependencies: {},
                     },{tokens: true, tokenNamespace: "default"});
                 this._TableView = new TableView({
                     id: 'TestTable',
                     managerid: 'details-search-manager',
                     drilldown: 'cell'
                 });
             },
             canRender: function(rowData) {
                 // Since more than one row expansion renderer can be registered we let each decide if they can handle that
                 // data
                 // Here we will always handle it.
                 return true;
             },
             render: function($container, rowData) {
                 // rowData contains information about the row that is expanded.  We can see the cells, fields, and values
                 // We will find the sourcetype cell to use its value
                 var sourcetypeCell = _(rowData.cells).find(function (cell) {
                    return cell.field === 'sourcetype';
                 });
                 //update the search with the sourcetype that we are interested in
                 this._searchManager.set({ search: 'index=_internal sourcetype=' + sourcetypeCell.value + '| head 5 | rename _raw as "Sample 5 rows" | table "Sample 5 rows"'});
                 // $container is the jquery object where we can put out content.
                 // In this case we will render our chart and add it to the $container
                 $container.append(this._TableView.render().el);
             }
         });
         var tableElement = mvc.Components.getInstance("expand_with_events");
         tableElement.getVisualization(function(tableView) {
             // Add custom cell renderer, the table will re-render automatically.
             tableView.addRowExpansionRenderer(new EventSearchBasedRowExpansionRenderer());
         });
         tableElement.on("click:cell",function(e){
             e.preventDefault(); // Prevent redirecting to the Search app
             if(isChildDrilldown==1 && (isParentDrilldownInsideChild==0 || isParentDrilldownInsideChild==1)){
                isParentDrilldownInsideChild=isParentDrilldownInsideChild+1;
                if(isParentDrilldownInsideChild==2){
                    isParentDrilldownInsideChild=0;
                    isChildDrilldown=0;
                }
             }else if(isChildDrilldown==0){
                 window.open('https://www.google.com','_blank');
             }else{
                 isChildDrilldown=0;
                 isParentDrilldownInsideChild=0;
             }
         });
         tableclick = mvc.Components.getInstance("TestTable");
         tableclick.on("click:cell", function (e) {
             e.preventDefault(); // Prevent redirecting to the Search app
             window.open('https://www.yahoo.com','_blank');
             isChildDrilldown=1;
         });
     });
____________________________________________
| makeresults | eval message= "Happy Splunking!!!"
0 Karma

azulgrana
Path Finder

thanks @niketnilay for the workaround, I'll give it a try. Do you think this is a bug?

0 Karma

niketn
Legend

I feel it is a bug with Table Row Expansion. Even with your code, you can see that some of the time yahoo.com will not open for all the rows when expanded.

In the debug mode I saw that it was picking up the sourcetype that I had not clicked that too multiple times.

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

azulgrana
Path Finder

Thanks @niketnilay for all the testing and information! I might have to look into a diff approach

0 Karma

azulgrana
Path Finder

I think my workaround would be create a new dashboard for all the Child data and then iframe the child data.

Here's the XML for the new Child data

Table Row Expansion Child

<input type="text" token="prmSourceType">
  <label>SourceType</label>
</input>


<panel>
  <table>
    <search>
      <query>index=_internal sourcetype=$prmSourceType|s$ | table _raw</query>
      <earliest>-24h@h</earliest>
      <latest>now</latest>
    </search>
    <option name="drilldown">cell</option>
    <option name="refresh.display">progressbar</option>
    <drilldown>
      <link target="_blank">www.yahoo.com</link>
    </drilldown>
  </table>
</panel>

and here's the new JS code

require([
        'splunkjs/mvc/tableview',
        'splunkjs/mvc/chartview',
        'splunkjs/mvc/searchmanager',
        'splunkjs/mvc',
        'underscore',
        'splunkjs/mvc/simplexml/ready!'],function(
        TableView,
        ChartView,
        SearchManager,
        mvc,
        _
        ){
        var EventSearchBasedRowExpansionRenderer = TableView.BaseRowExpansionRenderer.extend({
            initialize: function(args) {
                // initialize will run once, so we will set up a search and a chart to be reused.
            },
            canRender: function(rowData) {
                return true;
            },
            render: function($container, rowData) {

                var SourceType_txt = _(rowData.cells).find(function (cell) {
                    return cell.field === 'sourcetype';
                 });

                $container.append('<iframe src="/app/XYZ/table_row_expansion_child?hideChrome=true&amp;hideEdit=true&amp;hideTitle=true&amp;form.prmSourceType='+ SourceType_txt.value + '" width="100%" height="400" border="0" frameborder="0"/>');
            }
        });

        var tableElement = mvc.Components.getInstance("expand_with_events");
        tableElement.getVisualization(function(tableView) {
            // Add custom cell renderer, the table will re-render automatically.
            tableView.addRowExpansionRenderer(new EventSearchBasedRowExpansionRenderer());
        });
    });

Did a quick test and both drilldowns work Ok.

0 Karma

niketn
Legend

@azulgrana if this works it is great. I was thinking that it is possible to create our own table row expansion and drilldown instead of using built-in. However it would involve considerable coding. Something in line with : https://answers.splunk.com/answers/324828/render-html-code-from-search-result-in-splunk-dash.html

However, I would explore this only in case above approach of yours does not work for you. So if it does do convert your comment to answer and accept the same 🙂

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

azulgrana
Path Finder

done, thanks for your support @niketnilay

Get Updates on the Splunk Community!

Extending Observability Content to Splunk Cloud

Register to join us !   In this Extending Observability Content to Splunk Cloud Tech Talk, you'll see how to ...

What's new in Splunk Cloud Platform 9.1.2312?

Hi Splunky people! We are excited to share the newest updates in Splunk Cloud Platform 9.1.2312! Analysts can ...

What’s New in Splunk Security Essentials 3.8.0?

Splunk Security Essentials (SSE) is an app that can amplify the power of your existing Splunk Cloud Platform, ...