Skip to content

Commit

Permalink
Add a way to read outputs from the response headers, status code and …
Browse files Browse the repository at this point in the history
…req time, in addition to the response body
  • Loading branch information
jreyesr committed May 22, 2024
1 parent 16437b6 commit e7e9a97
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 22 deletions.
5 changes: 4 additions & 1 deletion components/BatchDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ export default function BatchDialog({context, request}) {
writeFile(csvPath, outString);
}, [csvData, csvHeaders, csvPath]);

const canRun = csvData.length > 0 && outputConfig.every(x => x.name && x.jsonPath);
// Valid output configs:
// * Must contain a name, AND
// * Must contain a jsonPath, OR must be statusCode or reqTime (which don't need a jsonPath)
const canRun = csvData.length > 0 && outputConfig.every(x => x.name && (x.jsonPath || ["statusCode", "reqTime"].includes(x.context)));
const onRun = async () => {
setSent(0);

Expand Down
37 changes: 31 additions & 6 deletions components/OutputField.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import React, { useCallback } from 'react';
import ActionButton from './ActionButton';

export default function OutputField({options, name, jsonPath, onChange, onDelete}) {
export default function OutputField({options, name, context, jsonPath, onChange, onDelete}) {
const onChangeName = useCallback((e) => {
onChange(e.target.value, jsonPath)
}, [jsonPath, onChange]);
onChange(e.target.value, context, jsonPath)
}, [context, jsonPath, onChange]);

const onChangeContext = useCallback((e) => {
onChange(name, e.target.value, jsonPath)
}, [name, jsonPath, onChange])

const onChangeJsonPath = useCallback((e) => {
onChange(name, e.target.value)
}, [name, onChange]);
onChange(name, context, e.target.value)
}, [name, context, onChange]);

const placeholder = {
body: "$.store.books[*].author",
headers: "X-Some-Header"
}[context];
const shouldShowValueField = ["body", "headers"].includes(context);

return <div className="form-row" data-testid="singlefield">
<select
Expand All @@ -19,7 +29,22 @@ export default function OutputField({options, name, jsonPath, onChange, onDelete
<option value="">---Choose one---</option>
{options.map(o => <option key={o} value={o}>{o}</option>)}
</select>
<input type="text" value={jsonPath} onChange={onChangeJsonPath} placeholder='$.store.books[*].author' data-testid="value"/>


<select value={context} onChange={onChangeContext}>
<option value="body">From body</option>
<option value="headers">From header</option>
<option value="statusCode">Status code</option>
<option value="reqTime">Request time (millis)</option>
</select>

<input
style={{visibility: shouldShowValueField ? 'visible' : 'hidden' }}
type="text" value={jsonPath}
onChange={onChangeJsonPath}
placeholder={placeholder} data-testid="value"/>

<ActionButton title="" icon="fa-trash" onClick={onDelete} data-testid="deletebtn"/>
</div>
}
12 changes: 8 additions & 4 deletions components/OutputFieldsChooser.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ export default function OutputFieldsChooser({colNames, onChange}) {
const [outputs, setOutputs] = useState([]);

const addNew = useCallback(() => {
const newVal = outputs.concat([{name: "", jsonPath: ""}]);
const newVal = outputs.concat([{name: "", context: "body", jsonPath: ""}]);
setOutputs(newVal);
onChange(newVal);
}, [outputs, setOutputs, onChange]);

const updateField = useCallback((i) => (newName, newJsonPath) => {
const updateField = useCallback((i) => (newName, newContext, newJsonPath) => {
// Poor man's deep copy, since I'm not sure if you should modify React state in place
const cloned = JSON.parse(JSON.stringify(outputs))
cloned[i] = {name: newName, jsonPath: newJsonPath};
cloned[i] = {name: newName, context: newContext, jsonPath: newJsonPath};
setOutputs(cloned);
onChange(cloned);
}, [outputs, setOutputs, onChange]);
Expand All @@ -30,7 +30,11 @@ export default function OutputFieldsChooser({colNames, onChange}) {

return <FormRow label="Outputs">
{outputs.map((o, i) =>
<OutputField key={i} options={colNames} name={o.name} jsonPath={o.jsonPath} onChange={updateField(i)} onDelete={deleteField(i)}/>
<OutputField key={i}
options={colNames}
name={o.name} context={o.context} jsonPath={o.jsonPath}
onChange={updateField(i)} onDelete={deleteField(i)}
/>
)}
<ActionButton title="Add" icon="fa-plus" onClick={addNew}/>
</FormRow>
Expand Down
45 changes: 34 additions & 11 deletions utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,42 @@ export async function makeRequest(context, request, i, row, delay, outputConfig,
return
}

// Check that the Content-Type header is sensible, otherwise error out
if(!response.contentType.includes("json")) {
context.app.alert("Error!", `The response has invalid Content-Type "${response.contentType}", needs "application/json"! Alternatively, delete all Outputs and try again.`)
return // There's no point in attempting to parse the response, just jump to the next request
let responseData = {};
// If any outputConfigs refer to the response body, we must parse it
if(outputConfig.some(x => x.context === "body")) {
// Check that the Content-Type header is sensible, otherwise error out
if(!response.contentType.includes("json")) {
context.app.alert("Error!", `The response has invalid Content-Type "${response.contentType}", needs "application/json"! Alternatively, delete all Outputs and try again.`)
return // There's no point in attempting to parse the response, just jump to the next request
}

console.debug("parsing response data")
responseData = JSON.parse(readResponseFromFile(response.bodyPath))
}

console.debug("parsing response data")
// Read the response data, then apply JSONPath expressions on it and update the CSV data
const responseData = JSON.parse(readResponseFromFile(response.bodyPath))
console.debug(responseData)
for(const {name, jsonPath} of outputConfig) {
let out = applyJsonPath(jsonPath, responseData) ?? null
console.debug(name, "+", jsonPath, "=>", out)

// WEIRD: Labeled statement! https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label
writerForLoop:
for(const {name, jsonPath, context: ctx} of outputConfig) {
let out;
switch(ctx) {
case "body":
out = applyJsonPath(jsonPath, responseData) ?? null
break
case "headers":
out = response.headers.find(h => h.name === jsonPath.toLowerCase()).value
break
case "statusCode":
out = response.statusCode.toString()
break
case "reqTime":
out = response.elapsedTime.toString()
break
default:
console.error("Unknown outputConfig:", "name", name, "jsonPath", jsonPath, "context", ctx)
continue writerForLoop // Skip to next outputConfig
}
console.debug(name, "+", jsonPath, "@", ctx, "=>", out)

setCsvData(csvData => {
let newData = [...csvData] // Make a copy of the old data so we can mutate it normally
Expand Down

0 comments on commit e7e9a97

Please sign in to comment.