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 <anoop.md1421@gmail.com>
This commit is contained in:
Sanjai Kumar 2024-06-21 11:03:50 +05:30 committed by GitHub
parent 963b197afc
commit 01e93b5c2b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 86 additions and 27 deletions

View File

@ -69,6 +69,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
onChange={(newValue) => onUrlChange(newValue)}
onRun={handleRun}
collection={collection}
item={item}
/>
<div className="flex items-center h-full mr-2 cursor-pointer" id="send-request" onClick={handleRun}>
<div

View File

@ -24,13 +24,15 @@ class SingleLineEditor extends Component {
componentDidMount() {
// Initialize CodeMirror as a single line editor
/** @type {import("codemirror").Editor} */
const variables = getAllVariables(this.props.collection, this.props.item);
this.editor = CodeMirror(this.editorRef.current, {
lineWrapping: false,
lineNumbers: false,
theme: this.props.theme === 'dark' ? 'monokai' : 'default',
mode: 'brunovariables',
brunoVarInfo: {
variables: getAllVariables(this.props.collection)
variables
},
scrollbarStyle: null,
tabindex: 0,
@ -82,7 +84,7 @@ class SingleLineEditor extends Component {
});
if (this.props.autocomplete) {
this.editor.on('keyup', (cm, event) => {
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() {

View File

@ -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');

View File

@ -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

View File

@ -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
);
});
};