Skip to content

Commit

Permalink
[Console] Update "Copy as cURL" to interpolate variables and strip re…
Browse files Browse the repository at this point in the history
…quest-body comments (#140262)

* Interpolate variables and strip request-body comments in cURLs

* Address CR change

Co-authored-by: Muhammad Ibragimov <[email protected]>
Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
3 people authored Sep 13, 2022
1 parent b95485e commit dfafa26
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 17 deletions.
16 changes: 13 additions & 3 deletions src/plugins/console/public/application/components/console_menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ interface Props {
interface State {
isPopoverOpen: boolean;
curlCode: string;
curlError: Error | null;
}

export class ConsoleMenu extends Component<Props, State> {
Expand All @@ -40,14 +41,20 @@ export class ConsoleMenu extends Component<Props, State> {
this.state = {
curlCode: '',
isPopoverOpen: false,
curlError: null,
};
}

mouseEnter = () => {
if (this.state.isPopoverOpen) return;
this.props.getCurl().then((text) => {
this.setState({ curlCode: text });
});
this.props
.getCurl()
.then((text) => {
this.setState({ curlCode: text, curlError: null });
})
.catch((e) => {
this.setState({ curlError: e });
});
};

async copyAsCurl() {
Expand All @@ -69,6 +76,9 @@ export class ConsoleMenu extends Component<Props, State> {
}

async copyText(text: string) {
if (this.state.curlError) {
throw this.state.curlError;
}
if (window.navigator?.clipboard) {
await window.navigator.clipboard.writeText(text);
return;
Expand Down
9 changes: 8 additions & 1 deletion src/plugins/console/public/application/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ import {

import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import { KibanaThemeProvider } from '../shared_imports';
import { createStorage, createHistory, createSettings, AutocompleteInfo } from '../services';
import {
createStorage,
createHistory,
createSettings,
AutocompleteInfo,
setStorage,
} from '../services';
import { createUsageTracker } from '../services/tracker';
import * as localStorageObjectClient from '../lib/local_storage_object_client';
import { Main } from './containers';
Expand Down Expand Up @@ -56,6 +62,7 @@ export function renderApp({
engine: window.localStorage,
prefix: 'sense:',
});
setStorage(storage);
const history = createHistory({ storage });
const settings = createSettings({ storage });
const objectStorageClient = localStorageObjectClient.create(storage);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ import { URL } from 'url';
import { create } from './create';
import { XJson } from '@kbn/es-ui-shared-plugin/public';
import editorInput1 from './__fixtures__/editor_input1.txt';
import { setStorage, createStorage } from '../../../services';

const { collapseLiteralStrings } = XJson;

describe('Editor', () => {
let input;
let oldUrl;
let olldWindow;
let storage;

beforeEach(function () {
// Set up our document body
Expand All @@ -43,12 +45,18 @@ describe('Editor', () => {
origin: 'http://localhost:5620',
},
});
storage = createStorage({
engine: global.window.localStorage,
prefix: 'console_test',
});
setStorage(storage);
});
afterEach(function () {
global.URL = oldUrl;
global.window = olldWindow;
$(input.getCoreEditor().getContainer()).hide();
input.autocomplete._test.addChangeListener();
setStorage(null);
});

let testCount = 0;
Expand Down Expand Up @@ -506,4 +514,104 @@ curl -XPOST "http://localhost:9200/_sql?format=txt" -H "kbn-xsrf: reporting" -H
`
curl -XGET "http://localhost:5620/api/spaces/space" -H \"kbn-xsrf: reporting\"`.trim()
);

describe('getRequestsAsCURL', () => {
it('should return empty string if no requests', async () => {
input?.getCoreEditor().setValue('', false);
const curl = await input.getRequestsAsCURL('http://localhost:9200', {
start: { lineNumber: 1 },
end: { lineNumber: 1 },
});
expect(curl).toEqual('');
});

it('should replace variables in the URL', async () => {
storage.set('variables', [{ name: 'exampleVariableA', value: 'valueA' }]);
input?.getCoreEditor().setValue('GET ${exampleVariableA}', false);
const curl = await input.getRequestsAsCURL('http://localhost:9200', {
start: { lineNumber: 1 },
end: { lineNumber: 1 },
});
expect(curl).toContain('valueA');
});

it('should replace variables in the body', async () => {
storage.set('variables', [{ name: 'exampleVariableB', value: 'valueB' }]);
console.log(storage.get('variables'));
input
?.getCoreEditor()
.setValue('GET _search\n{\t\t"query": {\n\t\t\t"${exampleVariableB}": ""\n\t}\n}', false);
const curl = await input.getRequestsAsCURL('http://localhost:9200', {
start: { lineNumber: 1 },
end: { lineNumber: 6 },
});
expect(curl).toContain('valueB');
});

it('should strip comments in the URL', async () => {
input?.getCoreEditor().setValue('GET _search // comment', false);
const curl = await input.getRequestsAsCURL('http://localhost:9200', {
start: { lineNumber: 1 },
end: { lineNumber: 6 },
});
expect(curl).not.toContain('comment');
});

it('should strip comments in the body', async () => {
input
?.getCoreEditor()
.setValue('{\n\t"query": {\n\t\t"match_all": {} // comment \n\t}\n}', false);
const curl = await input.getRequestsAsCURL('http://localhost:9200', {
start: { lineNumber: 1 },
end: { lineNumber: 8 },
});
console.log('curl', curl);
expect(curl).not.toContain('comment');
});

it('should strip multi-line comments in the body', async () => {
input
?.getCoreEditor()
.setValue('{\n\t"query": {\n\t\t"match_all": {} /* comment */\n\t}\n}', false);
const curl = await input.getRequestsAsCURL('http://localhost:9200', {
start: { lineNumber: 1 },
end: { lineNumber: 8 },
});
console.log('curl', curl);
expect(curl).not.toContain('comment');
});

it('should replace multiple variables in the URL', async () => {
storage.set('variables', [
{ name: 'exampleVariableA', value: 'valueA' },
{ name: 'exampleVariableB', value: 'valueB' },
]);
input?.getCoreEditor().setValue('GET ${exampleVariableA}/${exampleVariableB}', false);
const curl = await input.getRequestsAsCURL('http://localhost:9200', {
start: { lineNumber: 1 },
end: { lineNumber: 1 },
});
expect(curl).toContain('valueA');
expect(curl).toContain('valueB');
});

it('should replace multiple variables in the body', async () => {
storage.set('variables', [
{ name: 'exampleVariableA', value: 'valueA' },
{ name: 'exampleVariableB', value: 'valueB' },
]);
input
?.getCoreEditor()
.setValue(
'GET _search\n{\t\t"query": {\n\t\t\t"${exampleVariableA}": "${exampleVariableB}"\n\t}\n}',
false
);
const curl = await input.getRequestsAsCURL('http://localhost:9200', {
start: { lineNumber: 1 },
end: { lineNumber: 6 },
});
expect(curl).toContain('valueA');
expect(curl).toContain('valueB');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import _ from 'lodash';

import { parse } from 'hjson';
import { XJson } from '@kbn/es-ui-shared-plugin/public';

import RowParser from '../../../lib/row_parser';
Expand All @@ -19,6 +19,8 @@ import { constructUrl } from '../../../lib/es/es';
import { CoreEditor, Position, Range } from '../../../types';
import { createTokenIterator } from '../../factories';
import createAutocompleter from '../../../lib/autocomplete/autocomplete';
import { getStorage, StorageKeys } from '../../../services';
import { DEFAULT_VARIABLES } from '../../../../common/constants';

const { collapseLiteralStrings } = XJson;

Expand Down Expand Up @@ -474,7 +476,9 @@ export class SenseEditor {
}, 25);

getRequestsAsCURL = async (elasticsearchBaseUrl: string, range?: Range): Promise<string> => {
const requests = await this.getRequestsInRange(range, true);
const variables = getStorage().get(StorageKeys.VARIABLES, DEFAULT_VARIABLES);
let requests = await this.getRequestsInRange(range, true);
requests = utils.replaceVariables(requests, variables);
const result = _.map(requests, (req) => {
if (typeof req === 'string') {
// no request block
Expand All @@ -490,16 +494,30 @@ export class SenseEditor {

// Append 'kbn-xsrf' header to bypass (XSRF/CSRF) protections
let ret = `curl -X${method.toUpperCase()} "${url}" -H "kbn-xsrf: reporting"`;

if (data && data.length) {
ret += ` -H "Content-Type: application/json" -d'\n`;
const dataAsString = collapseLiteralStrings(data.join('\n'));

// We escape single quoted strings that that are wrapped in single quoted strings
ret += dataAsString.replace(/'/g, "'\\''");
if (data.length > 1) {
ret += '\n';
} // end with a new line
ret += "'";
const joinedData = data.join('\n');
let dataAsString: string;

try {
ret += ` -H "Content-Type: application/json" -d'\n`;

if (utils.hasComments(joinedData)) {
// if there are comments in the data, we need to strip them out
const dataWithoutComments = parse(joinedData);
dataAsString = collapseLiteralStrings(JSON.stringify(dataWithoutComments, null, 2));
} else {
dataAsString = collapseLiteralStrings(joinedData);
}
// We escape single quoted strings that are wrapped in single quoted strings
ret += dataAsString.replace(/'/g, "'\\''");
if (data.length > 1) {
ret += '\n';
} // end with a new line
ret += "'";
} catch (e) {
throw new Error(`Error parsing data: ${e.message}`);
}
}
return ret;
});
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/console/public/lib/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export const replaceVariables = (
});
}

if (req.data.length) {
if (req.data && req.data.length) {
if (bodyRegex.test(req.data[0])) {
const data = req.data[0].replaceAll(bodyRegex, (match) => {
// Sanitize variable name
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/console/public/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

export { createHistory, History } from './history';
export { createStorage, Storage, StorageKeys } from './storage';
export { createStorage, Storage, StorageKeys, setStorage, getStorage } from './storage';
export type { DevToolsSettings } from './settings';
export { createSettings, Settings, DEFAULT_SETTINGS } from './settings';
export { AutocompleteInfo, getAutocompleteInfo, setAutocompleteInfo } from './autocomplete';
3 changes: 3 additions & 0 deletions src/plugins/console/public/services/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import { transform, keys, startsWith } from 'lodash';
import { createGetterSetter } from '@kbn/kibana-utils-plugin/public';

type IStorageEngine = typeof window.localStorage;

Expand Down Expand Up @@ -71,3 +72,5 @@ export class Storage {
export function createStorage(deps: { engine: IStorageEngine; prefix: string }) {
return new Storage(deps.engine, deps.prefix);
}

export const [getStorage, setStorage] = createGetterSetter<Storage>('storage');

0 comments on commit dfafa26

Please sign in to comment.