thunder-client-support icon indicating copy to clipboard operation
thunder-client-support copied to clipboard

Add Support For Responses Visualisation

Open oliverdrummond opened this issue 3 years ago • 3 comments

Is your feature request related to a problem? Please describe. I cannot see my responses as a table format in Thunder Client. So, to have this visualisation I need to copy the response, save it as a JSON file and import it in Microsoft Excel

Describe the solution you'd like Have an option to view the responses in a table format without extra software

Describe alternatives you've considered 1- To convert the JSON responses to a table format 2- Allow users to create JS codes to visualise it (like Postman Visualiser )

Implementation: I use this code in my Postman "Tests" area. This way, I can see the table in the "Visualize".

var template = `
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<style>
    .table {
        font-size: 12px;
    }
</style>
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/index.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/react-json-table.min.js"></script>
<script type="text/babel">

class App extends React.Component {
    render() {
        return (
            <JsonTable
                className="table"
                rows={[...this.props.data.data]}
            />
        );
    }
}

pm.getData((err, data) => {
    ReactDOM.render(
        <App data={data} />,
        document.getElementById('root')
    );
});

</script>
`;


// Provide the props as per the documentation
// https://github.com/marudhupandiyang/react-csv-to-table
let tableProps = {
    data: pm.response.json(),
};

pm.visualizer.set(template, tableProps);

oliverdrummond avatar Mar 23 '22 23:03 oliverdrummond

Thanks @oliverdrummond for the feedback, will review and add to roadmap.

rangav avatar Mar 24 '22 08:03 rangav

+1. This one stops me from moving from Postman :sob:

HomelessCoder avatar Apr 19 '22 12:04 HomelessCoder

Hi @HomelessCoder as an alternative way, you can send that visualisation html as response for the request which will display graphs.

just a suggestion for short term use.

rangav avatar Apr 20 '22 21:04 rangav

This is very much of interest to my team/company as well. The built-in table visualization of a JSON response is very helpful on larger response bodies, whether more complex JSON objects, number of JSON objects returned, or combination of the two. There are aspects that can be caught much easier in table form with easier comparison side by side than scrolling through JSON text. The import of raw JSON into Excel has proven useless given the number of manual steps involved and how it hides nested objects instead of surfacing them. This may work fine for objects with fewer properties or flatter objects, but that is not what we're working with.

After looking at this capability in Postman again yesterday to gain the insight we needed, I see that there is not only a table visualization, but also options for a linear chart and a bar graph. I don't have readily available use cases for the charts/graphs, but having a sort or filter on the displayed table's columns would be VERY helpful, putting it far ahead of Postman's implementation, in my opinion.

As far as altering the API to return HTML instead of JSON, that is not an option for us, as some of the endpoints that we're working with are a 3rd party's API. These are the ones that we're trying to get the most insight into, and have the least familiarity with, where the table visualization would be the most helpful.

I have included the script below that we have used in the Tests tab in Postman to dynamically build a table from the JSON response.

