diff --git a/api/index_management/document.go b/api/index_management/document.go index 75754c77..7d4f2c97 100644 --- a/api/index_management/document.go +++ b/api/index_management/document.go @@ -17,6 +17,7 @@ type docReqBody struct { PageSize int `json:"pageSize"` Filter string `json:"filter"` Cluster string `json:"cluster"` + Keyword string `json:"keyword"` } func (handler APIHandler) HandleDocumentAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { @@ -93,10 +94,14 @@ func (handler APIHandler) HandleDocumentAction(w http.ResponseWriter, req *http. } from := (pageIndex - 1) * pageSize filter := `{"match_all": {}}` + if reqBody.Keyword != "" { + filter = fmt.Sprintf(`{"query_string":{"query":"%s"}}`, reqBody.Keyword) + } if reqBody.Filter != "" { filter = reqBody.Filter } query := fmt.Sprintf(`{"from":%d, "size": %d, "query": %s}`, from, pageSize, filter) + fmt.Println(query) var reqBytes = []byte(query) resp, err := client.SearchWithRawQueryDSL(indexName, reqBytes) if err != nil { diff --git a/web/package.json b/web/package.json index b35eed9c..6332987c 100644 --- a/web/package.json +++ b/web/package.json @@ -8,6 +8,7 @@ "@antv/data-set": "^0.9.6", "@antv/g2-brush": "^0.0.2", "@babel/runtime": "^7.1.2", + "@monaco-editor/react": "^3.7.4", "@svgdotjs/svg.js": "^3.0.16", "antd": "^3.26.18", "antd-table-infinity": "^1.1.6", diff --git a/web/src/pages/DataManagement/Document.js b/web/src/pages/DataManagement/Document.js index c4d20be4..4a1b2828 100644 --- a/web/src/pages/DataManagement/Document.js +++ b/web/src/pages/DataManagement/Document.js @@ -1,7 +1,84 @@ -import React, { Component } from 'react'; +import React, { Component, createRef } from 'react'; +import router from 'umi/router'; import { connect } from 'dva'; import { Col, Form, Row,Select, Input, Card,Icon, Table, InputNumber, Popconfirm, - Divider,Button,Tooltip, Cascader } from 'antd'; + Divider,Button,Tooltip, Cascader, Modal } from 'antd'; +import Editor, {monaco} from '@monaco-editor/react'; + +import {createDependencyProposals} from './autocomplete'; + +function findParentIdentifier(textUntilPosition){ + let chars = textUntilPosition; + let length = chars.length; + let stack = []; + let targetIdx = -1; + for(let i = length-1; i>-1; i--){ + if(chars[i] == '}'){ + stack.push('}'); + }else if(chars[i] == '{'){ + if(stack.length == 0){ + targetIdx = i; + break; + } + stack.pop(); + } + } + let foundColon = false; + for(let i = targetIdx; i > -1; i--){ + if(chars[i] == ":"){ + targetIdx = i; + foundColon = true; + break; + } + } + if(!foundColon){ + return "" + } + let identifer = []; + let startFound = false; + for(let i = targetIdx; i > -1; i--){ + if((chars[i]>='a' && chars[i] <= 'z') || chars[i] == '_'){ + identifer.push(chars[i]); + startFound = true; + }else if(startFound){ + break; + } + } + return identifer.reverse().join(''); +} + +monaco.init().then((mi)=>{ + mi.languages.registerCompletionItemProvider('json', { + provideCompletionItems: function(model, position) { + // find out if we are completing a property in the 'dependencies' object. + var textUntilPosition = model.getValueInRange({startLineNumber: 1, startColumn: 1, endLineNumber: position.lineNumber, endColumn: position.column}); + + if(textUntilPosition.indexOf('{') < 0){ + return { suggestions: [] }; + } + + let key = findParentIdentifier(textUntilPosition); + //console.log(key); + // var match = textUntilPosition.match(/"match"\s*:\s*\{\s*("[^"]*"\s*:\s*"[^"]*"\s*,\s*)*([^"]*)?$/); + // if (!match) { + // return { suggestions: [] }; + // } + var word = model.getWordUntilPosition(position); + + var range = { + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + startColumn: word.startColumn, + endColumn: word.endColumn + }; + return { + suggestions: createDependencyProposals(key, range, mi) + }; + } + }); + +}) + const {Option} = Select; const EditableContext = React.createContext(); @@ -130,7 +207,7 @@ class EditableCell extends React.Component { dispatch({ type: 'document/addDocItem', payload: { - index: doclist.index, + index: doclist._index, data: row, } }) @@ -244,22 +321,37 @@ class EditableCell extends React.Component { @Form.create() class Doucment extends React.Component { state={ - bodyDisplay: 'none', + bodyDisplay: 'none', } - + // constructor(props){ + // super(props); + // this.filterGetter = createRef(); + // } + fetchData = (params) => { const {dispatch} = this.props; - dispatch({ + return dispatch({ type: 'document/fetchDocList', payload: params, }) } + + handleEditorDidMount = (_valueGetter) =>{ + this.filterGetter = _valueGetter; + } componentDidMount(){ + const {location } = this.props; + //console.log(match, location); + let index = location.query.index || 'infini-test'; + let cluster = location.query.cluster || 'single-es'; + this.indexEl.setState({ + value: [cluster, index] + }) this.fetchData({ pageSize: 10, pageIndex: 1, - index: 'infini-test', + index, }) } @@ -268,6 +360,21 @@ class Doucment extends React.Component { if(!document.data || document.data.length == 0 || document.isAddNew){ return; } + let {indices} = document; + let _index = indices[0]; + if(indices && indices.length > 1){ + console.log(this.indexSelEl); + let vals = this.indexSelEl.rcSelect.state.value; + if(vals.length == 0){ + Modal.error({ + title: '系统提示', + content: '请选择新建文档目标索引', + }); + return + }else{ + _index = vals[0]; + } + } let keys = Object.keys(document.data[0]) let newDoc = {}; for(let key of keys){ @@ -278,36 +385,45 @@ class Doucment extends React.Component { payload: { docItem: newDoc, extra: { - isAddNew: true + isAddNew: true, + _index } }, }) } handleSearchClick = (value)=>{ - const [cluster, index] = this.indexEl.state.value; - let targetIndex = index; - if(value != ""){ - targetIndex = value; + let [cluster, index] = this.indexEl.state.value; + let rewriteIndex = this.reindexEl.state.value; + if(typeof rewriteIndex !== 'undefined' && rewriteIndex !=""){ + index = rewriteIndex; } - console.log(targetIndex); + this.fetchData({ cluster, - index: targetIndex, + index: index, pageSize: 10, pageIndex: 1, - filter: this.filterEl.state.value, + //filter: this.filterEl.state.value, + filter: this.filterGetter(), + keyword: value, + }).then(()=>{ + if(this.hashChanged){ + router.push(`/data/doc?index=${index}`); + this.hashChanged = !this.hashChanged; + } }) + } - + renderNew = ()=>{ const {indices} = this.props.document; - if((indices && indices.length > 1)){ - return; - } + // if((indices && indices.length > 1)){ + // return; + // } return (
- {(indices && indices.length > 1) ? ({this.indexSelEl=el}} style={{width: 200, marginRight:5}} placeholder="please select a index"> {indices.map(item=>{ return ({item}) })} @@ -328,25 +444,30 @@ class Doucment extends React.Component { { value: 'infini-test', label: 'infini-test', + }, + { + value: 'infini-test1', + label: 'infini-test1', } ]}]; return (
- + {this.indexEl=el}} style={{width: '20%'}} - onChange={(value, selectedOptions)=>{console.log(value)}} + onChange={(values, selectedOptions)=>{this.reindexEl.setState({value: values[1]}); this.hashChanged = true;}} placeholder="Please select index" showSearch={{filter: (inputValue, path)=>path.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1) }} /> + {this.reindexEl=el}} placeholder="rewrite index or index pattern" style={{width: '20%'}}/> @@ -369,8 +490,22 @@ class Doucment extends React.Component { - - {this.filterEl=el}} placeholder="input query filter (elasticsearch query DSL)" rows={8}/> + + {/* {this.filterEl=el}} placeholder="input query filter (elasticsearch query DSL)" rows={8}/> */} + diff --git a/web/src/pages/DataManagement/autocomplete.js b/web/src/pages/DataManagement/autocomplete.js new file mode 100644 index 00000000..cdd48e84 --- /dev/null +++ b/web/src/pages/DataManagement/autocomplete.js @@ -0,0 +1,143 @@ + +function registerKeyProposals(key){ + let proposals = { + 'bool': [{ + label: 'filter', + documentation: "filter", + insertText: '"filter": [\n\t{}\n]', + },{ + label: 'minimum_should_match', + insertText: '"minimum_should_match": 1', + },{ + label: 'must', + insertText: '"must": [\n\t{}\n]', + },{ + label: 'must_not', + insertText: '"must_not": [\n\t{}\n]', + },{ + label: 'should', + insertText: '"should": [\n\t{}\n]', + }], + 'multi_match':[{ + label: 'analyzer', + insertText: '"analyzer": ""', + },{ + label: 'boost', + insertText: '"boost": 1', + },{ + label: 'fuzziness', + insertText: '"fuzziness": 1', + },{ + label: 'minimum_should_match', + insertText: '"minimum_should_match": 1', + },{ + label: 'operator', + insertText: '"operator": "and"', + },{ + label: 'type', + insertText: '"type": "best_fields"', + }] + }; + if(proposals[key]){ + return proposals[key]; + }else{ + return []; + } +} + +export function createDependencyProposals(key, range, mi) { + if(['', 'must', 'must_not', 'should'].includes(key)){ + return queryProposals.map(p=>{ + return { + kind: mi.languages.CompletionItemKind.Property, + ...p, + insertTextRules: mi.languages.CompletionItemInsertTextRule.InsertAsSnippet, + range: range + } + }) + } + return registerKeyProposals(key).map(p=>{ + return { + kind: mi.languages.CompletionItemKind.Property, + ...p, + insertTextRules: mi.languages.CompletionItemInsertTextRule.InsertAsSnippet, + range: range + } + }); + +} + +let queryProposals = [{ + label: 'bool', + documentation: "bool query", + insertText: '"bool": {}', +},{ + label: 'exists', + documentation: "exists query", + insertText: '"exists": {}', +},{ + label: 'fuzzy', + documentation: "fuzzy query", + insertText: '"fuzzy": {\n\t"${1:FIELD}": {}\n}', + insertTextRules: 1, +},{ + label: 'match', + documentation: "match query", + insertText: '"match": {\n\t"${1:FIELD}": "${2:TEXT}"\n}', + insertTextRules: 1, +},{ + label: 'match_all', + documentation: "match_all query", + insertText: '"match_all": {}', + +},{ + label: 'match_phrase', + documentation: "match_phrease query", + insertText: '"match_phrase": {\n\t"${1:FIELD}": "${2:PHRASE}"\n}', + insertTextRules: 1, +},{ + label: 'match_phrase_prefix', + documentation: "match_phrease query", + insertText: '"match_phrase_prefix": {\n\t"${1:FIELD}": "${2:PREFIX}"\n}', + insertTextRules: 1, +},{ + label: 'multi_match', + documentation: "multi_match query", + insertText: '"multi_match": {\n\t"query": "${1:}",\n\t"fields": [${2:}]\n}', + insertTextRules: 1, +},{ + label: 'nested', + documentation: "nested query", + insertText: '"nested": {\n\t"path": "${1:path_to_nested_doc}",\n\t"query": {}\n}', + insertTextRules: 1, +},{ + label: 'prefix', + documentation: "prefix query", + insertText: '"prefix": {\n\t"${1:FIELD}": {\n\t\t"value": "${2:}"\n\t}\n}', + insertTextRules: 1, +},{ + label: 'query_string', + documentation: "query_string query", + insertText: '"query_string": {\n\t"default_field": "${1:FIELD}",\n\t"query": "${2:this AND that OR thus}"\n}', + insertTextRules: 1, +},{ + label: 'range', + documentation: "range query", + insertText: '"range": {\n\t"${1:FIELD}":{\n\t\t"gte": 0,\n\t\t"lte": 10\n\t}\n}', + insertTextRules: 1, +},{ + label: 'regexp', + documentation: "regexp query", + insertText: '"regexp": {\n\t"${1:FIELD}": "${2:REGEXP}"\n}', + insertTextRules: 1, +},{ + label: 'term', + documentation: "term query", + insertText: '"term": {\n\t"${1:FIELD}": {\n\t\t"value": "${2:}"\n\t}\n}', + insertTextRules: 1, +},{ + label: 'terms', + documentation: "terms query", + insertText: '"terms": {\n\t"${1:FIELD}": [\n\t\t"VALUE1",\n\t\t"VALUE2"\n\t]\n}', + insertTextRules: 1, +},] \ No newline at end of file