From aba3c8ed869e05a47d3b1b4e62baed238231cef8 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Thu, 21 Dec 2017 06:01:51 -0800 Subject: [PATCH] Prompt Template Tag and Plugin arg validation (#673) * Plugin arg validation, prompt tag, and some changes needed * Version bumps --- .../app/common/__tests__/render.test.js | 26 +++++ packages/insomnia-app/app/common/render.js | 103 ++++++++++-------- packages/insomnia-app/app/network/network.js | 22 ++-- .../app/plugins/context/__tests__/app.test.js | 59 +++++++--- .../plugins/context/__tests__/request.test.js | 19 +--- .../context/__tests__/response.test.js | 17 +-- .../insomnia-app/app/plugins/context/app.js | 34 +++++- .../insomnia-app/app/plugins/context/data.js | 3 +- .../app/plugins/context/request.js | 2 - .../app/plugins/context/response.js | 2 - packages/insomnia-app/app/plugins/index.js | 1 + .../app/templating/base-extension.js | 5 + .../app/templating/extensions/index.js | 1 + packages/insomnia-app/app/templating/index.js | 2 +- .../app/ui/components/modals/prompt-modal.js | 10 +- .../ui/components/templating/tag-editor.js | 7 ++ packages/insomnia-app/package.json | 3 +- plugins/insomnia-plugin-prompt/README.md | 5 + plugins/insomnia-plugin-prompt/index.js | 21 ++++ plugins/insomnia-plugin-prompt/package.json | 18 +++ 20 files changed, 256 insertions(+), 104 deletions(-) create mode 100644 plugins/insomnia-plugin-prompt/README.md create mode 100644 plugins/insomnia-plugin-prompt/index.js create mode 100644 plugins/insomnia-plugin-prompt/package.json diff --git a/packages/insomnia-app/app/common/__tests__/render.test.js b/packages/insomnia-app/app/common/__tests__/render.test.js index f16f46d36..89952b2b2 100644 --- a/packages/insomnia-app/app/common/__tests__/render.test.js +++ b/packages/insomnia-app/app/common/__tests__/render.test.js @@ -382,4 +382,30 @@ describe('render()', () => { expect(err.message).toBe('unknown block tag: invalid'); } }); + + it('outputs correct error path', async () => { + const template = { + foo: [{bar: '{% foo %}'}] + }; + + try { + await renderUtils.render(template); + fail('Should have failed to render'); + } catch (err) { + expect(err.path).toBe('foo[0].bar'); + } + }); + + it('outputs correct error path when private first node', async () => { + const template = { + _foo: {_bar: {baz: '{% foo %}'}} + }; + + try { + await renderUtils.render(template); + fail('Should have failed to render'); + } catch (err) { + expect(err.path).toBe('_bar.baz'); + } + }); }); diff --git a/packages/insomnia-app/app/common/render.js b/packages/insomnia-app/app/common/render.js index 2fcb7ccf7..f6e9ad518 100644 --- a/packages/insomnia-app/app/common/render.js +++ b/packages/insomnia-app/app/common/render.js @@ -12,6 +12,8 @@ import type {Environment} from '../models/environment'; export const KEEP_ON_ERROR = 'keep'; export const THROW_ON_ERROR = 'throw'; +export const RENDER_PURPOSE_SEND = 'send'; +export const RENDER_PURPOSE_GENERAL = 'general'; export type RenderedRequest = Request & { cookies: Array<{name: string, value: string, disabled?: boolean}>, @@ -112,7 +114,7 @@ export async function render ( // Make a deep copy so no one gets mad :) const newObj = clone(obj); - async function next (x: any, path: string = name): Promise { + async function next (x: any, path: string, first: boolean = false): Promise { if (blacklistPathRegex && path.match(blacklistPathRegex)) { return x; } @@ -158,21 +160,26 @@ export async function render ( const keys = Object.keys(x); for (const key of keys) { - const pathPrefix = path ? path + '.' : ''; - x[key] = await next(x[key], `${pathPrefix}${key}`); + if (first && key.indexOf('_') === 0) { + x[key] = await next(x[key], path); + } else { + const pathPrefix = path ? path + '.' : ''; + x[key] = await next(x[key], `${pathPrefix}${key}`); + } } } return x; } - return next(newObj); + return next(newObj, name, true); } export async function getRenderContext ( request: Request, environmentId: string, - ancestors: Array | null = null + ancestors: Array | null = null, + purpose: string | null = null ): Promise { if (!request) { return {}; @@ -201,21 +208,22 @@ export async function getRenderContext ( workspaceId: workspace ? workspace._id : 'n/a' }); + baseContext.getPurpose = () => purpose; + // Generate the context we need to render - const context = await buildRenderContext( + return await buildRenderContext( ancestors, rootEnvironment, subEnvironment, baseContext ); - - return context; } -export async function getRenderedRequest ( +export async function getRenderedRequestAndContext ( request: Request, - environmentId: string -): Promise { + environmentId: string, + purpose?: string +): Promise<{request: RenderedRequest, context: Object}> { const ancestors = await db.withAncestors(request, [ models.request.type, models.requestGroup.type, @@ -225,20 +233,17 @@ export async function getRenderedRequest ( const parentId = workspace ? workspace._id : 'n/a'; const cookieJar = await models.cookieJar.getOrCreateForParentId(parentId); - const renderContext = await getRenderContext(request, environmentId, ancestors); + const renderContext = await getRenderContext(request, environmentId, ancestors, purpose); // Render all request properties - const renderedRequest = await render( - request, + const renderResult = await render( + {_request: request, _cookieJar: cookieJar}, renderContext, request.settingDisableRenderRequestBody ? /^body.*/ : null ); - // Render cookies - const renderedCookieJar = await render( - cookieJar, - renderContext - ); + const renderedRequest = renderResult._request; + const renderedCookieJar = renderResult._cookieJar; // Remove disabled params renderedRequest.parameters = renderedRequest.parameters.filter(p => !p.disabled); @@ -260,34 +265,46 @@ export async function getRenderedRequest ( renderedRequest.url = setDefaultProtocol(renderedRequest.url); return { - // Add the yummy cookies - // TODO: Eventually get rid of RenderedRequest type and put these elsewhere - cookieJar: renderedCookieJar, - cookies: [], + context: renderContext, + request: { + // Add the yummy cookies + // TODO: Eventually get rid of RenderedRequest type and put these elsewhere + cookieJar: renderedCookieJar, + cookies: [], - // NOTE: Flow doesn't like Object.assign, so we have to do each property manually - // for now to convert Request to RenderedRequest. - _id: renderedRequest._id, - authentication: renderedRequest.authentication, - body: renderedRequest.body, - created: renderedRequest.created, - modified: renderedRequest.modified, - description: renderedRequest.description, - headers: renderedRequest.headers, - metaSortKey: renderedRequest.metaSortKey, - method: renderedRequest.method, - name: renderedRequest.name, - parameters: renderedRequest.parameters, - parentId: renderedRequest.parentId, - settingDisableRenderRequestBody: renderedRequest.settingDisableRenderRequestBody, - settingEncodeUrl: renderedRequest.settingEncodeUrl, - settingSendCookies: renderedRequest.settingSendCookies, - settingStoreCookies: renderedRequest.settingStoreCookies, - type: renderedRequest.type, - url: renderedRequest.url + // NOTE: Flow doesn't like Object.assign, so we have to do each property manually + // for now to convert Request to RenderedRequest. + _id: renderedRequest._id, + authentication: renderedRequest.authentication, + body: renderedRequest.body, + created: renderedRequest.created, + modified: renderedRequest.modified, + description: renderedRequest.description, + headers: renderedRequest.headers, + metaSortKey: renderedRequest.metaSortKey, + method: renderedRequest.method, + name: renderedRequest.name, + parameters: renderedRequest.parameters, + parentId: renderedRequest.parentId, + settingDisableRenderRequestBody: renderedRequest.settingDisableRenderRequestBody, + settingEncodeUrl: renderedRequest.settingEncodeUrl, + settingSendCookies: renderedRequest.settingSendCookies, + settingStoreCookies: renderedRequest.settingStoreCookies, + type: renderedRequest.type, + url: renderedRequest.url + } }; } +export async function getRenderedRequest ( + request: Request, + environmentId: string, + purpose?: string +): Promise { + const result = await getRenderedRequestAndContext(request, environmentId, purpose); + return result.request; +} + /** * Sort the keys that may have Nunjucks last, so that other keys get * defined first. Very important if env variables defined in same obj diff --git a/packages/insomnia-app/app/network/network.js b/packages/insomnia-app/app/network/network.js index e6798ab16..71432bbb3 100644 --- a/packages/insomnia-app/app/network/network.js +++ b/packages/insomnia-app/app/network/network.js @@ -4,7 +4,7 @@ import type {Request, RequestHeader} from '../models/request'; import type {Workspace} from '../models/workspace'; import type {Settings} from '../models/settings'; import type {RenderedRequest} from '../common/render'; -import {getRenderContext, getRenderedRequest} from '../common/render'; +import {getRenderedRequest, getRenderedRequestAndContext, RENDER_PURPOSE_SEND} from '../common/render'; import mkdirp from 'mkdirp'; import clone from 'clone'; import {parse as urlParse, resolve as urlResolve} from 'url'; @@ -15,7 +15,7 @@ import * as electron from 'electron'; import * as models from '../models'; import {AUTH_AWS_IAM, AUTH_BASIC, AUTH_DIGEST, AUTH_NETRC, AUTH_NTLM, CONTENT_TYPE_FORM_DATA, CONTENT_TYPE_FORM_URLENCODED, getAppVersion, getTempDir, STATUS_CODE_PLUGIN_ERROR} from '../common/constants'; import {delay, describeByteSize, getContentTypeHeader, getLocationHeader, getSetCookieHeaders, hasAcceptEncodingHeader, hasAcceptHeader, hasAuthHeader, hasContentTypeHeader, hasUserAgentHeader, waitForStreamToFinish} from '../common/misc'; -import {setDefaultProtocol, smartEncodeUrl, buildQueryStringFromParams, joinUrlAndQueryString} from 'insomnia-url'; +import {buildQueryStringFromParams, joinUrlAndQueryString, setDefaultProtocol, smartEncodeUrl} from 'insomnia-url'; import fs from 'fs'; import * as db from '../common/database'; import * as CACerts from './cacert'; @@ -733,8 +733,14 @@ export async function send ( throw new Error(`Failed to find request to send for ${requestId}`); } - const renderedRequestBeforePlugins = await getRenderedRequest(request, environmentId); - const renderedContextBeforePlugins = await getRenderContext(request, environmentId, ancestors); + const renderResult = await getRenderedRequestAndContext( + request, + environmentId, + RENDER_PURPOSE_SEND + ); + + const renderedRequestBeforePlugins = renderResult.request; + const renderedContextBeforePlugins = renderResult.context; const workspaceDoc = ancestors.find(doc => doc.type === models.workspace.type); const workspace = await models.workspace.getById(workspaceDoc ? workspaceDoc._id : 'n/a'); @@ -775,8 +781,8 @@ async function _applyRequestPluginHooks ( newRenderedRequest = clone(newRenderedRequest); const context = { - ...pluginContexts.app.init(plugin), - ...pluginContexts.request.init(plugin, newRenderedRequest, renderedContext) + ...pluginContexts.app.init(), + ...pluginContexts.request.init(newRenderedRequest, renderedContext) }; try { @@ -795,8 +801,8 @@ async function _applyResponsePluginHooks ( ): Promise { for (const {plugin, hook} of await plugins.getResponseHooks()) { const context = { - ...pluginContexts.app.init(plugin), - ...pluginContexts.response.init(plugin, response) + ...pluginContexts.app.init(), + ...pluginContexts.response.init(response) }; try { diff --git a/packages/insomnia-app/app/plugins/context/__tests__/app.test.js b/packages/insomnia-app/app/plugins/context/__tests__/app.test.js index 962cfbad7..f34e4c756 100644 --- a/packages/insomnia-app/app/plugins/context/__tests__/app.test.js +++ b/packages/insomnia-app/app/plugins/context/__tests__/app.test.js @@ -1,22 +1,17 @@ import * as plugin from '../app'; import * as modals from '../../../ui/components/modals'; import {globalBeforeEach} from '../../../__jest__/before-each'; - -const PLUGIN = { - name: 'my-plugin', - version: '1.0.0', - directory: '/plugins/my-plugin', - module: {} -}; +import {RENDER_PURPOSE_SEND} from '../../../common/render'; describe('init()', () => { beforeEach(globalBeforeEach); it('initializes correctly', async () => { - const result = plugin.init({name: PLUGIN}); + const result = plugin.init(); expect(Object.keys(result)).toEqual(['app']); expect(Object.keys(result.app).sort()).toEqual([ 'alert', 'getPath', + 'prompt', 'showSaveDialog' ]); }); @@ -24,18 +19,56 @@ describe('init()', () => { describe('app.alert()', () => { beforeEach(globalBeforeEach); - it('shows alert with message', async () => { + it('does not show alert when not sending', async () => { + modals.showAlert = jest.fn(); + const result = plugin.init(); + + result.app.alert(); + + // Make sure it passes correct arguments + expect(modals.showAlert.mock.calls).toEqual([]); + }); + + it('shows alert with message when sending', async () => { modals.showAlert = jest.fn().mockReturnValue('dummy-return-value'); - const result = plugin.init(PLUGIN); + const result = plugin.init(RENDER_PURPOSE_SEND); // Make sure it returns result of showAlert() expect(result.app.alert()).toBe('dummy-return-value'); - expect(result.app.alert('My message')).toBe('dummy-return-value'); + expect(result.app.alert({title: 'My message'})).toBe('dummy-return-value'); // Make sure it passes correct arguments expect(modals.showAlert.mock.calls).toEqual([ - [{message: '', title: 'Plugin my-plugin'}], - [{message: 'My message', title: 'Plugin my-plugin'}] + [{}], + [{title: 'My message'}] + ]); + }); +}); + +describe('app.prompt()', () => { + beforeEach(globalBeforeEach); + it('does not show prompt when not sending', async () => { + modals.showPrompt = jest.fn(); + const result = plugin.init(); + + result.app.prompt(); + + // Make sure it passes correct arguments + expect(modals.showPrompt.mock.calls).toEqual([]); + }); + + it('shows alert with message when sending', async () => { + modals.showPrompt = jest.fn(); + const result = plugin.init(RENDER_PURPOSE_SEND); + + // Make sure it returns result of showAlert() + result.app.prompt(); + result.app.prompt({title: 'My message'}); + + // Make sure it passes correct arguments + expect(modals.showPrompt.mock.calls).toEqual([ + [{cancelable: false, onComplete: expect.any(Function)}], + [{cancelable: false, onComplete: expect.any(Function), title: 'My message'}] ]); }); }); diff --git a/packages/insomnia-app/app/plugins/context/__tests__/request.test.js b/packages/insomnia-app/app/plugins/context/__tests__/request.test.js index ae3b99a69..af11990c2 100644 --- a/packages/insomnia-app/app/plugins/context/__tests__/request.test.js +++ b/packages/insomnia-app/app/plugins/context/__tests__/request.test.js @@ -2,13 +2,6 @@ import * as plugin from '../request'; import * as models from '../../../models'; import {globalBeforeEach} from '../../../__jest__/before-each'; -const PLUGIN = { - name: 'my-plugin', - version: '1.0.0', - directory: '/plugins/my-plugin', - module: {} -}; - const CONTEXT = { user_key: 'my_user_key', hello: 'world', @@ -25,7 +18,7 @@ describe('init()', () => { }); it('initializes correctly', async () => { - const result = plugin.init(PLUGIN, await models.request.getById('req_1'), CONTEXT); + const result = plugin.init(await models.request.getById('req_1'), CONTEXT); expect(Object.keys(result)).toEqual(['request']); expect(Object.keys(result.request).sort()).toEqual([ 'addHeader', @@ -49,7 +42,7 @@ describe('init()', () => { }); it('fails to initialize without request', () => { - expect(() => plugin.init(PLUGIN)) + expect(() => plugin.init()) .toThrowError('contexts.request initialized without request'); }); }); @@ -70,7 +63,7 @@ describe('request.*', () => { }); it('works for basic getters', async () => { - const result = plugin.init(PLUGIN, await models.request.getById('req_1'), CONTEXT); + const result = plugin.init(await models.request.getById('req_1'), CONTEXT); expect(result.request.getId()).toBe('req_1'); expect(result.request.getName()).toBe('My Request'); expect(result.request.getUrl()).toBe(''); @@ -78,7 +71,7 @@ describe('request.*', () => { }); it('works for headers', async () => { - const result = plugin.init(PLUGIN, await models.request.getById('req_1'), CONTEXT); + const result = plugin.init(await models.request.getById('req_1'), CONTEXT); // getHeaders() expect(result.request.getHeaders()).toEqual([ @@ -112,7 +105,7 @@ describe('request.*', () => { const request = await models.request.getById('req_1'); request.cookies = []; // Because the plugin technically needs a RenderedRequest - const result = plugin.init(PLUGIN, request, CONTEXT); + const result = plugin.init(request, CONTEXT); result.request.setCookie('foo', 'bar'); result.request.setCookie('foo', 'baz'); @@ -123,7 +116,7 @@ describe('request.*', () => { const request = await models.request.getById('req_1'); request.cookies = []; // Because the plugin technically needs a RenderedRequest - const result = plugin.init(PLUGIN, request, CONTEXT); + const result = plugin.init(request, CONTEXT); // getEnvironment expect(result.request.getEnvironment()).toEqual({ diff --git a/packages/insomnia-app/app/plugins/context/__tests__/response.test.js b/packages/insomnia-app/app/plugins/context/__tests__/response.test.js index 0f52560a1..d999e5379 100644 --- a/packages/insomnia-app/app/plugins/context/__tests__/response.test.js +++ b/packages/insomnia-app/app/plugins/context/__tests__/response.test.js @@ -5,17 +5,10 @@ import fs from 'fs'; import path from 'path'; import * as models from '../../../models/index'; -const PLUGIN = { - name: 'my-plugin', - version: '1.0.0', - directory: '/plugins/my-plugin', - module: {} -}; - describe('init()', () => { beforeEach(globalBeforeEach); it('initializes correctly', async () => { - const result = plugin.init(PLUGIN, {}); + const result = plugin.init({}); expect(Object.keys(result)).toEqual(['response']); expect(Object.keys(result.response)).toEqual([ 'getRequestId', @@ -31,7 +24,7 @@ describe('init()', () => { }); it('fails to initialize without response', () => { - expect(() => plugin.init(PLUGIN)) + expect(() => plugin.init()) .toThrowError('contexts.response initialized without response'); }); }); @@ -51,7 +44,7 @@ describe('response.*', () => { bytesRead: 123, elapsedTime: 321 }); - const result = plugin.init(PLUGIN, response); + const result = plugin.init(response); expect(result.response.getRequestId()).toBe('req_1'); expect(result.response.getStatusCode()).toBe(200); expect(result.response.getBytesRead()).toBe(123); @@ -60,7 +53,7 @@ describe('response.*', () => { }); it('works for basic and empty response', async () => { - const result = plugin.init(PLUGIN, {}); + const result = plugin.init({}); expect(result.response.getRequestId()).toBe(''); expect(result.response.getStatusCode()).toBe(0); expect(result.response.getBytesRead()).toBe(0); @@ -76,7 +69,7 @@ describe('response.*', () => { {name: 'set-cookie', value: 'baz=qux'} ] }; - const result = plugin.init(PLUGIN, response); + const result = plugin.init(response); expect(result.response.getHeader('Does-Not-Exist')).toBeNull(); expect(result.response.getHeader('CONTENT-TYPE')).toBe('application/json'); expect(result.response.getHeader('set-cookie')).toEqual(['foo=bar', 'baz=qux']); diff --git a/packages/insomnia-app/app/plugins/context/app.js b/packages/insomnia-app/app/plugins/context/app.js index eb674b230..fd9fd7e9a 100644 --- a/packages/insomnia-app/app/plugins/context/app.js +++ b/packages/insomnia-app/app/plugins/context/app.js @@ -1,13 +1,35 @@ // @flow -import type {Plugin} from '../'; import * as electron from 'electron'; import {showAlert} from '../../ui/components/modals/index'; +import {showPrompt} from '../../ui/components/modals'; +import {RENDER_PURPOSE_SEND} from '../../common/render'; -export function init (plugin: Plugin): {app: Object} { +export function init (renderPurpose?: string): {app: Object} { return { app: { - alert (message: string): Promise { - return showAlert({title: `Plugin ${plugin.name}`, message: message || ''}); + alert (options: {title: string, message: string}): Promise { + if (renderPurpose !== RENDER_PURPOSE_SEND) { + return Promise.resolve(); + } + + return showAlert(options || {}); + }, + prompt (options: {title: string, label?: string, defaultValue?: string, submitName?: string}): Promise { + options = options || {}; + + if (renderPurpose !== RENDER_PURPOSE_SEND) { + return Promise.resolve(options.defaultValue || ''); + } + + return new Promise(resolve => { + showPrompt({ + ...(options || {}), + cancelable: false, + onComplete (value: string) { + resolve(value); + } + }); + }); }, getPath (name: string): string { switch (name.toLowerCase()) { @@ -18,6 +40,10 @@ export function init (plugin: Plugin): {app: Object} { } }, async showSaveDialog (options: {defaultPath?: string} = {}): Promise { + if (renderPurpose !== RENDER_PURPOSE_SEND) { + return Promise.resolve(null); + } + return new Promise(resolve => { const saveOptions = { title: 'Save File', diff --git a/packages/insomnia-app/app/plugins/context/data.js b/packages/insomnia-app/app/plugins/context/data.js index 2a67cf31f..6a38d0906 100644 --- a/packages/insomnia-app/app/plugins/context/data.js +++ b/packages/insomnia-app/app/plugins/context/data.js @@ -1,8 +1,7 @@ // @flow -import type {Plugin} from '../'; import {exportHAR, exportJSON, importRaw, importUri} from '../../common/import'; -export function init (plugin: Plugin): {'import': Object, 'export': Object} { +export function init (): {'import': Object, 'export': Object} { return { 'import': { async uri (uri: string, options: {workspaceId?: string} = {}): Promise { diff --git a/packages/insomnia-app/app/plugins/context/request.js b/packages/insomnia-app/app/plugins/context/request.js index 52d2a4f88..0397fa8ad 100644 --- a/packages/insomnia-app/app/plugins/context/request.js +++ b/packages/insomnia-app/app/plugins/context/request.js @@ -1,10 +1,8 @@ // @flow -import type {Plugin} from '../'; import type {RenderedRequest} from '../../common/render'; import * as misc from '../../common/misc'; export function init ( - plugin: Plugin, renderedRequest: RenderedRequest, renderedContext: Object ): {request: Object} { diff --git a/packages/insomnia-app/app/plugins/context/response.js b/packages/insomnia-app/app/plugins/context/response.js index b9efded07..fed7195fb 100644 --- a/packages/insomnia-app/app/plugins/context/response.js +++ b/packages/insomnia-app/app/plugins/context/response.js @@ -1,5 +1,4 @@ // @flow -import type {Plugin} from '../'; import type {ResponseHeader} from '../../models/response'; import * as models from '../../models/index'; import {Readable} from 'stream'; @@ -16,7 +15,6 @@ type MaybeResponse = { } export function init ( - plugin: Plugin, response: MaybeResponse, bodyBuffer: Buffer | null = null ): {response: Object} { diff --git a/packages/insomnia-app/app/plugins/index.js b/packages/insomnia-app/app/plugins/index.js index 562d08500..33bc1c432 100644 --- a/packages/insomnia-app/app/plugins/index.js +++ b/packages/insomnia-app/app/plugins/index.js @@ -37,6 +37,7 @@ const CORE_PLUGINS = [ 'insomnia-plugin-file', 'insomnia-plugin-now', 'insomnia-plugin-uuid', + 'insomnia-plugin-prompt', 'insomnia-plugin-request', 'insomnia-plugin-response' ]; diff --git a/packages/insomnia-app/app/templating/base-extension.js b/packages/insomnia-app/app/templating/base-extension.js index 632da7ec5..8a6ee358d 100644 --- a/packages/insomnia-app/app/templating/base-extension.js +++ b/packages/insomnia-app/app/templating/base-extension.js @@ -1,5 +1,6 @@ import * as models from '../models/index'; import * as templating from './index'; +import * as pluginContexts from '../plugins/context'; const EMPTY_ARG = '__EMPTY_NUNJUCKS_ARG__'; @@ -60,6 +61,9 @@ export default class BaseExtension { // Pull out the meta helper const renderMeta = renderContext.getMeta ? renderContext.getMeta() : {}; + // Pull out the purpose + const renderPurpose = renderContext.getPurpose ? renderContext.getPurpose() : null; + // Extract the rest of the args const args = runArgs .slice(0, runArgs.length - 1) @@ -67,6 +71,7 @@ export default class BaseExtension { // Define a helper context with utils const helperContext = { + ...pluginContexts.app.init(renderPurpose), context: renderContext, meta: renderMeta, util: { diff --git a/packages/insomnia-app/app/templating/extensions/index.js b/packages/insomnia-app/app/templating/extensions/index.js index 15e9b165d..eeb46612f 100644 --- a/packages/insomnia-app/app/templating/extensions/index.js +++ b/packages/insomnia-app/app/templating/extensions/index.js @@ -82,5 +82,6 @@ export type PluginTemplateTag = { description: string, run: (context: PluginTemplateTagContext, ...arg: Array) => Promise | any, deprecated?: boolean, + validate?: (value: any) => ?string, priority?: number }; diff --git a/packages/insomnia-app/app/templating/index.js b/packages/insomnia-app/app/templating/index.js index 05114c330..6be5e1642 100644 --- a/packages/insomnia-app/app/templating/index.js +++ b/packages/insomnia-app/app/templating/index.js @@ -54,7 +54,7 @@ export function render (text: string, config: Object = {}): Promise { : 'error'; const newError = new RenderError(sanitizedMsg); - newError.path = path || null; + newError.path = path || ''; newError.message = sanitizedMsg; newError.location = {line, column}; newError.type = 'render'; diff --git a/packages/insomnia-app/app/ui/components/modals/prompt-modal.js b/packages/insomnia-app/app/ui/components/modals/prompt-modal.js index a7175b875..42890f67f 100644 --- a/packages/insomnia-app/app/ui/components/modals/prompt-modal.js +++ b/packages/insomnia-app/app/ui/components/modals/prompt-modal.js @@ -20,6 +20,7 @@ class PromptModal extends PureComponent { upperCase: false, hint: null, inputType: 'text', + cancelable: true, hints: [] }; } @@ -66,6 +67,7 @@ class PromptModal extends PureComponent { selectText, upperCase, hint, + cancelable, inputType, placeholder, label, @@ -82,6 +84,7 @@ class PromptModal extends PureComponent { defaultValue, submitName, selectText, + cancelable, placeholder, upperCase, hint, @@ -107,7 +110,7 @@ class PromptModal extends PureComponent { ); return ( -
+
@@ -131,7 +134,8 @@ class PromptModal extends PureComponent { placeholder, label, upperCase, - hints + hints, + cancelable } = this.state; const input = ( @@ -152,7 +156,7 @@ class PromptModal extends PureComponent { } return ( - + {title}
diff --git a/packages/insomnia-app/app/ui/components/templating/tag-editor.js b/packages/insomnia-app/app/ui/components/templating/tag-editor.js index 2e71dcbbf..4d33a82c5 100644 --- a/packages/insomnia-app/app/ui/components/templating/tag-editor.js +++ b/packages/insomnia-app/app/ui/components/templating/tag-editor.js @@ -509,6 +509,12 @@ class TagEditor extends React.PureComponent { typeof argDefinition.displayName === 'function' ) ? fnOrString(argDefinition.displayName, argDatas) : ''; + let validationError = ''; + const canValidate = argDefinition.type === 'string' || argDefinition.type === 'number'; + if (canValidate && typeof argDefinition.validate === 'function') { + validationError = argDefinition.validate(strValue) || ''; + } + const formControlClasses = classnames({ 'form-control': true, 'form-control--thin': argDefinition.type === 'boolean', @@ -522,6 +528,7 @@ class TagEditor extends React.PureComponent { {fnOrString(displayName, argDatas)} {isVariable && (Variable)} {help && {help}} + {validationError && {validationError}} {argInputVariable || argInput}
diff --git a/packages/insomnia-app/package.json b/packages/insomnia-app/package.json index 8b624cfd1..ad48fede1 100644 --- a/packages/insomnia-app/package.json +++ b/packages/insomnia-app/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "1.0.5", + "version": "1.0.6", "name": "insomnia-app", "app": { "name": "insomnia", @@ -108,6 +108,7 @@ "insomnia-plugin-file": "^1.0.3", "insomnia-plugin-hash": "^1.0.3", "insomnia-plugin-now": "^1.0.3", + "insomnia-plugin-prompt": "^1.0.1", "insomnia-plugin-request": "^1.0.4", "insomnia-plugin-response": "^1.0.5", "insomnia-plugin-uuid": "^1.0.3", diff --git a/plugins/insomnia-plugin-prompt/README.md b/plugins/insomnia-plugin-prompt/README.md new file mode 100644 index 000000000..9ab33e723 --- /dev/null +++ b/plugins/insomnia-plugin-prompt/README.md @@ -0,0 +1,5 @@ +# Insomnia Prompt Template Tag + +[![Npm Version](https://img.shields.io/npm/v/insomnia-plugin-prompt.svg)](https://www.npmjs.com/package/insomnia-plugin-prompt) + +This is a core Insomnia plugin. diff --git a/plugins/insomnia-plugin-prompt/index.js b/plugins/insomnia-plugin-prompt/index.js new file mode 100644 index 000000000..848db3467 --- /dev/null +++ b/plugins/insomnia-plugin-prompt/index.js @@ -0,0 +1,21 @@ +module.exports.templateTags = [{ + displayName: 'Prompt', + name: 'prompt', + description: 'prompt user for input', + args: [{ + displayName: 'Title', + type: 'string' + }, { + displayName: 'Label', + type: 'string' + }, { + displayName: 'Default Value', + type: 'string', + help: 'This value is used to pre-populate the prompt dialog, but is ALSO used ' + + 'when the app renders preview values (like the one below). This is to prevent the ' + + 'prompt from displaying too frequently during general app use.' + }], + run (context, title, label, defaultValue) { + return context.app.prompt({title, label, defaultValue}); + } +}]; diff --git a/plugins/insomnia-plugin-prompt/package.json b/plugins/insomnia-plugin-prompt/package.json new file mode 100644 index 000000000..b3827900f --- /dev/null +++ b/plugins/insomnia-plugin-prompt/package.json @@ -0,0 +1,18 @@ +{ + "name": "insomnia-plugin-prompt", + "version": "1.0.1", + "author": "Gregory Schier ", + "description": "Insomnia prompt template tag", + "license": "MIT", + "repository": "https://github.com/getinsomnia/insomnia/tree/master/plugins/insomnia-plugin-prompt", + "bugs": { + "url": "https://github.com/getinsomnia/insomnia" + }, + "main": "index.js", + "insomnia": { + "name": "prompt" + }, + "scripts": { + "test": "node --version" + } +}