var template = `
<style>
        .fill,
body,
html {
  height: 100%
}

#json_vl,
.td_head,
.td_row_even,
.td_row_odd {
  font-size: small
}

#json_pnl,
#xxa,
.navbar-header,
.navbar-nav,
.navbar-nav>li,
.td_head {
  float: left
}

.fill {
  min-height: 100%
}

#json_vl {
  font-family: Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace
}

#widget {
  width: 100%
}

.top_size {
  height: 51px
}

#all_panels {
  height: 100%;
  min-height: 100%
}

#aboutLnk {
  position: fixed;
  right: 10px;
  top: 15px
}

#inner_text {
  display: block;
  position: absolute;
  height: auto;
  bottom: 0;
  top: 0;
  left: 0;
  right: 0;
  margin-top: 51px;
  margin-bottom: 0
}

#json_pnl {
  background-color: #ccc;
  width: 33.3%
}

#xxa {
  background-color: #E8E8E8;
  width: 66.7%
}

#table_pnl,
#tree_pnl {
  background-color: #E8E8E8;
  float: left;
  width: 50%
}

#sharethis {
  position: fixed;
  right: 80px;
  top: 10px
}

#inner_tbl {
  padding-left: 2px
}

.td_row_even {
  padding: 2px;
  background-color: #F6F4F0
}

.td_row_odd {
  padding: 2px;
  background-color: #FFF
}

.td_head {
  padding: 2px;
  font-weight: 700
}

input,
p,
select,
td,
textarea,
th {
  font-size: 1em
}

table,
td,
th {
  border: 1px solid gray
}

textarea {
  -moz-box-sizing: border-box;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  display: block;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 4px;
  border: 1px solid #333;
  overflow-y: auto;
  overflow-x: hidden
}

*,
html {
  font-family: Verdana, Arial, Helvetica, sans-serif
}

form,
h1,
h2,
h3,
h4,
h5,
li,
p,
ul {
  margin: 0;
  padding: 0
}

img {
  border: none
}

p {
  margin: 0 0 1em
}

table {
  font-size: 100%;
  background-color: white;
}

ol.tree {
  padding: 0 0 0 30px;
  width: 300px
}

li {
  position: relative;
  margin-left: -15px;
  list-style: none
}

li.file {
  margin-left: -1px !important
}

li.file a {
  color: #000;
  padding-left: 12px;
  text-decoration: none;
  display: block;
  font-size: small
}

li.file a[href$='.css'],
li.file a[href$='.js'],
li.file a[href*='.pdf']

li input {
  position: absolute;
  left: 0;
  margin-left: 0;
  opacity: 0;
  z-index: 2;
  cursor: pointer;
  height: 1em;
  width: 1em;
  top: 0
}

li input+ol {
  margin: -20px 0 0 -44px;
  height: 1em;
  padding: 1.563em 0 0 80px
}

li label.lbl_array,
li label.lbl_obj {
  display: block;
  padding-left: 33px;
  margin-bottom: 2px
}

li input+ol>li {
  display: none;
  margin-left: -14px !important;
  padding-left: 1px
}

li label.lbl_obj {
  cursor: pointer
}

li label.lbl_array {
  cursor: pointer
}

li input:checked+ol {
  margin: -20px 0 0 -44px;
  padding: 1.563em 0 0 80px;
  height: auto
}

li input:checked+ol>li {
  display: block;
  margin: 0 0 .125em
}

li input:checked+ol>li:last-child {
  margin: 0 0 .063em
}

.container {
  width: 970px;
  max-width: none !important
}

.col-xs-4 {
  padding-top: 15px;
  padding-bottom: 15px;
  background-color: #eee;
  background-color: rgba(86, 61, 124, .15);
  border: 1px solid #ddd;
  border: 1px solid rgba(86, 61, 124, .2)
}

    </style>
<div id="html">
<input type="text" id="json">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!--<script src="json2table.js"></script>-->
<script>
$(function() {
    pm.getData((err, data) => {
        $("#json").val(JSON.stringify(data.json));
        json2table("#html");
    });
});

function call(a) {
    $("#json").val(JSON.stringify(a, void 0, 2));
    json2table()
}

function json2table(selector) {
    $(selector).html(buildTable(getJsonVar()));
}

function getJsonVar() {
    try {
        var a = $.parseJSON($("#json").val());
        $("#json").val(JSON.stringify(a, void 0, 2));
        return a
    } catch (e) {
        alert(e);
    }
}

function buildTable(a) {
    var e = document.createElement("table"),
        d, b;
    if (isArray(a)) return buildArray(a);
    for (var c in a) "object" != typeof a[c] || isArray(a[c]) ? "object" == typeof a[c] && isArray(a[c]) ? (d = e.insertRow(-1), b = d.insertCell(-1), b.colSpan = 2, b.innerHTML = '<div class="td_head">' + encodeText(c) + '</div><table style="width:100%">' + $(buildArray(a[c]), !1).html() + "</table>") : (d = e.insertRow(-1), b = d.insertCell(-1), b.innerHTML = "<div class='td_head'>" + encodeText(c) + "</div>", d = d.insertCell(-1), d.innerHTML = "<div class='td_row_even'>" +
        encodeText(a[c]) + "</div>") : (d = e.insertRow(-1), b = d.insertCell(-1), b.colSpan = 2, b.innerHTML = '<div class="td_head">' + encodeText(c) + '</div><table style="width:100%">' + $(buildTable(a[c]), !1).html() + "</table>");
    return e
}

function buildArray(a) {
    var e = document.createElement("table"),
        d, b, c = !1,
        p = !1,
        m = {},
        h = -1,
        n = 0,
        l;
    l = "";
    if (0 == a.length) return "<div></div>";
    d = e.insertRow(-1);
    for (var f = 0; f < a.length; f++)
        if ("object" != typeof a[f] || isArray(a[f])) "object" == typeof a[f] && isArray(a[f]) ? (b = d.insertCell(h), b.colSpan = 2, b.innerHTML = '<div class="td_head"></div><table style="width:100%">' + $(buildArray(a[f]), !1).html() + "</table>", c = !0) : p || (h += 1, p = !0, b = d.insertCell(h), m.empty = h, b.innerHTML = "<div class='td_head'>&nbsp;</div>");
        else
            for (var k in a[f]) l =
                "-" + k, l in m || (c = !0, h += 1, b = d.insertCell(h), m[l] = h, b.innerHTML = "<div class='td_head'>" + encodeText(k) + "</div>");
    c || e.deleteRow(0);
    n = h + 1;
    for (f = 0; f < a.length; f++)
        if (d = e.insertRow(-1), td_class = isEven(f) ? "td_row_even" : "td_row_odd", "object" != typeof a[f] || isArray(a[f]))
            if ("object" == typeof a[f] && isArray(a[f]))
                for (h = m.empty, c = 0; c < n; c++) b = d.insertCell(c), b.className = td_class, l = c == h ? '<table style="width:100%">' + $(buildArray(a[f]), !1).html() + "</table>" : " ", b.innerHTML = "<div class='" + td_class + "'>" + encodeText(l) +
                    "</div>";
            else
                for (h = m.empty, c = 0; c < n; c++) b = d.insertCell(c), l = c == h ? a[f] : " ", b.className = td_class, b.innerHTML = "<div class='" + td_class + "'>" + encodeText(l) + "</div>";
    else {
        for (c = 0; c < n; c++) b = d.insertCell(c), b.className = td_class, b.innerHTML = "<div class='" + td_class + "'>&nbsp;</div>";
        for (k in a[f]) c = a[f], l = "-" + k, h = m[l], b = d.cells[h], b.className = td_class, "object" != typeof c[k] || isArray(c[k]) ? "object" == typeof c[k] && isArray(c[k]) ? b.innerHTML = '<table style="width:100%">' + $(buildArray(c[k]), !1).html() + "</table>" : b.innerHTML =
            "<div class='" + td_class + "'>" + encodeText(c[k]) + "</div>" : b.innerHTML = '<table style="width:100%">' + $(buildTable(c[k]), !1).html() + "</table>"
    }
    return e
}

function encodeText(a) {
    return $("<div />").text(a).html()
}

function isArray(a) {
    return "[object Array]" === Object.prototype.toString.call(a)
}

function isEven(a) {
    return 0 == a % 2
}
</script>`;


