From d8066bc55887b865c359a8f8815c44c9f7bdf325 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sat, 4 May 2019 16:34:52 -0400 Subject: [PATCH] Add ability for plugins to add folder actions (#774) --- packages/insomnia-app/app/common/render.js | 6 +- packages/insomnia-app/app/network/network.js | 5 +- .../app/plugins/context/__tests__/app.test.js | 1 + .../insomnia-app/app/plugins/context/app.js | 6 +- .../app/plugins/context/network.js | 2 +- packages/insomnia-app/app/plugins/index.js | 25 +++++ .../request-group-actions-dropdown.js | 106 ++++++++++++++---- .../app/ui/components/modals/wrapper-modal.js | 14 ++- .../ui/components/sidebar/sidebar-children.js | 4 + .../sidebar/sidebar-request-group-row.js | 3 + .../app/ui/components/sidebar/sidebar.js | 1 + 11 files changed, 144 insertions(+), 29 deletions(-) diff --git a/packages/insomnia-app/app/common/render.js b/packages/insomnia-app/app/common/render.js index e1c1cc5c6..d12bb07ae 100644 --- a/packages/insomnia-app/app/common/render.js +++ b/packages/insomnia-app/app/common/render.js @@ -192,7 +192,7 @@ export async function render( export async function getRenderContext( request: Request, - environmentId: string, + environmentId: string | null, ancestors: Array | null = null, purpose: RenderPurpose | null = null, ): Promise { @@ -212,7 +212,7 @@ export async function getRenderContext( const rootEnvironment = await models.environment.getOrCreateForWorkspaceId( workspace ? workspace._id : 'n/a', ); - const subEnvironment = await models.environment.getById(environmentId); + const subEnvironment = await models.environment.getById(environmentId || 'n/a'); let keySource = {}; for (let key in (rootEnvironment || {}).data) { @@ -261,7 +261,7 @@ export async function getRenderContext( export async function getRenderedRequestAndContext( request: Request, - environmentId: string, + environmentId: string | null, purpose?: RenderPurpose, ): Promise<{ request: RenderedRequest, context: Object }> { const ancestors = await db.withAncestors(request, [ diff --git a/packages/insomnia-app/app/network/network.js b/packages/insomnia-app/app/network/network.js index a5b6feeb7..9506f54ed 100644 --- a/packages/insomnia-app/app/network/network.js +++ b/packages/insomnia-app/app/network/network.js @@ -805,7 +805,10 @@ export async function sendWithSettings( return _actuallySend(renderResult.request, renderResult.context, workspace, settings); } -export async function send(requestId: string, environmentId: string): Promise { +export async function send( + requestId: string, + environmentId: string | null, +): Promise { // HACK: wait for all debounces to finish /* * TODO: Do this in a more robust way 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 e0af8d663..b9f4302ba 100644 --- a/packages/insomnia-app/app/plugins/context/__tests__/app.test.js +++ b/packages/insomnia-app/app/plugins/context/__tests__/app.test.js @@ -12,6 +12,7 @@ describe('init()', () => { 'alert', 'getPath', 'prompt', + 'showGenericModalDialog', 'showSaveDialog', ]); }); diff --git a/packages/insomnia-app/app/plugins/context/app.js b/packages/insomnia-app/app/plugins/context/app.js index c7c7e029a..343803593 100644 --- a/packages/insomnia-app/app/plugins/context/app.js +++ b/packages/insomnia-app/app/plugins/context/app.js @@ -1,12 +1,13 @@ // @flow import * as electron from 'electron'; -import { showAlert, showPrompt } from '../../ui/components/modals/index'; +import { showAlert, showModal, showPrompt } from '../../ui/components/modals'; import type { RenderPurpose } from '../../common/render'; import { RENDER_PURPOSE_GENERAL, RENDER_PURPOSE_NO_RENDER, RENDER_PURPOSE_SEND, } from '../../common/render'; +import WrapperModal from '../../ui/components/modals/wrapper-modal'; export function init(renderPurpose: RenderPurpose = RENDER_PURPOSE_GENERAL): { app: Object } { return { @@ -18,6 +19,9 @@ export function init(renderPurpose: RenderPurpose = RENDER_PURPOSE_GENERAL): { a return showAlert({ title, message }); }, + showGenericModalDialog(title: string, options?: { html: string } = {}): Promise { + return showModal(WrapperModal, { title, bodyHTML: options.html }); + }, prompt( title: string, options?: { diff --git a/packages/insomnia-app/app/plugins/context/network.js b/packages/insomnia-app/app/plugins/context/network.js index b85b4fc2e..4169e99f1 100644 --- a/packages/insomnia-app/app/plugins/context/network.js +++ b/packages/insomnia-app/app/plugins/context/network.js @@ -4,7 +4,7 @@ import { send } from '../../network/network'; import type { Request } from '../../models/request'; import * as models from '../../models'; -export function init(activeEnvironmentId: string): { network: Object } { +export function init(activeEnvironmentId: string | null): { network: Object } { const network = { async sendRequest(request: Request): Promise { const responsePatch = await send(request._id, activeEnvironmentId); diff --git a/packages/insomnia-app/app/plugins/index.js b/packages/insomnia-app/app/plugins/index.js index 80de0dee7..3f4ff43d0 100644 --- a/packages/insomnia-app/app/plugins/index.js +++ b/packages/insomnia-app/app/plugins/index.js @@ -8,6 +8,8 @@ import { resolveHomePath } from '../common/misc'; import { showError } from '../ui/components/modals/index'; import type { PluginTemplateTag } from '../templating/extensions/index'; import type { PluginTheme } from './misc'; +import type { RequestGroup } from '../models/request-group'; +import type { Request } from '../models/request'; export type Plugin = { name: string, @@ -22,6 +24,19 @@ export type TemplateTag = { templateTag: PluginTemplateTag, }; +export type RequestGroupAction = { + plugin: Plugin, + action: ( + context: Object, + models: { + requestGroup: RequestGroup, + requests: Array, + }, + ) => void | Promise, + label: string, + icon?: string, +}; + export type RequestHook = { plugin: Plugin, hook: Function, @@ -156,6 +171,16 @@ export async function getPlugins(force: boolean = false): Promise> return plugins; } +export async function getRequestGroupActions(): Promise> { + let extensions = []; + for (const plugin of await getPlugins()) { + const actions = plugin.module.requestGroupActions || []; + extensions = [...extensions, ...actions.map(p => ({ plugin, ...p }))]; + } + + return extensions; +} + export async function getTemplateTags(): Promise> { let extensions = []; for (const plugin of await getPlugins()) { diff --git a/packages/insomnia-app/app/ui/components/dropdowns/request-group-actions-dropdown.js b/packages/insomnia-app/app/ui/components/dropdowns/request-group-actions-dropdown.js index a964bd268..7fe9a8a8f 100644 --- a/packages/insomnia-app/app/ui/components/dropdowns/request-group-actions-dropdown.js +++ b/packages/insomnia-app/app/ui/components/dropdowns/request-group-actions-dropdown.js @@ -1,6 +1,7 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; +// @flow +import * as React from 'react'; import autobind from 'autobind-decorator'; +import classnames from 'classnames'; import PromptButton from '../base/prompt-button'; import { Dropdown, @@ -11,12 +12,43 @@ import { } from '../base/dropdown'; import EnvironmentEditModal from '../modals/environment-edit-modal'; import * as models from '../../../models'; -import { showPrompt, showModal } from '../modals/index'; +import { showError, showModal, showPrompt } from '../modals'; +import type { HotKeyRegistry } from '../../../common/hotkeys'; import { hotKeyRefs } from '../../../common/hotkeys'; +import type { RequestGroupAction } from '../../../plugins'; +import { getRequestGroupActions } from '../../../plugins'; +import type { RequestGroup } from '../../../models/request-group'; +import type { Workspace } from '../../../models/workspace'; +import * as pluginContexts from '../../../plugins/context/index'; +import { RENDER_PURPOSE_NO_RENDER } from '../../../common/render'; +import type { Environment } from '../../../models/environment'; + +type Props = { + workspace: Workspace, + requestGroup: RequestGroup, + hotKeyRegistry: HotKeyRegistry, + activeEnvironment: Environment | null, + handleCreateRequest: (id: string) => any, + handleDuplicateRequestGroup: (rg: RequestGroup) => any, + handleMoveRequestGroup: (rg: RequestGroup) => any, + handleCreateRequestGroup: (id: string) => any, +}; + +type State = { + actionPlugins: Array, + loadingActions: { [string]: boolean }, +}; @autobind -class RequestGroupActionsDropdown extends PureComponent { - _setDropdownRef(n) { +class RequestGroupActionsDropdown extends React.PureComponent { + _dropdown: ?Dropdown; + + state = { + actionPlugins: [], + loadingActions: {}, + }; + + _setDropdownRef(n: ?Dropdown) { this._dropdown = n; } @@ -56,19 +88,54 @@ class RequestGroupActionsDropdown extends PureComponent { showModal(EnvironmentEditModal, this.props.requestGroup); } - show() { - this._dropdown.show(); + async onOpen() { + const plugins = await getRequestGroupActions(); + this.setState({ actionPlugins: plugins }); + } + + async show() { + this._dropdown && this._dropdown.show(); + } + + async _handlePluginClick(p: RequestGroupAction) { + this.setState(state => ({ loadingActions: { ...state.loadingActions, [p.label]: true } })); + + try { + const { activeEnvironment, requestGroup } = this.props; + const activeEnvironmentId = activeEnvironment ? activeEnvironment._id : null; + + const context = { + ...pluginContexts.app.init(RENDER_PURPOSE_NO_RENDER), + ...pluginContexts.store.init(p.plugin), + ...pluginContexts.network.init(activeEnvironmentId), + }; + + const requests = await models.request.findByParentId(requestGroup._id); + requests.sort((a, b) => a.metaSortKey - b.metaSortKey); + await p.action(context, { requestGroup, requests }); + } catch (err) { + showError({ + title: 'Plugin Action Failed', + error: err, + }); + } + + this.setState(state => ({ loadingActions: { ...state.loadingActions, [p.label]: false } })); + this._dropdown && this._dropdown.hide(); } render() { const { + workspace, // eslint-disable-line no-unused-vars requestGroup, // eslint-disable-line no-unused-vars hotKeyRegistry, ...other } = this.props; + const { actionPlugins, loadingActions } = this.state; + return ( - + @@ -96,21 +163,20 @@ class RequestGroupActionsDropdown extends PureComponent { Delete + {actionPlugins.length > 0 && Plugins} + {actionPlugins.map((p: RequestGroupAction) => ( + this._handlePluginClick(p)} stayOpenAfterClick> + {loadingActions[p.label] ? ( + + ) : ( + + )} + {p.label} + + ))} ); } } -RequestGroupActionsDropdown.propTypes = { - workspace: PropTypes.object.isRequired, - hotKeyRegistry: PropTypes.object.isRequired, - handleCreateRequest: PropTypes.func.isRequired, - handleCreateRequestGroup: PropTypes.func.isRequired, - handleDuplicateRequestGroup: PropTypes.func.isRequired, - handleMoveRequestGroup: PropTypes.func.isRequired, - - // Optional - requestGroup: PropTypes.object, -}; - export default RequestGroupActionsDropdown; diff --git a/packages/insomnia-app/app/ui/components/modals/wrapper-modal.js b/packages/insomnia-app/app/ui/components/modals/wrapper-modal.js index ea8f069df..ed572d885 100644 --- a/packages/insomnia-app/app/ui/components/modals/wrapper-modal.js +++ b/packages/insomnia-app/app/ui/components/modals/wrapper-modal.js @@ -9,6 +9,7 @@ type Props = {}; type State = { title: string, body: React.Node, + bodyHTML: ?string, tall: boolean, }; @@ -22,6 +23,7 @@ class WrapperModal extends React.PureComponent { this.state = { title: '', body: null, + bodyHTML: null, tall: false, }; } @@ -31,10 +33,11 @@ class WrapperModal extends React.PureComponent { } show(options: Object = {}) { - const { title, body, tall } = options; + const { title, body, bodyHTML, tall } = options; this.setState({ title, body, + bodyHTML, tall: !!tall, }); @@ -42,12 +45,17 @@ class WrapperModal extends React.PureComponent { } render() { - const { title, body, tall } = this.state; + const { title, body, bodyHTML, tall } = this.state; + + let finalBody = body; + if (bodyHTML) { + finalBody =
; + } return ( {title || 'Uh Oh!'} - {body} + {finalBody} ); } diff --git a/packages/insomnia-app/app/ui/components/sidebar/sidebar-children.js b/packages/insomnia-app/app/ui/components/sidebar/sidebar-children.js index fdb04415d..2fa9cc139 100644 --- a/packages/insomnia-app/app/ui/components/sidebar/sidebar-children.js +++ b/packages/insomnia-app/app/ui/components/sidebar/sidebar-children.js @@ -7,6 +7,7 @@ import type { RequestGroup } from '../../../models/request-group'; import type { Workspace } from '../../../models/workspace'; import type { Request } from '../../../models/request'; import type { HotKeyRegistry } from '../../../common/hotkeys'; +import type { Environment } from '../../../models/environment'; type Child = { doc: Request | RequestGroup, @@ -31,6 +32,7 @@ type Props = { workspace: Workspace, filter: string, hotKeyRegistry: HotKeyRegistry, + activeEnvironment: Environment | null, // Optional activeRequest?: Request, @@ -53,6 +55,7 @@ class SidebarChildren extends React.PureComponent { activeRequest, workspace, hotKeyRegistry, + activeEnvironment, } = this.props; const activeRequestId = activeRequest ? activeRequest._id : 'n/a'; @@ -119,6 +122,7 @@ class SidebarChildren extends React.PureComponent { requestGroup={requestGroup} hotKeyRegistry={hotKeyRegistry} children={children} + activeEnvironment={activeEnvironment} /> ); }); diff --git a/packages/insomnia-app/app/ui/components/sidebar/sidebar-request-group-row.js b/packages/insomnia-app/app/ui/components/sidebar/sidebar-request-group-row.js index 0daf9184c..f10456660 100644 --- a/packages/insomnia-app/app/ui/components/sidebar/sidebar-request-group-row.js +++ b/packages/insomnia-app/app/ui/components/sidebar/sidebar-request-group-row.js @@ -64,6 +64,7 @@ class SidebarRequestGroupRow extends PureComponent { isDraggingOver, workspace, hotKeyRegistry, + activeEnvironment, } = this.props; const { dragDirection } = this.state; @@ -117,6 +118,7 @@ class SidebarRequestGroupRow extends PureComponent { workspace={workspace} requestGroup={requestGroup} hotKeyRegistry={hotKeyRegistry} + activeEnvironment={activeEnvironment} right />
@@ -176,6 +178,7 @@ SidebarRequestGroupRow.propTypes = { // Optional children: PropTypes.node, + activeEnvironment: PropTypes.object, }; /** diff --git a/packages/insomnia-app/app/ui/components/sidebar/sidebar.js b/packages/insomnia-app/app/ui/components/sidebar/sidebar.js index e90fd7245..261ae6ecf 100644 --- a/packages/insomnia-app/app/ui/components/sidebar/sidebar.js +++ b/packages/insomnia-app/app/ui/components/sidebar/sidebar.js @@ -129,6 +129,7 @@ class Sidebar extends PureComponent { activeRequest={activeRequest} filter={filter || ''} hotKeyRegistry={hotKeyRegistry} + activeEnvironment={activeEnvironment} /> {enableSyncBeta && vcs && isLoggedIn() && (