mirror of
https://github.com/Kong/insomnia
synced 2024-11-08 06:39:48 +00:00
Support for tag editor dynamic variable args (#512)
This commit is contained in:
parent
14bdea8b34
commit
de08895092
@ -27,10 +27,11 @@ describe('ResponseExtension General', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('fails on empty filter', async () => {
|
it('fails on empty filter', async () => {
|
||||||
await models.response.create({parentId: 'req_test'}, '{"foo": "bar"}');
|
const request = await models.request.create({parentId: 'foo'});
|
||||||
|
await models.response.create({parentId: request._id, statusCode: 200}, '{"foo": "bar"}');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await templating.render(`{% response "body", "req_test", "" %}`);
|
await templating.render(`{% response "body", "${request._id}", "" %}`);
|
||||||
fail('Should have failed');
|
fail('Should have failed');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
expect(err.message).toContain('No body filter specified');
|
expect(err.message).toContain('No body filter specified');
|
||||||
|
@ -49,10 +49,6 @@ export default ({
|
|||||||
throw new Error('No request specified');
|
throw new Error('No request specified');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field !== 'raw' && !filter) {
|
|
||||||
throw new Error(`No ${field} filter specified`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const request = await context.util.models.request.getById(id);
|
const request = await context.util.models.request.getById(id);
|
||||||
if (!request) {
|
if (!request) {
|
||||||
throw new Error(`Could not find request ${id}`);
|
throw new Error(`Could not find request ${id}`);
|
||||||
@ -65,7 +61,11 @@ export default ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!response.statusCode) {
|
if (!response.statusCode) {
|
||||||
throw new Error('No responses for request');
|
throw new Error('No successful responses for request');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field !== 'raw' && !filter) {
|
||||||
|
throw new Error(`No ${field} filter specified`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sanitizedFilter = filter.trim();
|
const sanitizedFilter = filter.trim();
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
export type NunjucksParsedTagArg = {
|
export type NunjucksParsedTagArg = {
|
||||||
type: 'string' | 'number' | 'boolean' | 'number' | 'variable' | 'expression',
|
type: 'string' | 'number' | 'boolean' | 'number' | 'variable' | 'expression',
|
||||||
value: string | number | boolean,
|
value: string | number | boolean,
|
||||||
|
forceVariable?: boolean,
|
||||||
quotedBy?: '"' | "'"
|
quotedBy?: '"' | "'"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,13 +67,16 @@ class CookieList extends React.PureComponent<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className="selectable" key={i}>
|
<tr className="selectable" key={i}>
|
||||||
<RenderedText render={handleRender} component="td">
|
<td>
|
||||||
{cookie.domain}
|
<RenderedText render={handleRender}>
|
||||||
</RenderedText>
|
{cookie.domain}
|
||||||
<RenderedText render={handleRender} component="td"
|
</RenderedText>
|
||||||
props={{className: 'force-wrap wide'}}>
|
</td>
|
||||||
{cookieString}
|
<td className="force-wrap wide">
|
||||||
</RenderedText>
|
<RenderedText render={handleRender}>
|
||||||
|
{cookieString}
|
||||||
|
</RenderedText>
|
||||||
|
</td>
|
||||||
<td onClick={null} className="text-right no-wrap">
|
<td onClick={null} className="text-right no-wrap">
|
||||||
<button className="btn btn--super-compact btn--outlined"
|
<button className="btn btn--super-compact btn--outlined"
|
||||||
onClick={e => handleShowModifyCookieModal(cookie)}
|
onClick={e => handleShowModifyCookieModal(cookie)}
|
||||||
|
@ -79,6 +79,7 @@ class NunjucksModal extends PureComponent {
|
|||||||
onChange={this._handleTemplateChange}
|
onChange={this._handleTemplateChange}
|
||||||
defaultValue={defaultTemplate}
|
defaultValue={defaultTemplate}
|
||||||
handleRender={handleRender}
|
handleRender={handleRender}
|
||||||
|
handleGetRenderContext={handleGetRenderContext}
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -2,10 +2,8 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
component: string,
|
|
||||||
children: string,
|
children: string,
|
||||||
render: Function,
|
render: Function
|
||||||
props?: Object
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@ -35,8 +33,7 @@ class RenderedText extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {component, props} = this.props;
|
return this.state.renderedText;
|
||||||
return React.createElement(component, props || {}, this.state.renderedText);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,9 +14,11 @@ import {trackEvent} from '../../../analytics/index';
|
|||||||
import type {BaseModel} from '../../../models/index';
|
import type {BaseModel} from '../../../models/index';
|
||||||
import type {Workspace} from '../../../models/workspace';
|
import type {Workspace} from '../../../models/workspace';
|
||||||
import type {PluginArgumentEnumOption} from '../../../templating/extensions/index';
|
import type {PluginArgumentEnumOption} from '../../../templating/extensions/index';
|
||||||
|
import {Dropdown, DropdownButton, DropdownDivider, DropdownItem} from '../base/dropdown/index';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
handleRender: Function,
|
handleRender: Function,
|
||||||
|
handleGetRenderContext: Function,
|
||||||
defaultValue: string,
|
defaultValue: string,
|
||||||
onChange: Function,
|
onChange: Function,
|
||||||
workspace: Workspace
|
workspace: Workspace
|
||||||
@ -30,7 +32,8 @@ type State = {
|
|||||||
allDocs: {[string]: Array<BaseModel>},
|
allDocs: {[string]: Array<BaseModel>},
|
||||||
rendering: boolean,
|
rendering: boolean,
|
||||||
preview: string,
|
preview: string,
|
||||||
error: string
|
error: string,
|
||||||
|
variables: Array<{name: string, value: string}>
|
||||||
};
|
};
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
@ -48,7 +51,8 @@ class TagEditor extends React.PureComponent<Props, State> {
|
|||||||
allDocs: {},
|
allDocs: {},
|
||||||
rendering: true,
|
rendering: true,
|
||||||
preview: '',
|
preview: '',
|
||||||
error: ''
|
error: '',
|
||||||
|
variables: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,8 +72,15 @@ class TagEditor extends React.PureComponent<Props, State> {
|
|||||||
await this._update(tagDefinitions, activeTagDefinition, activeTagData, true);
|
await this._update(tagDefinitions, activeTagDefinition, activeTagData, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadVariables () {
|
||||||
|
const context = await this.props.handleGetRenderContext();
|
||||||
|
const variables = context.keys;
|
||||||
|
this.setState({variables});
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.load();
|
this.load();
|
||||||
|
this.loadVariables();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps: Props) {
|
componentWillReceiveProps (nextProps: Props) {
|
||||||
@ -104,7 +115,11 @@ class TagEditor extends React.PureComponent<Props, State> {
|
|||||||
this.setState({allDocs, loadingDocs: false});
|
this.setState({allDocs, loadingDocs: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateArg (argValue: string | number, argIndex: number) {
|
_updateArg (
|
||||||
|
argValue: string | number | boolean,
|
||||||
|
argIndex: number,
|
||||||
|
forceNewType: string | null = null
|
||||||
|
) {
|
||||||
const {tagDefinitions, activeTagData, activeTagDefinition} = this.state;
|
const {tagDefinitions, activeTagData, activeTagDefinition} = this.state;
|
||||||
|
|
||||||
if (!activeTagData) {
|
if (!activeTagData) {
|
||||||
@ -112,10 +127,13 @@ class TagEditor extends React.PureComponent<Props, State> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!activeTagDefinition) {
|
||||||
|
console.warn('No active tag definition to update', {state: this.state});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure all arguments exist
|
// Ensure all arguments exist
|
||||||
const defaultArgs = activeTagDefinition
|
const defaultArgs = this._getDefaultTagData(activeTagDefinition).args;
|
||||||
? this._getDefaultTagData(activeTagDefinition).args
|
|
||||||
: [];
|
|
||||||
for (let i = 0; i < defaultArgs.length; i++) {
|
for (let i = 0; i < defaultArgs.length; i++) {
|
||||||
if (activeTagData.args[i]) {
|
if (activeTagData.args[i]) {
|
||||||
continue;
|
continue;
|
||||||
@ -135,10 +153,42 @@ class TagEditor extends React.PureComponent<Props, State> {
|
|||||||
// Update it
|
// Update it
|
||||||
argData.value = argValue;
|
argData.value = argValue;
|
||||||
|
|
||||||
|
// Update type if we need to
|
||||||
|
if (forceNewType) {
|
||||||
|
// Ugh, what a hack (because it's enum)
|
||||||
|
(argData: any).type = forceNewType;
|
||||||
|
}
|
||||||
|
|
||||||
this._update(tagDefinitions, activeTagDefinition, tagData, false);
|
this._update(tagDefinitions, activeTagDefinition, tagData, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleChange (e: SyntheticEvent<HTMLInputElement>) {
|
async _handleChangeArgVariable (options: {argIndex: number, variable: boolean}) {
|
||||||
|
const {variable, argIndex} = options;
|
||||||
|
const {activeTagData, activeTagDefinition, variables} = this.state;
|
||||||
|
|
||||||
|
if (!activeTagData || !activeTagDefinition) {
|
||||||
|
console.warn('Failed to change arg variable', {state: this.state});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const argData = activeTagData.args[argIndex];
|
||||||
|
const argDef = activeTagDefinition.args[argIndex];
|
||||||
|
const existingValue = argData ? argData.value : '';
|
||||||
|
|
||||||
|
if (variable) {
|
||||||
|
const variable = variables.find(v => v.value === existingValue);
|
||||||
|
const firstVariable = variables.length ? variables[0].name : '';
|
||||||
|
const value = variable ? variable.name : firstVariable;
|
||||||
|
return this._updateArg(value || 'my_variable', argIndex, 'variable');
|
||||||
|
} else {
|
||||||
|
const initialType = argDef ? argDef.type : 'string';
|
||||||
|
const variable = variables.find(v => v.name === existingValue);
|
||||||
|
const value = variable ? variable.value : '';
|
||||||
|
return this._updateArg(value, argIndex, initialType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleChange (e: SyntheticEvent<HTMLInputElement>, forceVariable: boolean = false) {
|
||||||
const parent = e.currentTarget.parentNode;
|
const parent = e.currentTarget.parentNode;
|
||||||
let argIndex = -1;
|
let argIndex = -1;
|
||||||
if (parent instanceof HTMLElement) {
|
if (parent instanceof HTMLElement) {
|
||||||
@ -250,6 +300,19 @@ class TagEditor extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderArgVariable (path: string) {
|
||||||
|
const {variables} = this.state;
|
||||||
|
return (
|
||||||
|
<select type="text" defaultValue={path || ''} onChange={this._handleChange}>
|
||||||
|
{variables.map((v, i) => (
|
||||||
|
<option key={`${i}::${v.name}`} value={v.name}>
|
||||||
|
{v.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderArgString (value: string, placeholder: string) {
|
renderArgString (value: string, placeholder: string) {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
@ -355,9 +418,13 @@ class TagEditor extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const value = argData.value.toString();
|
const value = argData.value.toString();
|
||||||
|
const isVariable = argData.type === 'variable';
|
||||||
|
const argInputVariable = isVariable ? this.renderArgVariable(value) : null;
|
||||||
|
|
||||||
let argInput;
|
let argInput;
|
||||||
|
let isVariableAllowed = false;
|
||||||
if (argDefinition.type === 'string') {
|
if (argDefinition.type === 'string') {
|
||||||
|
isVariableAllowed = true;
|
||||||
const placeholder = typeof argDefinition.placeholder === 'string'
|
const placeholder = typeof argDefinition.placeholder === 'string'
|
||||||
? argDefinition.placeholder
|
? argDefinition.placeholder
|
||||||
: '';
|
: '';
|
||||||
@ -370,6 +437,7 @@ class TagEditor extends React.PureComponent<Props, State> {
|
|||||||
const modelId = typeof value === 'string' ? value : 'unknown';
|
const modelId = typeof value === 'string' ? value : 'unknown';
|
||||||
argInput = this.renderArgModel(modelId, model);
|
argInput = this.renderArgModel(modelId, model);
|
||||||
} else if (argDefinition.type === 'number') {
|
} else if (argDefinition.type === 'number') {
|
||||||
|
isVariableAllowed = true;
|
||||||
const placeholder = typeof argDefinition.placeholder === 'string'
|
const placeholder = typeof argDefinition.placeholder === 'string'
|
||||||
? argDefinition.placeholder
|
? argDefinition.placeholder
|
||||||
: '';
|
: '';
|
||||||
@ -385,16 +453,39 @@ class TagEditor extends React.PureComponent<Props, State> {
|
|||||||
) ? fnOrString(argDefinition.displayName, argDatas) : '';
|
) ? fnOrString(argDefinition.displayName, argDatas) : '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={argIndex} className="form-control form-control--outlined">
|
<div key={argIndex} className="form-row">
|
||||||
<label>
|
<div className="form-control form-control--outlined">
|
||||||
{fnOrString(displayName, argDatas)}
|
<label>
|
||||||
{help && (
|
{fnOrString(displayName, argDatas)}
|
||||||
<HelpTooltip className="space-left">{help}</HelpTooltip>
|
{argData.type === 'variable' ? (
|
||||||
)}
|
<span className="faded space-left">(Variable)</span>
|
||||||
<div data-arg-index={argIndex}>
|
) : null}
|
||||||
{argInput}
|
{help && (
|
||||||
|
<HelpTooltip className="space-left">{help}</HelpTooltip>
|
||||||
|
)}
|
||||||
|
<div data-arg-index={argIndex}>
|
||||||
|
{argInputVariable || argInput}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{isVariableAllowed ? (
|
||||||
|
<div className="form-control form-control--outlined form-control--no-label width-auto">
|
||||||
|
<Dropdown right>
|
||||||
|
<DropdownButton className="btn btn--clicky">
|
||||||
|
<i className="fa fa-gear"/>
|
||||||
|
</DropdownButton>
|
||||||
|
<DropdownDivider>Input Type</DropdownDivider>
|
||||||
|
<DropdownItem value={{variable: false, argIndex}}
|
||||||
|
onClick={this._handleChangeArgVariable}>
|
||||||
|
<i className={'fa ' + (isVariable ? '' : 'fa-check')}/> Static Value
|
||||||
|
</DropdownItem>
|
||||||
|
<DropdownItem value={{variable: true, argIndex}}
|
||||||
|
onClick={this._handleChangeArgVariable}>
|
||||||
|
<i className={'fa ' + (isVariable ? 'fa-check' : '')}/> Environment Variable
|
||||||
|
</DropdownItem>
|
||||||
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -422,7 +513,8 @@ class TagEditor extends React.PureComponent<Props, State> {
|
|||||||
} else {
|
} else {
|
||||||
previewElement = (
|
previewElement = (
|
||||||
<pre>
|
<pre>
|
||||||
<code className="block selectable scrollable force-wrap">{preview || <span> </span>}</code>
|
<code className="block selectable scrollable force-wrap">{preview ||
|
||||||
|
<span> </span>}</code>
|
||||||
</pre>
|
</pre>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user