// In case you only want a specific property, change it here.
//
// Default: You can set the entire JSON response using pm.response.json()
let tableProps = {
    json: pm.response.json()
};

pm.visualizer.set(template, tableProps);

cw1934 avatar Sep 20 '23 14:09 cw1934

Hi @cw1934 is your team using thunder client free or paid version?

what is your team size?

rangav avatar Sep 21 '23 06:09 rangav

@rangav Our company is already on a paid plan. We are currently 12 users across 2 teams, growing team by team as ThunderClient proves full-featured enough for each team. Total department is 50+.

cw1934 avatar Sep 21 '23 14:09 cw1934

Thanks @cw1934 for clarifying

can you please confirm your company using below form. https://www.thunderclient.com/contact

rangav avatar Sep 21 '23 14:09 rangav

Hello, just want to add my name to the list of people saying this would be great. My team and I currently use Postman, but we are switching to Thunderclient (I think our director is still working on getting licenses purchased). We do use a few visualizations and would love to have that, but here's a different use case.

My team and I use Thunderclient/Postman for working with APIs in our company - we are not developing/testing our own APIs but using Thunderclient as just a way to consume these APIs. So there are many cases where we get lots of data back then have to parse out certain sections of it based on various requirements.

Typically what we do is use the code button to grab the curl command. Then we jump down to the cli and run the curl command piped to a jq command.

