postmanlabs / postman-app-support

Postman is an API platform for building and using APIs. Postman simplifies each step of the API lifecycle and streamlines collaboration so you can create better APIs—faster.
https://www.postman.com
5.85k stars 841 forks source link

Issue with URL-Encoding in Postman Code Blocks #13286

Open Gbahdeyboh opened 1 day ago

Gbahdeyboh commented 1 day ago

Is there an existing issue for this?

Describe the Issue

Hey Postman team,

I’ve noticed that when adding scripts to documentation or the visualizer in Postman, the code blocks are URL-encoded(e.g., spaces as %20, = as %3D) or Improper Escaping in Editors.

Please fix this ?

Code blocks should render as plain text, not URL-encoded. Copy-paste should provide clean, ready-to-use scripts.

Workspace & Example

Here’s an example of the issue:

Expandable Grid Table Visualizer Collection: Link to Postman Collection Workspace: Link to Postman Workspace Video: Loom Demo

image

https://global.discourse-cdn.com/getpostman/original/3X/6/8/683ed3d18c6ee2f50f84944d03cef17ceefbc500.png

https://global.discourse-cdn.com/getpostman/optimized/3X/a/b/abc6681ab999d8b647308333207f98af53ab9ad5_2_690x431.jpeg

Steps To Reproduce

  1. Copy and paste the below code into the code editor in the description of a Postman Collection. Set "javascript" as the language of the editor.
const responseData = pm.response.json();

// Utility to flatten nested objects
function flattenObject(obj, parent = '', res = {}) {
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            let propName = parent ? `${parent}.${key}` : key;
            if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
                flattenObject(obj[key], propName, res);
            } else {
                res[propName] = obj[key];
            }
        }
    }
    return res;
}

// Function to generate the HTML table with nested arrays and objects
function generateTable(data) {
    if (!Array.isArray(data) && data !== null && data !== undefined) {
        data = [data];
    }

    let table = '<table border="1" cellspacing="0" cellpadding="5" class="mac-table full-width" style="table-layout: auto; width: 100%;">';
    table += '<thead><tr>';
    if (data.length > 0) {
        Object.keys(flattenObject(data[0] || {})).forEach(function (key) {
            table += `<th style="white-space: nowrap; padding: 10px; text-align: left; border-bottom: 1px solid #ddd;">${key}</th>`;
        });
    }
    table += '</tr></thead><tbody>';

    data.forEach(function (row) {
        table += '<tr>';
        Object.values(flattenObject(row)).forEach(function (value) {
            if (value === null || value === '' || value === undefined) {
                table += '<td style="padding: 10px; border-bottom: 1px solid #ddd;">null</td>';
            } else if (Array.isArray(value)) {
                table += `<td style="position: relative; padding: 10px; border-bottom: 1px solid #ddd;">`;
                if (value.length > 0) {
                    table += `<button class="expand-button mac-button" data-expanded="false">+</button>
                              <div class="nested-table full-width" style="display:none; margin-top: 10px; background: #fff; box-shadow: 0 4px 8px rgba(0,0,0,0.1); overflow: auto; max-height: 500px;">
                              ${generateTable(value)}</div>`;
                } else {
                    table += 'null';
                }
                table += '</td>';
            } else if (typeof value === 'object' && value !== null) {
                table += `<td style="padding: 10px; border-bottom: 1px solid #ddd;">${JSON.stringify(value)}</td>`;
            } else {
                table += `<td style="padding: 10px; border-bottom: 1px solid #ddd;">${value}</td>`;
            }
        });
        table += '</tr>';
    });
    table += '</tbody></table>';
    return table;
}

// Template with CSS and expanded search functionality
const template = `<!DOCTYPE html>
<html>
<head>
    <style>
        .mac-table {
            width: 100%;
            table-layout: auto;
            border-collapse: collapse;
        }
        .mac-table th, .mac-table td {
            padding: 10px;
            border-bottom: 1px solid #ddd;
            text-align: left;
        }
        .nested-table {
            display: none;
            margin-top: 10px;
            background: #fff;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
            max-height: 500px;
            overflow: auto;
        }
        .expand-button {
            cursor: pointer;
            background-color: #f4f4f4;
            border: none;
            padding: 5px;
            font-size: 14px;
        }
        .search-box {
            margin-bottom: 10px;
            width: 100%;
            padding: 8px;
        }
    </style>
</head>
<body>
    <input type="text" id="search-input" class="search-box" placeholder="Search..." />
    <div id="response">
        {{{tableHtml}}}
    </div>
    <script>
        // Search functionality
        document.getElementById('search-input').addEventListener('input', function () {
            const filter = this.value.trim().toLowerCase();
            const rows = document.querySelectorAll('#response tbody tr');

            rows.forEach(row => {
                let rowText = row.textContent.toLowerCase();
                let matchFound = rowText.includes(filter);
                let nestedMatch = false;

                // Check for matches in nested tables
                row.querySelectorAll('.nested-table').forEach(nestedTable => {
                    const nestedText = nestedTable.textContent.toLowerCase();
                    if (nestedText.includes(filter)) {
                        nestedMatch = true;
                        nestedTable.style.display = 'block'; // Expand matching nested tables
                        const expandButton = nestedTable.previousElementSibling;
                        expandButton.setAttribute('data-expanded', 'true');
                    } else {
                        nestedTable.style.display = 'none';
                        const expandButton = nestedTable.previousElementSibling;
                        expandButton.setAttribute('data-expanded', 'false');
                    }
                });

                // Display row if a match is found in the row or nested table
                row.style.display = matchFound || nestedMatch || filter === '' ? '' : 'none';
            });
        });

        // Expand/collapse functionality for nested tables
        document.addEventListener('click', function (event) {
            if (event.target.classList.contains('expand-button')) {
                const button = event.target;
                const nestedTable = button.nextElementSibling;
                const expanded = button.getAttribute('data-expanded') === 'true';
                nestedTable.style.display = expanded ? 'none' : 'block';
                button.setAttribute('data-expanded', !expanded);
            }
        });
    </script>
</body>
</html>`;

// Render the HTML with the generated table and search input
pm.visualizer.set(template, { tableHtml: generateTable(responseData) });
  1. Click outside of the editor to save.
  2. Once save, click inside the editor and don't edit anything. Then click outside to save again.
  3. repeat 2 & 3 three to four times and you will see the below screenshot.
  4. image

Screenshots or Videos

No response

Operating System

macOS

Postman Version

11.13.2-240925-1853

Postman Platform

Postman App

User Account Type

Signed In User

Additional Context?

PLEASE NOTE: This post was originally made here.

It is helpful to go through the already existing conversation thread first.

GeeGee-Tech commented 1 day ago

Thanks for the quick response and for filing the bug report. I’ll follow up on the issue on GitHub to stay updated and provide any additional context if needed. Honestly, I had previously tried addressing this issue through email, but I struggled to figure out how to proceed. I was sent documentation links that left me more confused than before. I kept going back to them, trying to make sense of it all, but each attempt felt more futile than the last. Frustration set in, and I finally gave up, feeling completely stuck. It was tough revisiting what I thought was well documented, only to find it encoded and inaccessible. At times, I worried that others in the community might think less of me, which made the process even more discouraging. I began to feel like the community might see me as nothing more than a joke or an amateur, just another beginner in the coding field. It was mentally draining, and for a while, I even thought about abandoning this completely. I convinced myself to remove the code block entirely, unsure if I’d ever find a solution. This final attempt to seek help here in the Postman community has been a turning point. I’m truly grateful to finally understand and feel supported. Thank you.it really means a lot. Cheers! Gladwin GT