Skip to content

Commit

Permalink
parse kibana query on paste
Browse files Browse the repository at this point in the history
  • Loading branch information
Carsten König committed Sep 13, 2024
1 parent 2fae910 commit 0e81ff8
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 1.0.12

* parse kibana console queries on paste in REST editor. You can paste kibana console queries into the editor (or use the dedicated button) to fill out the form automatically.
* fix sorting issue when changing indices, fixes [#261](https://github.com/cars10/elasticvue/issues/261)

## 1.0.11
Expand Down
19 changes: 17 additions & 2 deletions src/components/rest/RestQueryForm.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
<template>
<q-form @submit.prevent="sendRequest">
<div class="q-mb-md">
<q-btn :label="t('query.rest.form.paste_kibana')"
flat
size="md"
no-caps
class="btn-link q-py-none q-px-sm"
@click="pasteClipboard" />
</div>

<div class="flex">
<q-select v-model="ownTab.request.method"
:options="HTTP_METHODS"
Expand Down Expand Up @@ -51,7 +60,8 @@

<code-editor v-show="!['GET', 'HEAD'].includes(ownTab.request.method)"
v-model="ownTab.request.body"
:commands="editorCommands" />
:commands="editorCommands"
:on-paste="onPaste" />
</div>
<div class="col-6 q-pl-sm full-height">
<code-viewer :value="ownTab.response.bodyText" />
Expand Down Expand Up @@ -97,6 +107,10 @@
const t = useTranslation()
const props = defineProps<{ tab: IdbRestQueryTab }>()
const emit = defineEmits(['reloadHistory', 'reloadSavedQueries'])
const pasteClipboard = async () => {
const text = await navigator.clipboard.readText()
onPaste(text)
}
const resizeStore = useResizeStore()
const {
Expand All @@ -107,6 +121,7 @@
downloadFileName,
loading,
sendRequest,
responseStatusClass
responseStatusClass,
onPaste
} = useRestQueryForm(props, emit)
</script>
5 changes: 3 additions & 2 deletions src/components/shared/CodeEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
import { useTranslation } from '../../composables/i18n'
import { KeyBinding } from '@codemirror/view'
const props = defineProps<{ modelValue: string, commands?: KeyBinding[] }>()
const props = defineProps<{ modelValue: string, commands?: KeyBinding[], onPaste?: (data: string) => void }>()
const emit = defineEmits(['update:modelValue'])
const t = useTranslation()
const codeEditorStore = useCodeEditorStore()
Expand All @@ -53,7 +53,8 @@
const { copyContent, beautifyEditorValue } = useCodeEditor(editor, {
initialValue: toRef(props, 'modelValue'),
commands: props.commands,
emit
emit,
onPaste: props.onPaste
})
const validJson = computed(() => {
Expand Down
18 changes: 16 additions & 2 deletions src/composables/CodeEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,13 @@ const completions = (context: any) => {
export const useCodeEditor = (editorRef: Ref<HTMLElement | null>, {
initialValue,
emit,
commands
commands,
onPaste
}: {
initialValue: Ref<string>,
emit?: any,
commands?: KeyBinding[]
commands?: KeyBinding[],
onPaste?: (data: string) => void
}) => {
const codeEditorStore = useCodeEditorStore()

Expand All @@ -79,6 +81,17 @@ export const useCodeEditor = (editorRef: Ref<HTMLElement | null>, {
emit('update:modelValue', editorValue())
})

const eventHandlers = []
if (onPaste) {
const handler = EditorView.domEventHandlers({
paste(event) {
const newValue = event.clipboardData?.getData('text')
if (newValue) onPaste(newValue)
}
})
eventHandlers.push(handler)
}

const vimExtension = vim()
codeMirrorEditor = new EditorView({
extensions: [
Expand All @@ -88,6 +101,7 @@ export const useCodeEditor = (editorRef: Ref<HTMLElement | null>, {
json(),
autocompletion({ override: [completions] }),
onChange,
eventHandlers,
keymap.of([indentWithTab]),
Prec.highest(keymap.of(commands || [])),
keymap.of([{ key: 'Ctrl-Alt-l', mac: 'Ctrl-Cmd-l', run: beautifyEditorValue }]),
Expand Down
18 changes: 16 additions & 2 deletions src/composables/components/rest/RestQueryForm.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { computed, ref, toRaw, watch } from 'vue'
import { computed, ref, toRaw, watch, nextTick } from 'vue'
import { buildFetchAuthHeader } from '../../../helpers/elasticsearchAdapter.ts'
import { REQUEST_DEFAULT_HEADERS } from '../../../consts'
import { useConnectionStore } from '../../../store/connection'
Expand All @@ -8,6 +8,7 @@ import { removeComments } from '../../../helpers/json/parse'
import { fetchMethod } from '../../../helpers/fetch'
import { IdbRestQueryTab, IdbRestQueryTabRequest } from '../../../db/types.ts'
import { debounce } from '../../../helpers/debounce.ts'
import { parseKibana } from '../../../helpers/parseKibana.ts'

type RestQueryFormProps = {
tab: IdbRestQueryTab
Expand Down Expand Up @@ -139,6 +140,18 @@ export const useRestQueryForm = (props: RestQueryFormProps, emit: any) => {
return `${ownTab.value.request.method.toLowerCase()}_${ownTab.value.request.path.replace(/[\W_]+/g, '_')}.json`
})

const onPaste = (data: string) => {
const kibanaRequest = parseKibana(data)

nextTick(() => {
if (kibanaRequest.method) {
ownTab.value.request.method = kibanaRequest.method
ownTab.value.request.path = kibanaRequest.path || ''
ownTab.value.request.body = kibanaRequest.body || ''
}
})
}

return {
saveQuery,
ownTab,
Expand All @@ -147,6 +160,7 @@ export const useRestQueryForm = (props: RestQueryFormProps, emit: any) => {
downloadFileName,
loading,
sendRequest,
responseStatusClass
responseStatusClass,
onPaste
}
}
26 changes: 26 additions & 0 deletions src/helpers/parseKibana.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const kibanaBodyRegex = /^(GET|POST|PUT|PATCH|HEAD|OPTIONS|DELETE)(?:\s(.*)?)?/

type KibanaRequest = {
method: string | null,
path: string | null,
body: string | null
}

export const parseKibana = (input: string): KibanaRequest => {
const lines = input.split('\n')
const possibleRequestLine = lines[0]
const [, ...rest] = lines

const matches = possibleRequestLine.match(kibanaBodyRegex)

const matchedMethod = matches?.[1]?.trim()
if (matchedMethod && matchedMethod.length > 0) {
const matchedpath = matches?.[2]?.trim()
const path = matchedpath || ''
const body = rest.join('\n')

return { method: matchedMethod, path, body }
} else {
return { method: null, path: null, body: input }
}
}
3 changes: 2 additions & 1 deletion src/locales/cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,8 @@
},
"send_request": "发送请求",
"save_request": "保存",
"download_as_json": "下载为 JSON 文件"
"download_as_json": "下载为 JSON 文件",
"paste_kibana": "粘贴 kibana 控制台查询"
},
"get_request_hint": {
"cannot_send_body": "你无法通过 {method} 发送请求体,如果要发送请求体,请",
Expand Down
3 changes: 2 additions & 1 deletion src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,8 @@
},
"send_request": "Send request",
"save_request": "Save",
"download_as_json": "Download response body"
"download_as_json": "Download response body",
"paste_kibana": "Paste kibana console query"
},
"get_request_hint": {
"cannot_send_body": "You cannot send a request body via {method}.<br> Please ",
Expand Down
3 changes: 2 additions & 1 deletion src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,8 @@
},
"send_request": "Envoyer la requète",
"save_request": "Enregistrer",
"download_as_json": "Télécharger la réponse JSON"
"download_as_json": "Télécharger la réponse JSON",
"paste_kibana": "Coller la requête de la console kibana"
},
"get_request_hint": {
"cannot_send_body": "Vous ne pouvez pas envoyer de corps via {method}.<br> Veuillez ",
Expand Down
90 changes: 90 additions & 0 deletions tests/unit/helpers/parseKibana.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, it, expect } from 'vitest'
import { parseKibana } from '../../../src/helpers/parseKibana'

describe.concurrent('parseKibana.ts', () => {
describe.concurrent('plain requests', () => {
it('accepts ""', () => {
const input = ''
const request = parseKibana(input)
expect(request.method).toEqual(null)
expect(request.path).toEqual(null)
expect(request.body).toEqual(input)
})

it('accepts "{"query": "*"}"', () => {
const input = '{"query": "*"}'
const request = parseKibana(input)
expect(request.method).toEqual(null)
expect(request.path).toEqual(null)
expect(request.body).toEqual(input)
})

it('accepts "{\n\t"query": "*"\n}\n"', () => {
const input = '{\n\t"query": "*"\n}\n'
const request = parseKibana(input)
expect(request.method).toEqual(null)
expect(request.path).toEqual(null)
expect(request.body).toEqual(input)
})
})

describe.concurrent('kibana single line request', () => {
it('accepts "GET"', () => {
const input = 'GET'
const request = parseKibana(input)
expect(request.method).toEqual('GET')
expect(request.path).toEqual('')
expect(request.body).toEqual('')
})

it('accepts "GET "', () => {
const input = 'GET '
const request = parseKibana(input)
expect(request.method).toEqual('GET')
expect(request.path).toEqual('')
expect(request.body).toEqual('')
})

it('accepts "GET "', () => {
const input = 'GET '
const request = parseKibana(input)
expect(request.method).toEqual('GET')
expect(request.path).toEqual('')
expect(request.body).toEqual('')
})

it('accepts "GET /"', () => {
const input = 'GET /'
const request = parseKibana(input)
expect(request.method).toEqual('GET')
expect(request.path).toEqual('/')
expect(request.body).toEqual('')
})

it('accepts "GET _cat/foo"', () => {
const input = 'GET _cat/foo'
const request = parseKibana(input)
expect(request.method).toEqual('GET')
expect(request.path).toEqual('_cat/foo')
expect(request.body).toEqual('')
})

it('accepts "POST _search?query=foobar"', () => {
const input = 'POST _search?query=foobar'
const request = parseKibana(input)
expect(request.method).toEqual('POST')
expect(request.path).toEqual('_search?query=foobar')
expect(request.body).toEqual('')
})
})

describe.concurrent('kibana multiline request', () => {
it('accepts "POST /_search\n{\n\t"query": {\n\t\t"query_string": {\n\t\t\t"query": "*"\n\t\t}\n\t}\n}"', () => {
const input = 'POST /_search\n{\n\t"query": {\n\t\t"query_string": {\n\t\t\t"query": "*"\n\t\t}\n\t}\n}'
const request = parseKibana(input)
expect(request.method).toEqual('POST')
expect(request.path).toEqual('/_search')
expect(request.body).toEqual('{\n\t"query": {\n\t\t"query_string": {\n\t\t\t"query": "*"\n\t\t}\n\t}\n}')
})
})
})

0 comments on commit 0e81ff8

Please sign in to comment.