curl -X GET https://foo.com | jq '[.result[] | {name: .name, use_types: .network_address.use_types}] | group_by(.use_types[]) | map({use_type: .[0].use_types[0], names: map(.name)})'

(the jq is just grouping together things based on the use_type)

This is just example fake data - the normal response has a ton of data. But here's a small working example of fake json that the jq above will parse:

{
  "result": [
    {
      "name": "foo1",
      "network_address": { "use_types": [ "cp" ] }
    },
    {
      "name": "foo2",
      "network_address": { "use_types": [ "w" ] }
    },
    {
      "name": "foo3",
      "network_address": { "use_types": [ "cp" ] }
    },
    {
      "name": "foo4",
      "network_address": { "use_types": [ "w" ] }
    }
  ]
}

And here's an example of the output that jq returns:

[
  {
    "use_type": "cp",
    "names": [
      "foo1",
      "foo3"
    ]
  },
  {
    "use_type": "w",
    "names": [
      "foo2",
      "foo4"
    ]
  }
]

Not everyone on our team is familiar with code or jq, so they do a lot of copy/pasting. I would love to have something like this saved with the request itself so that I could then create a bunch of canned queries for my teammates.

Hopefully I've made that clear, but let me know if any questions or clarification needed. Thanks for the fantastic tool!

seanwcom avatar Nov 27 '23 21:11 seanwcom

Thanks @seanwcom for the feedback, This feature is on the priority list now. Planning to implement next month.

Do you mind sharing your company name using below form https://www.thunderclient.com/contact

rangav avatar Nov 29 '23 11:11 rangav

Thanks @seanwcom for the feedback, This feature is on the priority list now. Planning to implement next month.

Do you mind sharing your company name using below form https://www.thunderclient.com/contact

Sure thing - I sent a contact request. Thanks!

seanwcom avatar Nov 29 '23 16:11 seanwcom

This feature is implemented and published to the marketplace, please update to v2.17.0

See all features in this update https://github.com/rangav/thunder-client-support/releases/tag/v2.17.0

Please test and let me know your feedback.

rangav avatar Jan 03 '24 18:01 rangav

I've finally had a chance to do some initial testing with this and its great so far. I've been able to fully recreate what I was doing in Postman, thank you!

Here's an idea for a bit more functionality. One of the APIs we regularly consume has close to a 60 second response time (ugh...). What do you think about a way to refresh the chart without requerying the API? Obviously this doesn't get new data, but it would be helpful while editing the inline script's handlebars template, refreshing the display (instantly) without having to constantly hit the API.

seanwcom avatar Jan 12 '24 15:01 seanwcom

Thanks @seanwcom for the feedback.

Will check if it's easy to refresh chart without executing the api request.

rangav avatar Jan 12 '24 17:01 rangav

We're trying to get this to work on our end, but haven't been successful yet. I'm not sure if it's the JSON response schema that we're testing with, or if we're missing some step completely. Can the following 3 pieces for a working visualization be added here (or release notes) so we can better determine if we really have all the right pieces in place?

  1. Response JSON
  2. Test script
  3. Screenshot of the rendered chart

cw1934 avatar Jan 12 '24 20:01 cw1934

@cw1934 - Here's a really simple one I just threw together, see if you can get this working. This is a simple GET request to some public sample data: https://jsonplaceholder.typicode.com/users

Drop this in your Tests, Scripting tab:

