Skip to content

Commit

Permalink
feat(ui): Response Panel > xml response filtering (#152)
Browse files Browse the repository at this point in the history
  • Loading branch information
kobenguyent committed May 15, 2024
1 parent d6a114b commit 3eabd2c
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 6 deletions.
16 changes: 15 additions & 1 deletion packages/ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"socket.io-client-v4": "npm:socket.io-client@^4.7.4",
"vue": "^3.2.41",
"vue-toast-notification": "^3.0.0",
"vuex": "^4.0.2"
"vuex": "^4.0.2",
"xpath": "^0.0.34"
},
"devDependencies": {
"@edge-runtime/vm": "^3.2.0",
Expand Down
34 changes: 32 additions & 2 deletions packages/ui/src/components/ResponsePanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,15 @@
<div class="content-box" style="height: 100%" v-else-if="responseContentType.startsWith('text/html')">
<IframeFromBuffer :buffer="response.buffer" style="width: 100%; height: 100%; border: none;" />
</div>
<template v-else>
<template v-if="responseContentType.startsWith('application/xml')">
<CodeMirrorResponsePanelPreview :model-value="responseFilter === '' ? bufferToJSONString(response.buffer) : filterXmlResponse(response.buffer, responseFilter)" @selection-changed="codeMirrorSelectionChanged" />
</template>
<template v-else-if="responseContentType.startsWith('application/json')">
<CodeMirrorResponsePanelPreview :model-value="responseFilter === '' ? bufferToJSONString(response.buffer) : filterResponse(response.buffer, responseFilter)" @selection-changed="codeMirrorSelectionChanged" />
</template>
<template v-else>
<CodeMirrorResponsePanelPreview :model-value="bufferToJSONString(response.buffer)" @selection-changed="codeMirrorSelectionChanged" />
</template>
</template>
<div class="content-box" v-else>
<div style="white-space: pre-line;">{{ response.error }}</div>
Expand Down Expand Up @@ -88,6 +94,10 @@
<input type="text" class="full-width-input" title="Filter response body" placeholder="$.store.books[*].author" v-model="responseFilter">
<a href="#" @click.prevent="showResFilteringHelpModal" class="help-link"><i class="fas fa-question-circle"></i></a>
</div>
<div class="row" v-else-if="responseContentType.startsWith('application/xml')">
<input type="text" class="full-width-input" title="Filter response body" placeholder="/store/books/author" v-model="responseFilter">
<a href="#" @click.prevent="showResFilteringHelpModal" class="help-link"><i class="fas fa-question-circle"></i></a>
</div>
</section>
</template>
<template v-if="activeResponsePanelTab === 'Header'">
Expand Down Expand Up @@ -136,7 +146,7 @@
</div>
</template>
<ContextMenu :options="responseHistoryContextMenuOptions" :element="responseHistoryContextMenuElement" v-model:show="showResponseHistoryContextMenu" @click="handleResponseHistoryContextMenuItemClick" />
<ResponseFilteringHelpModal v-model:showModal="showResponseFilteringHelpModal"></ResponseFilteringHelpModal>
<ResponseFilteringHelpModal v-model:showModal="showResponseFilteringHelpModal" v-model:is-xml="isXmlResponse"></ResponseFilteringHelpModal>
</template>
<script>
Expand All @@ -156,6 +166,8 @@ import {
import { emitter } from '@/event-bus'
import {JSONPath} from 'jsonpath-plus'
import ResponseFilteringHelpModal from '@/components/modals/ResponseFilteringHelpModal.vue'
import xpath from 'xpath'
import constants from '@/constants'
export default {
components: {
Expand All @@ -174,6 +186,7 @@ export default {
responseHistoryContextMenuElement: null,
showResponseHistoryContextMenu: false,
currentlySelectedText: '',
isXmlResponse: false,
showResponseFilteringHelpModal: false,
responseFilter: ''
}
Expand Down Expand Up @@ -362,6 +375,7 @@ export default {
this.activeResponsePanelTab = 'Preview'
}
this.responseFilter = ''
this.isXmlResponse = this.responseContentType.startsWith(constants.MIME_TYPE.XML) ? true : false
}
},
methods: {
Expand All @@ -376,6 +390,22 @@ export default {
}
},
filterXmlResponse(buffer, query) {
try {
const doc = new DOMParser().parseFromString(this.bufferToString(buffer), 'text/xml')
const result = xpath.evaluate(query, doc, null, xpath.XPathResult.ANY_TYPE, null)
const nodes = []
let node = result.iterateNext()
while (node) {
nodes.push(new XMLSerializer().serializeToString(node))
node = result.iterateNext()
}
return nodes.join('\n')
} catch (error) {
console.error('Error filtering XML:', error.message)
return this.bufferToString(buffer)
}
},
cancelRequest() {
this.requestAbortController.abort()
},
Expand Down
37 changes: 36 additions & 1 deletion packages/ui/src/components/modals/ResponseFilteringHelpModal.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
<template>
<div v-if="showModalComp" class="help-modal">
<modal :title="title" v-model="showModalComp">
<modal :title="title" v-model="showModalComp" v-if="isXml">
<p>Use XPath to filter the response body. Here are some examples that you might use on a book store API:</p>
<table style="table-layout: fixed">
<tr v-for="(example, index) in XPathExamples" :key="index">
<td>
<code class="code-block">{{ example.path }}</code>
</td>
<td>
{{ example.description }}
</td>
</tr>
</table>
<p>Restfox uses <a href="https://www.npmjs.com/package/xpath" target="_blank">xpath</a>.</p>
</modal>
<modal :title="title" v-model="showModalComp" v-else>
<p>Use <a href="http:https://goessner.net/articles/JsonPath/" target="_blank">JSONPath</a> to filter the response body. Here are some examples that you might use on a book store API:</p>
<table style="table-layout: fixed">
<tr v-for="(example, index) in jsonPathExamples" :key="index">
Expand All @@ -24,6 +38,7 @@ export default {
components: {Modal},
props: {
showModal: Boolean,
isXml: Boolean,
},
computed: {
title() {
Expand Down Expand Up @@ -60,6 +75,26 @@ export default {
description: 'Get book by title regular expression'
}
]
},
XPathExamples() {
return [
{
path: '/store/books/title',
description: 'Get titles of all books in the store'
},
{
path: '/store/books[price < 10]',
description: 'Get books costing less than $10'
},
{
path: '/store/books[last()]',
description: 'Get the last book in the store'
},
{
path: 'count(/store/books)',
description: 'Get the number of books in the store'
}
]
}
},
}
Expand Down
3 changes: 2 additions & 1 deletion packages/ui/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export default {
TEXT_PLAIN: 'text/plain',
JSON: 'application/json',
GRAPHQL: 'application/graphql',
OCTET_STREAM: 'application/octet-stream'
OCTET_STREAM: 'application/octet-stream',
XML: 'application/xml'
}
}

0 comments on commit 3eabd2c

Please sign in to comment.