You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by ki...@apache.org on 2020/08/30 08:02:17 UTC
[incubator-pinot] 01/02: adding autocomplete in sql editor
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...@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-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org