var template = `
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.7.8/handlebars.min.js"></script>

<div id="output"></div>
<script id="entry-template" type="text/x-handlebars-template">
    {{#each this}}
        {{name}}<br>
    {{/each}}
</script>

<script>
  var source = document.getElementById("entry-template").innerHTML;
  var template = Handlebars.compile(source);

  document.getElementById("output").innerHTML = template(chart_data);
</script>
`;

var data = tc.response.json;
tc.chartHTML(template, data);

Since the response json is an array, I'm looping with {{#each this}} - this was a bit tricky at first having not used handlebars before. Hope it helps!

seanwcom avatar Jan 12 '24 20:01 seanwcom

@seanwcom Thanks so much for the sample, that helped us get past our initial hold-up.

To everyone; While we've gotten something to work, we're ultimately still looking for a solution that works in any/all requests/responses so we can reuse the same script without having to adjust it to match the exact schema of each response. I realize the script I posted before is larger than ideal and can be pared down, but we're really looking for something that dynamically renders the table based on the json in the response.

cw1934 avatar Jan 17 '24 22:01 cw1934

The template we did get to work borrows from seanwcom's response above. Replacing the 3-line 'each' block with this: <table class="tftable" border="1"> <tr> <th>Work Order Id</th> </tr> {{#each this}} <tr> <td>{{workOrderId}}</td> </tr> {{/each}} </table> It's not a universal solution, but maybe it will help someone.

CharlieSnowCode avatar Jan 18 '24 21:01 CharlieSnowCode

@rangav - do you prefer any other issues with the chart stuff here in this thread or a new issue? Not sure how you prefer it since this is still a beta feature. It seems that I can't copy text from the chart. I can select text, but cmd-c does not copy (I'm on a mac if that makes a difference).

seanwcom avatar Jan 19 '24 14:01 seanwcom

@seanwcom you can create a new issue, if its related to copy/paste issue.

rangav avatar Jan 19 '24 14:01 rangav

@CharlieSnowCode and @cw1934 will provide a universal solution example for converting JSON to HTML table in 1 or 2 days.

rangav avatar Jan 19 '24 14:01 rangav

Here is generic example below to convert any response JSON array to HTML Table

You can modify the style as needed



var response = tc.response.json;

var html = `
    <style>
      body {
        font-size: 13px;
        font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
      }

      table {
        width: 100%;
        box-sizing: border-box;
        border-collapse: collapse;
        border-spacing: 0;
      }

      td,
      th {
        padding: 0;
      }

      th, td {
        padding: 4px 6px;
        text-align: left;
        border-bottom: 1px solid #E1E1E1;
      }

      th:first-child,
      td:first-child {
        padding-left: 0;
      }

      th:last-child,
      td:last-child {
        padding-right: 0;
      }
  
  </style>

    <div id="container"></div>
    
    <script>
         // get the container element
         let container = document.getElementById("container");
         
         var cols = Object.keys(chart_data[0]);

          var headerRow = '<tr>';
          var bodyRows = '';
      
          cols.map(function(col) {
              headerRow += '<th>' + col + '</th>';
          });
      
          headerRow += '</tr>';
      
          chart_data.map(function(row) {
              bodyRows += '<tr>';
      
              cols.map(function(colName) {
                  bodyRows += '<td>' + row[colName] + '</td>';
              })
      
              bodyRows += '</tr>';
          });
      
          var tabledata=  '<table>' + headerRow + bodyRows + '</table>';
         container.innerHTML = tabledata; // set table html
      
   </script>`
   
   tc.chartHTML(html, response);

rangav avatar Jan 23 '24 21:01 rangav

Thank you very much, worked like a charm!

CharlieSnowCode avatar Jan 24 '24 12:01 CharlieSnowCode

@rangav Thanks for the example, much appreciated!

cw1934 avatar Jan 24 '24 14:01 cw1934

Thanks @CharlieSnowCode, @cw1934 for the feedback, Let me know if you need any further improvements.

rangav avatar Jan 24 '24 17:01 rangav