This is an automated email from the ASF dual-hosted git repository. kishoreg pushed a commit to branch zookeeper-put-api in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git
commit d6b9bef3a1ae10c0c7ec3707652e950c6cdca147 Author: Sanket Shah <er.sankets...@gmail.com> AuthorDate: Wed Aug 5 19:54:37 2020 +0530 adding autocomplete in sql editor --- .../src/main/resources/app/pages/Query.tsx | 161 +++++++++++++------- .../src/main/resources/app/styles/styles.css | 61 ++++++++ .../main/resources/app/utils/PinotMethodUtils.ts | 11 +- .../src/main/resources/app/utils/Utils.tsx | 163 ++++++++++++++++++++- pinot-controller/src/main/resources/package.json | 1 + 5 files changed, 342 insertions(+), 55 deletions(-) diff --git a/pinot-controller/src/main/resources/app/pages/Query.tsx b/pinot-controller/src/main/resources/app/pages/Query.tsx index fdb9b88..e3b67f5 100644 --- a/pinot-controller/src/main/resources/app/pages/Query.tsx +++ b/pinot-controller/src/main/resources/app/pages/Query.tsx @@ -29,6 +29,10 @@ import 'codemirror/lib/codemirror.css'; import 'codemirror/theme/material.css'; import 'codemirror/mode/javascript/javascript'; import 'codemirror/mode/sql/sql'; +import 'codemirror/addon/hint/show-hint'; +import 'codemirror/addon/hint/sql-hint'; +import 'codemirror/addon/hint/show-hint.css'; +import NativeCodeMirror from 'codemirror'; import _ from 'lodash'; import FormControlLabel from '@material-ui/core/FormControlLabel'; import Switch from '@material-ui/core/Switch'; @@ -40,6 +44,7 @@ import QuerySideBar from '../components/Query/QuerySideBar'; import TableToolbar from '../components/TableToolbar'; import SimpleAccordion from '../components/SimpleAccordion'; import PinotMethodUtils from '../utils/PinotMethodUtils'; +import '../styles/styles.css'; const useStyles = makeStyles((theme) => ({ title: { @@ -48,7 +53,11 @@ const useStyles = makeStyles((theme) => ({ }, rightPanel: {}, codeMirror: { - '& .CodeMirror': { height: 100, border: '1px solid #BDCCD9', fontSize: '13px' }, + '& .CodeMirror': { + height: 100, + border: '1px solid #BDCCD9', + fontSize: '13px', + }, }, queryOutput: { '& .CodeMirror': { height: 430, border: '1px solid #BDCCD9' }, @@ -74,8 +83,8 @@ const useStyles = makeStyles((theme) => ({ marginBottom: '20px', }, sqlError: { - whiteSpace: 'pre-wrap' - } + whiteSpace: 'pre-wrap', + }, })); const jsonoptions = { @@ -85,16 +94,19 @@ const jsonoptions = { gutters: ['CodeMirror-lint-markers'], lint: true, theme: 'default', - readOnly: true + readOnly: true, }; const sqloptions = { lineNumbers: true, - mode: 'sql', + mode: 'text/x-sql', styleActiveLine: true, - gutters: ['CodeMirror-lint-markers'], lint: true, - theme: 'default' + theme: 'default', + indentWithTabs: true, + smartIndent: true, + lineWrapping: true, + extraKeys: { "'@'": 'autocomplete' }, }; const QueryPage = () => { @@ -125,13 +137,13 @@ const QueryPage = () => { const [queryStats, setQueryStats] = useState<TableData>({ columns: [], - records: [] + records: [], }); const [checked, setChecked] = React.useState({ tracing: false, querySyntaxPQL: false, - showResultJSON: false + showResultJSON: false, }); const [copyMsg, showCopyMsg] = React.useState(false); @@ -162,10 +174,14 @@ const QueryPage = () => { }); } - const results = await PinotMethodUtils.getQueryResults(params, url, checked); + const results = await PinotMethodUtils.getQueryResults( + params, + url, + checked + ); setResultError(results.error || ''); - setResultData(results.result || {columns: [], records: []}); - setQueryStats(results.queryStats || {columns: [], records: []}); + setResultData(results.result || { columns: [], records: [] }); + setQueryStats(results.queryStats || { columns: [], records: [] }); setOutputResult(JSON.stringify(results.data, null, 2) || ''); setQueryLoader(false); }; @@ -223,6 +239,40 @@ const QueryPage = () => { fetchData(); }, []); + const handleSqlHints = (cm: NativeCodeMirror.Editor) => { + const tableNames = []; + tableList.records.forEach((obj, i) => { + tableNames.push(obj[i]); + }); + const columnNames = tableSchema.records.map((obj) => { + return obj[0]; + }); + const hintOptions = []; + const defaultHint = (NativeCodeMirror as any).hint.sql(cm); + + Array.prototype.push.apply(hintOptions, Utils.generateCodeMirrorOptions(tableNames, 'TABLE')); + Array.prototype.push.apply(hintOptions, Utils.generateCodeMirrorOptions(columnNames, 'COLUMNS')); + + const cur = cm.getCursor(); + const curLine = cm.getLine(cur.line); + let start = cur.ch; + let end = start; + // eslint-disable-next-line no-plusplus + while (end < curLine.length && /[\w$]/.test(curLine.charAt(end))) ++end; + // eslint-disable-next-line no-plusplus + while (start && /[\w$]/.test(curLine.charAt(start - 1))) --start; + const curWord = start !== end && curLine.slice(start, end); + const regex = new RegExp(`^${ curWord}`, 'i'); + + const finalList = (!curWord ? hintOptions : hintOptions.filter(function (item) { + return item.displayText.match(regex); + })).sort(); + + Array.prototype.push.apply(defaultHint.list, finalList); + + return defaultHint; + }; + return fetching ? ( <AppLoader /> ) : ( @@ -235,13 +285,27 @@ const QueryPage = () => { selectedTable={selectedTable} /> </Grid> - <Grid item xs style={{ padding: 20, backgroundColor: 'white', maxHeight: 'calc(100vh - 70px)', overflowY: 'auto' }}> + <Grid + item + xs + style={{ + padding: 20, + backgroundColor: 'white', + maxHeight: 'calc(100vh - 70px)', + overflowY: 'auto', + }} + > <Grid container> <Grid item xs={12} className={classes.rightPanel}> <div className={classes.sqlDiv}> <TableToolbar name="SQL Editor" showSearchBox={false} /> <CodeMirror - options={sqloptions} + options={{ + ...sqloptions, + hintOptions: { + hint: handleSqlHints, + }, + }} value={inputQuery} onChange={handleOutputDataChange} className={classes.codeMirror} @@ -281,16 +345,17 @@ const QueryPage = () => { </Grid> </Grid> - {queryLoader ? + {queryLoader ? ( <AppLoader /> - : + ) : ( <> - { - resultError ? - <Alert severity="error" className={classes.sqlError}>{resultError}</Alert> - : + {resultError ? ( + <Alert severity="error" className={classes.sqlError}> + {resultError} + </Alert> + ) : ( <> - {queryStats.records.length ? + {queryStats.records.length ? ( <Grid item xs style={{ backgroundColor: 'white' }}> <CustomizedTables title="Query Response Stats" @@ -299,8 +364,7 @@ const QueryPage = () => { inAccordionFormat={true} /> </Grid> - : null - } + ) : null} <Grid item xs style={{ backgroundColor: 'white' }}> {resultData.records.length ? ( @@ -338,7 +402,8 @@ const QueryPage = () => { icon={<FileCopyIcon fontSize="inherit" />} severity="info" > - Copied {resultData.records.length} rows to Clipboard + Copied {resultData.records.length} rows to + Clipboard </Alert> ) : null} @@ -355,37 +420,35 @@ const QueryPage = () => { className={classes.runNowBtn} /> </Grid> - {!checked.showResultJSON - ? - <CustomizedTables - title="Query Result" - data={resultData} - isPagination - isSticky={true} - showSearchBox={true} - inAccordionFormat={true} + {!checked.showResultJSON ? ( + <CustomizedTables + title="Query Result" + data={resultData} + isPagination + isSticky={true} + showSearchBox={true} + inAccordionFormat={true} + /> + ) : resultData.records.length ? ( + <SimpleAccordion + headerTitle="Query Result (JSON Format)" + showSearchBox={false} + > + <CodeMirror + options={jsonoptions} + value={outputResult} + className={classes.queryOutput} + autoCursor={false} /> - : - resultData.records.length ? ( - <SimpleAccordion - headerTitle="Query Result (JSON Format)" - showSearchBox={false} - > - <CodeMirror - options={jsonoptions} - value={outputResult} - className={classes.queryOutput} - autoCursor={false} - /> - </SimpleAccordion> - ) : null} + </SimpleAccordion> + ) : null} </> ) : null} </Grid> </> - } + )} </> - } + )} </Grid> </Grid> </Grid> diff --git a/pinot-controller/src/main/resources/app/styles/styles.css b/pinot-controller/src/main/resources/app/styles/styles.css index 8e764f2..d3c2d97 100644 --- a/pinot-controller/src/main/resources/app/styles/styles.css +++ b/pinot-controller/src/main/resources/app/styles/styles.css @@ -19,4 +19,65 @@ body { font-family: 'Source Sans Pro', sans-serif; +} + +li.CodeMirror-hint { + margin: 0; + padding: 4px 0 4px 25px; + border-radius: 2px; + white-space: pre; + color: black; + cursor: pointer; + position: relative; + line-height: 1.5; +} + +li.CodeMirror-hint:before { + position: absolute; + left: 5px; + top: 5px; + border-radius: 50%; + font-size: 12px; + height: 15px; + width: 15px; + line-height: 15px; + text-align: center; + color: white; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +li.codemirror-table.CodeMirror-hint, +li.codemirror-column.CodeMirror-hint{ + color: black; +} + +li.codemirror-table.CodeMirror-hint > div > span.funcText > span, +li.codemirror-column.CodeMirror-hint > div > span.funcText > span{ + font-weight: bold; + font-size: 10px; +} + +li.codemirror-table.CodeMirror-hint::before { + content: "T"; + background: #2F7E89; +} + +li.codemirror-column.CodeMirror-hint::before { + content: "C"; + background: #05a; +} + +.CodeMirror-hints { + padding: 4px; + box-shadow: 0px 0px 5px rgba(0,0,0,.2); + border-radius: 0px; + font-family: 'Source Sans Pro', sans-serif; + font-size: 12px; + border: 1px solid #eaeaea; + min-width: 140px; +} + +li.CodeMirror-hint-active { + background-color: #dae2f2; } \ No newline at end of file diff --git a/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts b/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts index ff80e01..30947cf 100644 --- a/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts +++ b/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts @@ -92,7 +92,7 @@ const getInstanceData = (instances, liveInstanceArr) => { return Promise.all(promiseArr).then((result) => { return { - columns: ['Insance Name', 'Enabled', 'Hostname', 'Port', 'Status'], + columns: ['Instance Name', 'Enabled', 'Hostname', 'Port', 'Status'], records: [ ...result.map(({ data }) => [ data.instanceName, @@ -533,6 +533,15 @@ const getNodeData = (path) => { const currentNodeData = results[0].data || {}; const currentNodeListStat = results[1].data; const currentNodeMetadata = results[2].data; + + if(currentNodeMetadata['ctime'] || currentNodeMetadata['mtime']){ + currentNodeMetadata['ctime'] = moment(+currentNodeMetadata['ctime']).format( + 'MMMM Do YYYY, h:mm:ss' + ); + currentNodeMetadata['mtime'] = moment(+currentNodeMetadata['mtime']).format( + 'MMMM Do YYYY, h:mm:ss' + ); + } return { currentNodeData, currentNodeMetadata, currentNodeListStat }; }); }; diff --git a/pinot-controller/src/main/resources/app/utils/Utils.tsx b/pinot-controller/src/main/resources/app/utils/Utils.tsx index 8fa36d8..7f80dfb 100644 --- a/pinot-controller/src/main/resources/app/utils/Utils.tsx +++ b/pinot-controller/src/main/resources/app/utils/Utils.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-nested-ternary */ /** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -49,7 +50,7 @@ const tableFormat = (data) => { const results = []; rows.forEach((singleRow) => { const obj = {}; - singleRow.forEach((val: any, index: number)=>{ + singleRow.forEach((val: any, index: number) => { obj[header[index]] = val; }); results.push(obj); @@ -64,14 +65,14 @@ const getSegmentStatus = (idealStateObj, externalViewObj) => { const externalSegmentKeys = Object.keys(externalViewObj); const externalSegmentCount = externalSegmentKeys.length; - if(idealSegmentCount !== externalSegmentCount){ + if (idealSegmentCount !== externalSegmentCount) { return 'Bad'; } let segmentStatus = 'Good'; idealSegmentKeys.map((segmentKey) => { - if(segmentStatus === 'Good'){ - if( !_.isEqual( idealStateObj[segmentKey], externalViewObj[segmentKey] ) ){ + if (segmentStatus === 'Good') { + if (!_.isEqual(idealStateObj[segmentKey], externalViewObj[segmentKey])) { segmentStatus = 'Bad'; } } @@ -90,9 +91,161 @@ const findNestedObj = (entireObj, keyToFind, valToFind) => { return foundObj; }; +const generateCodeMirrorOptions = (array, type, modeType?) => { + const arr = []; + // eslint-disable-next-line no-shadow + const nestedFields = (arrayList, type, level, oldObj?) => { + _.map(arrayList, (a) => { + const obj = { + text: a.displayName || a.name || a, + displayText: a.displayName || a.name || a, + filterText: oldObj + ? `${oldObj.filterText}.${a.displayName || a.name || a}` + : a.displayName || a.name || a, + argsType: '', + description: '', + render: (el, cm, data) => {}, + className: + type === 'FUNCTION' + ? 'codemirror-func' + : type === 'SQL' + ? 'codemirror-sql' + : type === 'BINARY-OPERATORS' + ? 'codemirror-Operators' + : type === 'TABLE' + ? 'codemirror-table' + : 'codemirror-column' + }; + obj[type === 'FUNCTION' ? 'returnType' : 'type'] = + type === 'FUNCTION' + ? a.returnType + : type === 'SQL' + ? 'SQL' + : type === 'BINARY-OPERATORS' + ? 'Binary Operators' + : a.type; + if (type === 'FUNCTION') { + obj.argsType = a.argTypes.toString(); + obj.description = a.description + ? `Description: ${a.description}` + : undefined; + } + obj.render = (el, cm, data) => { + codeMirrorOptionsTemplate(el, data); + }; + + if (oldObj === undefined) { + arr.push(obj); + } else { + const index = _.findIndex( + arr, + (n) => n.filterText === oldObj.filterText + ); + if (index !== -1) { + const name = obj.displayText; + if (modeType === 'sql') { + obj.displayText = `${oldObj.displayText}.${name}`; + obj.text = `${oldObj.text}.${name}`; + } else { + obj.displayText = name; + obj.text = name; + } + obj.filterText = `${oldObj.text}.${name}`; + if (arr[index].fields) { + arr[index].fields.push(obj); + } else { + arr[index].fields = []; + arr[index].fields.push(obj); + } + } else { + const indexPath = getNestedObjPathFromList(arr, oldObj); + if (indexPath && indexPath.length) { + pushNestedObjectInArray(indexPath, obj, arr, modeType); + } + } + } + if (a.fields) { + nestedFields(a.fields, type, level + 1, obj); + } + }); + return arr; + }; + return nestedFields(array, type, 0); +}; + +const pushNestedObjectInArray = (pathArr, obj, targetList, modeType) => { + const rollOverFields = (target) => { + _.map(target, (list) => { + if (pathArr === list.filterText) { + if (modeType === 'sql') { + obj.displayText = `${pathArr}.${obj.displayText}`; + obj.text = `${pathArr}.${obj.text}`; + } else { + obj.displayText = obj.displayText; + obj.text = obj.text; + } + obj.filterText = `${pathArr}.${obj.text}`; + if (list.fields) { + list.fields.push(obj); + } else { + list.fields = []; + list.fields.push(obj); + } + } else if (list.fields) { + rollOverFields(list.fields); + } + }); + }; + rollOverFields(targetList); +}; + +const getNestedObjPathFromList = (list, obj) => { + const str = []; + const recursiveFunc = (arr, level) => { + _.map(arr, (a) => { + if (a.fields) { + str.push(a.filterText); + recursiveFunc(a.fields, level + 1); + } else if (obj.filterText === a.filterText) { + str.push(a.filterText); + } + }); + return _.findLast(str); + }; + return recursiveFunc(list, 0); +}; + +const codeMirrorOptionsTemplate = (el, data) => { + const text = document.createElement('div'); + const fNameSpan = document.createElement('span'); + fNameSpan.setAttribute('class', 'funcText'); + fNameSpan.innerHTML = data.displayText; + + // data.argsType is only for UDF Function + if (data.argsType && data.argsType.length) { + const paramSpan = document.createElement('span'); + paramSpan.innerHTML = `(${data.argsType})`; + fNameSpan.appendChild(paramSpan); + } + text.appendChild(fNameSpan); + el.appendChild(text); + + // data.returnType is for UDF Function || data.type is for Fields + if (data.returnType || data.type) { + const returnTypetxt = document.createElement('div'); + returnTypetxt.setAttribute('class', 'fieldText'); + const content = data.returnType + ? `Return Type: ${data.returnType}` + : `Type: ${data.type}`; + returnTypetxt.innerHTML = content; + el.appendChild(returnTypetxt); + } +}; + export default { sortArray, tableFormat, getSegmentStatus, - findNestedObj + findNestedObj, + generateCodeMirrorOptions }; diff --git a/pinot-controller/src/main/resources/package.json b/pinot-controller/src/main/resources/package.json index 81598e1..a05cb3b 100644 --- a/pinot-controller/src/main/resources/package.json +++ b/pinot-controller/src/main/resources/package.json @@ -58,6 +58,7 @@ "@material-ui/core": "4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.51", + "@types/codemirror": "0.0.97", "@types/react-router-dom": "^5.1.5", "axios": "^0.19.2", "codemirror": "^5.55.0", --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@pinot.apache.org For additional commands, e-mail: commits-h...@pinot.apache.org