Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support For Responses Visualisation #511

Closed
oliverdrummond opened this issue Mar 23, 2022 · 24 comments
Closed

Add Support For Responses Visualisation #511

oliverdrummond opened this issue Mar 23, 2022 · 24 comments
Labels
feature request New feature or request

Comments

@oliverdrummond
Copy link

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/react-csv-to-table@0.0.2/dist/index.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-json-table@0.1.1/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 oliverdrummond added the feature request New feature or request label Mar 23, 2022
@rangav
Copy link
Collaborator

rangav commented Mar 24, 2022

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

@HomelessCoder
Copy link

+1. This one stops me from moving from Postman 😭

@rangav
Copy link
Collaborator

rangav commented Apr 20, 2022

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.

@cw1934
Copy link

cw1934 commented Sep 20, 2023

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);

@rangav
Copy link
Collaborator

rangav commented Sep 21, 2023

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

what is your team size?

@cw1934
Copy link

cw1934 commented Sep 21, 2023

@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+.

@rangav
Copy link
Collaborator

rangav commented Sep 21, 2023

Thanks @cw1934 for clarifying

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

@seanwcom
Copy link

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!

@rangav
Copy link
Collaborator

rangav commented Nov 29, 2023

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

@seanwcom
Copy link

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!

@rangav
Copy link
Collaborator

rangav commented Jan 3, 2024

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 rangav closed this as completed Jan 3, 2024
@seanwcom
Copy link

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.

@rangav
Copy link
Collaborator

rangav commented Jan 12, 2024

Thanks @seanwcom for the feedback.

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

@cw1934
Copy link

cw1934 commented Jan 12, 2024

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

@seanwcom
Copy link

seanwcom commented Jan 12, 2024

@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!

@cw1934
Copy link

cw1934 commented Jan 17, 2024

@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.

@CharlieSnowCode
Copy link

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.

@seanwcom
Copy link

@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).

@rangav
Copy link
Collaborator

rangav commented Jan 19, 2024

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

@rangav
Copy link
Collaborator

rangav commented Jan 19, 2024

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

@rangav
Copy link
Collaborator

rangav commented Jan 23, 2024

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);

@CharlieSnowCode
Copy link

Thank you very much, worked like a charm!

@cw1934
Copy link

cw1934 commented Jan 24, 2024

@rangav Thanks for the example, much appreciated!

@rangav
Copy link
Collaborator

rangav commented Jan 24, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request New feature or request
Projects
None yet
Development

No branches or pull requests

6 participants