From 01e93b5c2be98c61adf9366b8525e31911cc2230 Mon Sep 17 00:00:00 2001 From: Sanjai Kumar <84461672+sanjai0py@users.noreply.github.com> Date: Fri, 21 Jun 2024 11:03:50 +0530 Subject: [PATCH] Feat/highlight path params (#2415) * fix: Update CodeMirror mode to use combinedmode for SingleLineEditor. This will highlight the pathparams in the QueryUrl. * Refactor: Updated url highlighting. * refactor: Improved the hinting part. * refactor: CodeEditor, MultiLineEditor, and QueryEditor to use defineCombinedCodeMirrorMode for highlighting Bruno variables instead of defineCodeMirrorBrunoVariablesMode * refactor: Updated defineCombinedCodeMirrorMode to defineCodeMirrorCombinedVariablesMode. Now the pathparams at the end of the URL is also highlighted. * refactor: Update CodeMirror mode to use defineCodeMirrorBrunoVariablesMode instead of defineCodeMirrorCombinedVariablesMode. Now the path params in the URL will be highlighted on application open. --------- Co-authored-by: Anoop M D --- .../components/RequestPane/QueryUrl/index.js | 1 + .../src/components/SingleLineEditor/index.js | 18 +++--- .../src/utils/codemirror/brunoVarInfo.js | 21 +++++-- .../bruno-app/src/utils/collections/index.js | 18 +++++- .../bruno-app/src/utils/common/codemirror.js | 55 ++++++++++++++----- 5 files changed, 86 insertions(+), 27 deletions(-) diff --git a/packages/bruno-app/src/components/RequestPane/QueryUrl/index.js b/packages/bruno-app/src/components/RequestPane/QueryUrl/index.js index 4989ac80d..88fe4ee01 100644 --- a/packages/bruno-app/src/components/RequestPane/QueryUrl/index.js +++ b/packages/bruno-app/src/components/RequestPane/QueryUrl/index.js @@ -69,6 +69,7 @@ const QueryUrl = ({ item, collection, handleRun }) => { onChange={(newValue) => onUrlChange(newValue)} onRun={handleRun} collection={collection} + item={item} />
{ - if (!cm.state.completionActive /*Enables keyboard navigation in autocomplete list*/ && event.keyCode != 13) { + if (!cm.state.completionActive /*Enables keyboard navigation in autocomplete list*/ && event.key !== 'Enter') { /*Enter - do not open autocomplete list just after item has been selected in it*/ CodeMirror.commands.autocomplete(cm, CodeMirror.hint.anyword, { autocomplete: this.props.autocomplete }); } @@ -90,7 +92,7 @@ class SingleLineEditor extends Component { } this.editor.setValue(String(this.props.value) || ''); this.editor.on('change', this._onEdit); - this.addOverlay(); + this.addOverlay(variables); } _onEdit = () => { @@ -108,10 +110,10 @@ class SingleLineEditor extends Component { // event loop. this.ignoreChangeEvent = true; - let variables = getAllVariables(this.props.collection); + let variables = getAllVariables(this.props.collection, this.props.item); if (!isEqual(variables, this.variables)) { this.editor.options.brunoVarInfo.variables = variables; - this.addOverlay(); + this.addOverlay(variables); } if (this.props.theme !== prevProps.theme && this.editor) { this.editor.setOption('theme', this.props.theme === 'dark' ? 'monokai' : 'default'); @@ -127,12 +129,10 @@ class SingleLineEditor extends Component { this.editor.getWrapperElement().remove(); } - addOverlay = () => { - let variables = getAllVariables(this.props.collection); + addOverlay = (variables) => { this.variables = variables; - defineCodeMirrorBrunoVariablesMode(variables, 'text/plain'); - this.editor.setOption('mode', 'brunovariables'); + this.editor.setOption('mode', 'combinedmode'); }; render() { diff --git a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js index 045c6acf7..1632aa43a 100644 --- a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js +++ b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js @@ -23,10 +23,23 @@ if (!SERVER_RENDERED) { if (!str || !str.length || typeof str !== 'string') { return; } - // str is of format {{variableName}}, extract variableName - // we are seeing that from the gql query editor, the token string is of format variableName - const variableName = str.replace('{{', '').replace('}}', '').trim(); - const variableValue = interpolate(get(options.variables, variableName), options.variables); + + // str is of format {{variableName}} or :variableName, extract variableName + let variableName; + let variableValue; + + if (str.startsWith('{{')) { + variableName = str.replace('{{', '').replace('}}', '').trim(); + variableValue = interpolate(get(options.variables, variableName), options.variables); + } else if (str.startsWith(':')) { + variableName = str.replace(':', '').trim(); + variableValue = + options.variables && options.variables.pathParams ? options.variables.pathParams[variableName] : undefined; + } + + if (variableValue === undefined) { + return; + } const into = document.createElement('div'); const descriptionDiv = document.createElement('div'); diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index 8b08174e2..6145ee200 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -657,6 +657,18 @@ export const getEnvironmentVariables = (collection) => { return variables; }; +const getPathParams = (item) => { + let pathParams = {}; + if (item && item.request && item.request.params) { + item.request.params.forEach((param) => { + if (param.type === 'path' && param.name && param.value) { + pathParams[param.name] = param.value; + } + }); + } + return pathParams; +}; + export const getTotalRequestCountInCollection = (collection) => { let count = 0; each(collection.items, (item) => { @@ -670,12 +682,16 @@ export const getTotalRequestCountInCollection = (collection) => { return count; }; -export const getAllVariables = (collection) => { +export const getAllVariables = (collection, item) => { const environmentVariables = getEnvironmentVariables(collection); + const pathParams = getPathParams(item); return { ...environmentVariables, ...collection.collectionVariables, + pathParams: { + ...pathParams + }, process: { env: { ...collection.processEnvVariables diff --git a/packages/bruno-app/src/utils/common/codemirror.js b/packages/bruno-app/src/utils/common/codemirror.js index a6bf52aed..ecd197428 100644 --- a/packages/bruno-app/src/utils/common/codemirror.js +++ b/packages/bruno-app/src/utils/common/codemirror.js @@ -13,32 +13,61 @@ const pathFoundInVariables = (path, obj) => { }; export const defineCodeMirrorBrunoVariablesMode = (variables, mode) => { - CodeMirror.defineMode('brunovariables', function (config, parserConfig) { - let variablesOverlay = { - token: function (stream, state) { + CodeMirror.defineMode('combinedmode', function (config, parserConfig) { + const variablesOverlay = { + token: function (stream) { if (stream.match('{{', true)) { let ch; let word = ''; while ((ch = stream.next()) != null) { - if (ch == '}' && stream.next() == '}') { + if (ch === '}' && stream.peek() === '}') { stream.eat('}'); - let found = pathFoundInVariables(word, variables); - if (found) { - return 'variable-valid random-' + (Math.random() + 1).toString(36).substring(9); - } else { - return 'variable-invalid random-' + (Math.random() + 1).toString(36).substring(9); - } - // Random classname added so adjacent variables are not rendered in the same SPAN by CodeMirror. + const found = pathFoundInVariables(word, variables); + const status = found ? 'valid' : 'invalid'; + const randomClass = `random-${(Math.random() + 1).toString(36).substring(9)}`; + return `variable-${status} ${randomClass}`; } word += ch; } } - while (stream.next() != null && !stream.match('{{', false)) {} + stream.skipTo('{{') || stream.skipToEnd(); return null; } }; - return CodeMirror.overlayMode(CodeMirror.getMode(config, parserConfig.backdrop || mode), variablesOverlay); + const urlPathParamsOverlay = { + token: function (stream) { + if (stream.match(':', true)) { + let ch; + let word = ''; + while ((ch = stream.next()) != null) { + if (ch === '/' || ch === '?' || ch === '&' || ch === '=') { + stream.backUp(1); + const found = pathFoundInVariables(word, variables?.pathParams); + const status = found ? 'valid' : 'invalid'; + const randomClass = `random-${(Math.random() + 1).toString(36).substring(9)}`; + return `variable-${status} ${randomClass}`; + } + word += ch; + } + + // If we've consumed all characters and the word is not empty, it might be a path parameter at the end of the URL. + if (word) { + const found = pathFoundInVariables(word, variables?.pathParams); + const status = found ? 'valid' : 'invalid'; + const randomClass = `random-${(Math.random() + 1).toString(36).substring(9)}`; + return `variable-${status} ${randomClass}`; + } + } + stream.skipTo(':') || stream.skipToEnd(); + return null; + } + }; + + return CodeMirror.overlayMode( + CodeMirror.overlayMode(CodeMirror.getMode(config, parserConfig.backdrop || mode), variablesOverlay), + urlPathParamsOverlay + ); }); };