diff --git a/packages/insomnia-app/app/plugins/context/index.js b/packages/insomnia-app/app/plugins/context/index.js index 4be3783ce..c68d9e4b7 100644 --- a/packages/insomnia-app/app/plugins/context/index.js +++ b/packages/insomnia-app/app/plugins/context/index.js @@ -6,6 +6,8 @@ import * as _request from './request'; import * as _response from './response'; import * as _store from './store'; +export { PluginStore } from './store'; + export const app = _app; export const data = _data; export const network = _network; diff --git a/packages/insomnia-app/app/plugins/context/store.js b/packages/insomnia-app/app/plugins/context/store.js index f6f90c7e7..19b106d6d 100644 --- a/packages/insomnia-app/app/plugins/context/store.js +++ b/packages/insomnia-app/app/plugins/context/store.js @@ -2,7 +2,16 @@ import type { Plugin } from '../index'; import * as models from '../../models'; -export function init(plugin: Plugin) { +export type PluginStore = { + hasItem(string): Promise, + setItem(string, string): Promise, + getItem(string): Promise, + removeItem(string): Promise, + clear(): Promise, + all(): Promise>, +}; + +export function init(plugin: Plugin): { store: PluginStore } { return { store: { async hasItem(key: string): Promise { diff --git a/packages/insomnia-app/app/templating/base-extension.js b/packages/insomnia-app/app/templating/base-extension.js index 640a29e2b..3fa163b16 100644 --- a/packages/insomnia-app/app/templating/base-extension.js +++ b/packages/insomnia-app/app/templating/base-extension.js @@ -46,6 +46,10 @@ export default class BaseExtension { return this._ext.args || []; } + getActions() { + return this._ext.actions || []; + } + isDeprecated() { return this._ext.deprecated || false; } diff --git a/packages/insomnia-app/app/templating/extensions/index.js b/packages/insomnia-app/app/templating/extensions/index.js index 162f4c55c..dc1d93a21 100644 --- a/packages/insomnia-app/app/templating/extensions/index.js +++ b/packages/insomnia-app/app/templating/extensions/index.js @@ -2,6 +2,7 @@ import type { NunjucksParsedTagArg } from '../utils'; import type { Request } from '../../models/request'; import type { Response } from '../../models/response'; +import type { PluginStore } from '../../plugins/context'; export type PluginArgumentValue = string | number | boolean; type DisplayName = string | ((args: Array) => string); @@ -75,12 +76,23 @@ export type PluginTemplateTagContext = { }, }; +export type PluginTemplateTagActionContext = { + store: PluginStore, +}; + +export type PluginTemplateTagAction = { + name: string, + icon?: string, + run: (context: PluginTemplateTagActionContext) => Promise, +}; + export type PluginTemplateTag = { args: Array, name: string, displayName: DisplayName, disablePreview: () => boolean, description: string, + actions: Array, run: (context: PluginTemplateTagContext, ...arg: Array) => Promise | any, deprecated?: boolean, validate?: (value: any) => ?string, diff --git a/packages/insomnia-app/app/templating/index.js b/packages/insomnia-app/app/templating/index.js index 35735ec28..face6c2d2 100644 --- a/packages/insomnia-app/app/templating/index.js +++ b/packages/insomnia-app/app/templating/index.js @@ -100,6 +100,7 @@ export async function getTagDefinitions(): Promise> { description: ext.getDescription(), disablePreview: ext.getDisablePreview(), args: ext.getArgs(), + actions: ext.getActions(), })); } diff --git a/packages/insomnia-app/app/templating/utils.js b/packages/insomnia-app/app/templating/utils.js index 158c4d562..1559d1753 100644 --- a/packages/insomnia-app/app/templating/utils.js +++ b/packages/insomnia-app/app/templating/utils.js @@ -2,6 +2,7 @@ import type { PluginArgumentEnumOption } from './extensions'; import objectPath from 'objectpath'; +import type { PluginStore } from '../plugins/context'; export type NunjucksParsedTagArg = { type: 'string' | 'number' | 'boolean' | 'variable' | 'expression' | 'enum' | 'file' | 'model', @@ -21,9 +22,16 @@ export type NunjucksParsedTagArg = { extensions?: Array, }; +export type NunjucksActionTag = { + name: string, + icon?: string, + run: (context: PluginStore) => Promise, +}; + export type NunjucksParsedTag = { name: string, args: Array, + actions: Array, rawValue?: string, displayName?: string, description?: string, 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 279616e07..a7f1a7464 100644 --- a/packages/insomnia-app/app/ui/components/templating/tag-editor.js +++ b/packages/insomnia-app/app/ui/components/templating/tag-editor.js @@ -4,7 +4,11 @@ import autobind from 'autobind-decorator'; import classnames from 'classnames'; import clone from 'clone'; import * as templating from '../../../templating'; -import type { NunjucksParsedTag, NunjucksParsedTagArg } from '../../../templating/utils'; +import type { + NunjucksActionTag, + NunjucksParsedTag, + NunjucksParsedTagArg, +} from '../../../templating/utils'; import * as templateUtils from '../../../templating/utils'; import * as db from '../../../common/database'; import * as models from '../../../models'; @@ -15,6 +19,8 @@ import type { Workspace } from '../../../models/workspace'; import type { PluginArgumentEnumOption } from '../../../templating/extensions/index'; import { Dropdown, DropdownButton, DropdownDivider, DropdownItem } from '../base/dropdown/index'; import FileInputButton from '../base/file-input-button'; +import { getTemplateTags } from '../../../plugins'; +import * as pluginContexts from '../../../plugins/context'; type Props = { handleRender: Function, @@ -252,6 +258,20 @@ class TagEditor extends React.PureComponent { this._update(this.state.tagDefinitions, tagDefinition, null, false); } + async _handleActionClick(action: NunjucksActionTag) { + const templateTags = await getTemplateTags(); + const activeTemplateTag = templateTags.find(({ templateTag }) => { + return templateTag.name === this.state.activeTagData.name; + }); + + const helperContext = { + ...pluginContexts.store.init(activeTemplateTag.plugin), + }; + + await action.run(helperContext); + return this._handleRefresh(); + } + _setSelectRef(n: ?HTMLSelectElement) { this._select = n; @@ -626,6 +646,33 @@ class TagEditor extends React.PureComponent { ); } + renderActions(actions = []) { + return ( +
+
+ +
{actions.map(this.renderAction)}
+
+
+ ); + } + + renderAction(action: NunjucksActionTag, index: number) { + const name = action.name; + const icon = action.icon ? : undefined; + + return ( + + ); + } + render() { const { error, preview, activeTagDefinition, activeTagData, rendering } = this.state; @@ -671,6 +718,9 @@ class TagEditor extends React.PureComponent { activeTagDefinition.args.map((argDefinition: NunjucksParsedTagArg, index) => this.renderArg(argDefinition, activeTagData.args, index), )} + + {activeTagDefinition?.actions?.length && this.renderActions(activeTagDefinition.actions)} + {!activeTagDefinition && (