chore(pre-req): improve the snippets menu for the pre-request script - INS-3319 (#7173)

* chore: add more helper snippets

* chore: add more helper snippets for pre-req scripting

* fix: use correct icons

* feat: enable basic auto code completing

* feedback

---------

Co-authored-by: jackkav <jackkav@gmail.com>
This commit is contained in:
Hexxa
2024-03-18 18:30:11 +08:00
committed by GitHub
parent 85cb6dba3c
commit a69a185fb6
4 changed files with 200 additions and 22 deletions

View File

@@ -15,12 +15,12 @@ export class InsomniaObject {
public baseEnvironment: Environment;
public variables: Variables;
public request: ScriptRequest;
private settings: Settings;
private clientCertificates: ClientCertificate[];
// TODO: follows will be enabled after Insomnia supports them
private _globals: Environment;
private _iterationData: Environment;
private _settings: Settings;
constructor(
rawObj: {
@@ -41,7 +41,7 @@ export class InsomniaObject {
this._iterationData = rawObj.iterationData;
this.variables = rawObj.variables;
this.request = rawObj.request;
this.settings = rawObj.settings;
this._settings = rawObj.settings;
this.clientCertificates = rawObj.clientCertificates;
}
@@ -50,7 +50,7 @@ export class InsomniaObject {
cb: (error?: string, response?: ScriptResponse) => void
) {
// TODO: hook to settings later
return sendRequest(request, cb, this.settings);
return sendRequest(request, cb, this._settings);
}
// TODO: remove this after enabled globals
@@ -63,6 +63,11 @@ export class InsomniaObject {
throw unsupportedError('iterationData', 'environment');
}
// TODO: remove this after enabled iterationData
get settings() {
return undefined;
}
toObject = () => {
return {
globals: this._globals.toObject(),

View File

@@ -333,7 +333,7 @@ export class Request extends Property {
return headersObj;
}
removeHeader(toRemove: string | Header, options: { ignoreCase: boolean }) {
removeHeader(toRemove: string | Header, options?: { ignoreCase: boolean }) {
const filteredHeaders = this.headers.filter(
header => {
if (!header.key) {
@@ -341,7 +341,7 @@ export class Request extends Property {
}
if (typeof toRemove === 'string') {
return options.ignoreCase ?
return options != null && options.ignoreCase ?
header.key.toLocaleLowerCase() !== toRemove.toLocaleLowerCase() :
header.key !== toRemove;
} else if (toRemove instanceof Header) {
@@ -349,7 +349,7 @@ export class Request extends Property {
return false;
}
return options.ignoreCase ?
return options != null && options.ignoreCase ?
header.key.toLocaleLowerCase() !== toRemove.key.toLocaleLowerCase() :
header.key !== toRemove.key;
} else {

View File

@@ -1,5 +1,11 @@
import { Snippet } from 'codemirror';
import React, { FC, Fragment, useRef } from 'react';
import { Settings } from '../../../models/settings';
import { Environment, Variables } from '../../../sdk/objects/environments';
import { InsomniaObject } from '../../../sdk/objects/insomnia';
import { Request as ScriptRequest } from '../../../sdk/objects/request';
import { Url } from '../../../sdk/objects/urls';
import { Dropdown, DropdownButton, DropdownItem, ItemContent } from '../base/dropdown';
import { CodeEditor, CodeEditorHandle } from '../codemirror/code-editor';
@@ -8,19 +14,20 @@ interface Props {
defaultValue: string;
uniquenessKey: string;
className?: string;
settings: Settings;
}
const getEnvVar = 'insomnia.environment.get("variable_name");\n';
// const getGlbVar = 'insomnia.globals.get("variable_name");\n';
const getVar = 'insomnia.variables.get("variable_name");\n';
const getCollectionVar = 'insomnia.collectionVariables.get("variable_name");\n';
const setEnvVar = 'insomnia.environment.set("variable_name", "variable_value");\n';
// const setGlbVar = 'insomnia.globals.set("variable_name", "variable_value");\n';
const setVar = 'insomnia.variables.set("variable_name", "variable_value");\n';
const setCollectionVar = 'insomnia.collectionVariables.set("variable_name", "variable_value");\n';
const unsetEnvVar = 'insomnia.environment.unset("variable_name");\n';
// const unsetGlbVar = 'insomnia.globals.unset("variable_name");\n';
const unsetCollectionVar = 'insomnia.collectionVariables.unset("variable_name");\n';
const getEnvVar = 'insomnia.environment.get("variable_name");';
// const getGlbVar = 'insomnia.globals.get("variable_name");';
const getVar = 'insomnia.variables.get("variable_name");';
const getCollectionVar = 'insomnia.collectionVariables.get("variable_name");';
const setEnvVar = 'insomnia.environment.set("variable_name", "variable_value");';
// const setGlbVar = 'insomnia.globals.set("variable_name", "variable_value");';
const setVar = 'insomnia.variables.set("variable_name", "variable_value");';
const setCollectionVar = 'insomnia.collectionVariables.set("variable_name", "variable_value");';
const unsetEnvVar = 'insomnia.environment.unset("variable_name");';
// const unsetGlbVar = 'insomnia.globals.unset("variable_name");';
const unsetCollectionVar = 'insomnia.collectionVariables.unset("variable_name");';
const sendReq =
`const resp = await new Promise((resolve, reject) => {
insomnia.sendRequest(
@@ -29,8 +36,28 @@ const sendReq =
err != null ? reject(err) : resolve(resp);
}
);
});\n`;
const logValue = 'console.log("log", variableName);\n';
});`;
const logValue = 'console.log("log", variableName);';
const addHeader = "insomnia.request.addHeader({key: 'X-Header-Name', value: 'header_value' });";
const removeHeader = "insomnia.request.removeHeader('X-Header-Name');";
const setMethod = "insomnia.request.method = 'GET';";
const addQueryParams = "insomnia.request.url.addQueryParams('k1=v1');";
const updateRequestBody =
`insomnia.request.body.update({
mode: 'raw',
raw: 'rawContent',
});`;
const updateRequestAuth =
`insomnia.request.auth.update(
{
type: 'bearer',
bearer: [
{key: 'token', value: 'tokenValue'},
],
},
'bearer'
);`;
const lintOptions = {
globals: {
@@ -48,11 +75,64 @@ const lintOptions = {
esversion: 8, // ES8 syntax (async/await, etc)
};
// TODO: We probably don't want to expose every property like .toObject() so we need a way to filter those out
// or make those properties private
// TODO: introduce this functionality for other objects, such as Url, UrlMatchPattern and so on
// TODO: introduce function arguments
// TODO: provide snippets for environment keys if possible
function getPreRequestScriptSnippets(insomniaObject: InsomniaObject, path: string): Snippet[] {
let snippets: Snippet[] = [];
const refs = new Set();
const insomniaRecords = insomniaObject as Record<string, any>;
for (const key in insomniaObject) {
const isPrivate = typeof key === 'string' && key.startsWith('_');
if (isPrivate) {
continue;
}
const value = insomniaRecords[key];
if (typeof key === 'object') {
if (refs.has(value)) {
// avoid cyclic referring
continue;
} else {
refs.add(value);
}
}
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
snippets.push({
displayValue: `${path}.${value}`,
name: `${path}.${key}`,
value: `${path}.${key}`,
});
} else if (typeof value === 'function') {
snippets.push({
displayValue: `${path}.${key}()`,
name: `${path}.${key}()`,
value: `${path}.${key}()`,
});
} else if (Array.isArray(value)) {
for (const item of value) {
snippets = snippets.concat(getPreRequestScriptSnippets(item, `${path}.${key}`));
}
} else {
snippets = snippets.concat(getPreRequestScriptSnippets(value, `${path}.${key}`));
}
}
return snippets;
}
export const PreRequestScriptEditor: FC<Props> = ({
className,
defaultValue,
onChange,
uniquenessKey,
settings,
}) => {
const editorRef = useRef<CodeEditorHandle>(null);
@@ -65,6 +145,7 @@ export const PreRequestScriptEditor: FC<Props> = ({
editorRef.current?.setValue([
...value.split('\n').slice(0, nextRow),
snippet,
'\n',
...value.split('\n').slice(nextRow),
].join('\n'));
@@ -72,6 +153,28 @@ export const PreRequestScriptEditor: FC<Props> = ({
editorRef.current?.setCursorLine(cursorRow + snippet.split('\n').length);
};
// TODO(george): Add more to this object to provide improved autocomplete
const preRequestScriptSnippets = getPreRequestScriptSnippets(
new InsomniaObject({
globals: new Environment({}),
iterationData: new Environment({}),
environment: new Environment({}),
baseEnvironment: new Environment({}),
variables: new Variables({
globals: new Environment({}),
environment: new Environment({}),
collection: new Environment({}),
data: new Environment({}),
}),
request: new ScriptRequest({
url: new Url('http://placeholder.com'),
}),
settings,
clientCertificates: [],
}),
'insomnia',
);
return (
<Fragment>
<div className="h-[calc(100%-var(--line-height-xs))]">
@@ -88,18 +191,18 @@ export const PreRequestScriptEditor: FC<Props> = ({
placeholder="..."
lintOptions={lintOptions}
ref={editorRef}
getAutocompleteSnippets={() => preRequestScriptSnippets}
/>
</div>
<div className="h-[calc(var(--line-height-xs))] border-solid border-t border-[var(--hl-md)] text-[var(--font-size-sm)] p-[var(--padding-xs)]">
<Dropdown
aria-label='Snippets'
aria-label='Variable Snippets'
placement='top left'
triggerButton={
<DropdownButton>
<ItemContent
icon="code"
label='Add Snippets'
onClick={() => addSnippet(getEnvVar)}
label='Variable Snippets'
/>
</DropdownButton>
}
@@ -181,6 +284,75 @@ export const PreRequestScriptEditor: FC<Props> = ({
onClick={() => addSnippet(unsetCollectionVar)}
/>
</DropdownItem>
</Dropdown>
<Dropdown
aria-label='Request Manipulation'
placement='top left'
triggerButton={
<DropdownButton>
<ItemContent
icon="code"
label='Request Manipulation'
/>
</DropdownButton>
}
>
<DropdownItem textValue='Add query param' arial-label={'Add query param'}>
<ItemContent
icon="p"
label='Add a query param'
onClick={() => addSnippet(addQueryParams)}
/>
</DropdownItem>
<DropdownItem textValue='Set method' arial-label={'Set method'}>
<ItemContent
icon="m"
label='Set method'
onClick={() => addSnippet(setMethod)}
/>
</DropdownItem>
<DropdownItem textValue='Add a header' arial-label={'Add a header'}>
<ItemContent
icon="h"
label='Add a header'
onClick={() => addSnippet(addHeader)}
/>
</DropdownItem>
<DropdownItem textValue='Remove header' arial-label={'Remove header'}>
<ItemContent
icon="h"
label='Remove a header'
onClick={() => addSnippet(removeHeader)}
/>
</DropdownItem>
<DropdownItem textValue='Update body as raw' arial-label={'Update body as raw'}>
<ItemContent
icon="b"
label='Update body as raw'
onClick={() => addSnippet(updateRequestBody)}
/>
</DropdownItem>
<DropdownItem textValue='Update auth method' arial-label={'Update auth method'}>
<ItemContent
icon="circle-user"
label='Update auth method'
onClick={() => addSnippet(updateRequestAuth)}
/>
</DropdownItem>
</Dropdown>
<Dropdown
aria-label='Misc'
placement='top left'
triggerButton={
<DropdownButton>
<ItemContent
icon="code"
label='Misc'
/>
</DropdownButton>
}
>
<DropdownItem textValue='Send a request' arial-label={'Send a request'}>
<ItemContent
icon="circle-play"

View File

@@ -310,6 +310,7 @@ export const RequestPane: FC<Props> = ({
uniquenessKey={uniqueKey}
defaultValue={activeRequest.preRequestScript || ''}
onChange={preRequestScript => patchRequest(requestId, { preRequestScript })}
settings={settings}
/>
</ErrorBoundary>
</TabItem>