From 1eac848963a247bb9caa648581528f3f713cabba Mon Sep 17 00:00:00 2001 From: Geunee Date: Thu, 4 Jul 2024 23:15:08 +0900 Subject: [PATCH] Feature/query editor (#25) * Add Query Editor * Add Query Editor --- cadio-web/src/main/webapp/package-lock.json | 70 +++++++ cadio-web/src/main/webapp/package.json | 2 + .../pages/cluster/components/query-home.js | 86 +++++++- .../cluster/components/query/query-editor.js | 197 +++++++----------- .../cluster/components/query/query-result.js | 93 +++++++++ 5 files changed, 322 insertions(+), 126 deletions(-) create mode 100644 cadio-web/src/main/webapp/src/pages/cluster/components/query/query-result.js diff --git a/cadio-web/src/main/webapp/package-lock.json b/cadio-web/src/main/webapp/package-lock.json index 952acd39..c65add4a 100644 --- a/cadio-web/src/main/webapp/package-lock.json +++ b/cadio-web/src/main/webapp/package-lock.json @@ -15,6 +15,7 @@ "@types/node": "^16.18.29", "@types/react": "^18.2.6", "@types/react-dom": "^18.2.4", + "ace-builds": "^1.35.2", "axios": "^1.2.4", "bootstrap": "^5.3.3", "bootstrap-icons": "^1.10.3", @@ -22,6 +23,7 @@ "moment": "^2.30.1", "node-sass": "^7.0.3", "react": "^18.3.1", + "react-ace": "^12.0.0", "react-bootstrap": "^2.10.3", "react-dom": "^18.3.1", "react-router-dom": "^6.11.0", @@ -4824,6 +4826,11 @@ "node": ">= 0.6" } }, + "node_modules/ace-builds": { + "version": "1.35.2", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.35.2.tgz", + "integrity": "sha512-06d00u4jDZx+ieI0jLlgy/uefx8kcgz7lhI0mCIFEu8NVWirH00U5IEP7tePHy4sjPsRcJUH4VbJZacoit2Hng==" + }, "node_modules/acorn": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", @@ -7245,6 +7252,11 @@ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + }, "node_modules/diff-sequences": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", @@ -12992,6 +13004,16 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -15948,6 +15970,22 @@ "node": ">=0.10.0" } }, + "node_modules/react-ace": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/react-ace/-/react-ace-12.0.0.tgz", + "integrity": "sha512-PstU6CSMfYIJknb4su2Fa0WgLXzq2ufQgR6fjcSWuGT1hGTHkBzuKw+SncV8PuLCdSJBJc1VehPhyeXlWByG/g==", + "dependencies": { + "ace-builds": "^1.32.8", + "diff-match-patch": "^1.0.5", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-app-polyfill": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", @@ -23375,6 +23413,11 @@ "negotiator": "0.6.3" } }, + "ace-builds": { + "version": "1.35.2", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.35.2.tgz", + "integrity": "sha512-06d00u4jDZx+ieI0jLlgy/uefx8kcgz7lhI0mCIFEu8NVWirH00U5IEP7tePHy4sjPsRcJUH4VbJZacoit2Hng==" + }, "acorn": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", @@ -25100,6 +25143,11 @@ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, + "diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + }, "diff-sequences": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", @@ -29257,6 +29305,16 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -31231,6 +31289,18 @@ "loose-envify": "^1.1.0" } }, + "react-ace": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/react-ace/-/react-ace-12.0.0.tgz", + "integrity": "sha512-PstU6CSMfYIJknb4su2Fa0WgLXzq2ufQgR6fjcSWuGT1hGTHkBzuKw+SncV8PuLCdSJBJc1VehPhyeXlWByG/g==", + "requires": { + "ace-builds": "^1.32.8", + "diff-match-patch": "^1.0.5", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "prop-types": "^15.8.1" + } + }, "react-app-polyfill": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", diff --git a/cadio-web/src/main/webapp/package.json b/cadio-web/src/main/webapp/package.json index 8f927e4f..cf4770bb 100644 --- a/cadio-web/src/main/webapp/package.json +++ b/cadio-web/src/main/webapp/package.json @@ -21,6 +21,8 @@ "react-dom": "^18.3.1", "react-router-dom": "^6.11.0", "react-scripts": "5.0.1", + "react-ace": "^12.0.0", + "ace-builds": "^1.35.2", "typescript": "^4.9.5", "web-vitals": "^2.1.4" }, diff --git a/cadio-web/src/main/webapp/src/pages/cluster/components/query-home.js b/cadio-web/src/main/webapp/src/pages/cluster/components/query-home.js index 35fbca7c..8aadbf15 100644 --- a/cadio-web/src/main/webapp/src/pages/cluster/components/query-home.js +++ b/cadio-web/src/main/webapp/src/pages/cluster/components/query-home.js @@ -1,6 +1,81 @@ import QueryEditor from "./query/query-editor"; +import axios from "axios"; +import {useEffect, useState} from "react"; +import {useParams} from "react-router-dom"; +import QueryResult from "./query/query-result"; const QueryHome = () => { + const routeParams = useParams(); + + const [queryParam, setQueryParam] = useState( + { + query: "", + nextCursor: "", + } + ); + + const initQueryResult = { + wasApplied: null, + rows: [], + columnNames: [], + }; + + const [queryResult, setQueryResult] = useState(initQueryResult) + + const queryExecute = (query, cursor, setLoading) => { + if (!query) { + alert("쿼리를 입력해 주세요.") + return; + } + + if (cursor === null) { + setQueryResult({ + wasApplied: null, + rows: [], + columnNames: [], + }) + } + + setLoading(true); + + axios({ + method: "POST", + url: `/api/cassandra/cluster/${routeParams.clusterId}/query`, + data: { + query: query, + pageSize: 2, + timeoutSeconds: 3, + cursor: cursor, + }, + }).then((response) => { + console.log("response query execute ", response.data.result.nextCursor) + setQueryParam({ + query: query, + nextCursor: response.data.result.nextCursor + }) + + setQueryResult({ + wasApplied: response.data.result.wasApplied, + rows: [...queryResult.rows, ...response.data.result.rows], + columnNames: response.data.result.columnNames, + }) + }).catch((error) => { + + }).finally(() => { + setLoading(false); + }) + } + + useEffect(() => { + //show component + setQueryResult(initQueryResult); + + return () => { + //hide component + + }; + }, []); + return ( <> @@ -19,8 +94,15 @@ const QueryHome = () => { - - + + ) } diff --git a/cadio-web/src/main/webapp/src/pages/cluster/components/query/query-editor.js b/cadio-web/src/main/webapp/src/pages/cluster/components/query/query-editor.js index 458a5968..c25dd13d 100644 --- a/cadio-web/src/main/webapp/src/pages/cluster/components/query/query-editor.js +++ b/cadio-web/src/main/webapp/src/pages/cluster/components/query/query-editor.js @@ -1,70 +1,33 @@ -import {useState} from "react"; -import axios from "axios"; -import {useParams} from "react-router-dom"; +import {useRef,useState} from "react"; -const QueryEditor = () => { - const routeParams = useParams(); +import AceEditor from "react-ace"; +import "ace-builds/src-noconflict/ext-language_tools"; +import "ace-builds/src-noconflict/mode-sql"; +import "ace-builds/src-noconflict/theme-sqlserver" - const [queryLoading, setQueryLoading] = useState(false) +const QueryEditor = (props) => { - const [queryParam, setQueryParam] = useState( - { - query: "SELECT * FROM testdb.test_table_1;", - nextCursor: "", - } - ); + const [queryLoading, setQueryLoading] = useState(false) + const [query, setQuery] = useState("SELECT * FROM testdb.test_table_1;") - const [queryResult, setQueryResult] = useState({ - wasApplied: null, - rows: [], - columnNames: [], - }) + const editorRef = useRef(); const queryExecute = (cursor) => { - if (!queryParam.query) { - alert("쿼리를 입력해 주세요.") - return; - } - - if (cursor === null) { - setQueryResult({ - wasApplied: null, - rows: [], - columnNames: [], - }) - } - - setQueryLoading(true); - - axios({ - method: "POST", - url: `/api/cassandra/cluster/${routeParams.clusterId}/query`, - data: { - query: queryParam.query, - pageSize: 2, - timeoutSeconds: 3, - cursor: cursor, - }, - }).then((response) => { - console.log("response : ", response); - - setQueryParam(t => { - return { - ...t, - nextCursor: response.data.result.nextCursor - } - }) - - setQueryResult({ - wasApplied: response.data.result.wasApplied, - rows: [...queryResult.rows, ...response.data.result.rows], - columnNames: response.data.result.columnNames, - }) - }).catch((error) => { - - }).finally(() => { - setQueryLoading(false); - }) + props.queryExecute(query, cursor, setQueryLoading); + } + + function onSelectionChange(value, event) { + const content = editorRef.current.editor.getSelectedText(); + console.log("content : ", content) + // use content + } + function onLoad(newValue) { + console.log("load", newValue); + } + + function onChange(newValue) { + console.log("change", newValue); + setQuery(newValue) } return ( @@ -83,6 +46,20 @@ const QueryEditor = () => { +
+ + +
-
- {/*현재는 단일 쿼리만 실행 가능하도록*/} - - -
- - { - queryResult.wasApplied && <> -

Result

- -

wasApplied : {queryResult.wasApplied}

- -
- - - - { - queryResult.columnNames.map((info, infoIndex) => { - return ( - - ) - }) - } - - - - { - queryResult.rows.map((row, rowIndex) => { - return ( - - { - queryResult.columnNames.map((info, infoIndex) => { - return ( - - ) - }) - } - - ) - }) - } - - -
{info}
{row[info]}
-
- - { - queryParam.nextCursor && -
- -
- } - - } + {/*
*/} + {/* /!*현재는 단일 쿼리만 실행 가능하도록*!/*/} + {/* */} + {/* */} + {/*
*/} + + ) } diff --git a/cadio-web/src/main/webapp/src/pages/cluster/components/query/query-result.js b/cadio-web/src/main/webapp/src/pages/cluster/components/query/query-result.js new file mode 100644 index 00000000..8c651afe --- /dev/null +++ b/cadio-web/src/main/webapp/src/pages/cluster/components/query/query-result.js @@ -0,0 +1,93 @@ +import {useEffect, useState} from "react"; + +const QueryResult = (props) => { + const queryExecute = props.queryExecute; + const result = props.result; + + const query = props.query; + const nextCursor = props.nextCursor; + + const [moreQueryLoading, setMoreQueryLoading] = useState(false); + + useEffect(() => { + //show component + + return () => { + //hide component + + }; + }, []); + + return ( + <> + { + result.wasApplied && <> +

Result

+ + {/*

wasApplied : {result.wasApplied}

*/} + + + {query} + + +
+ + + + { + result.columnNames.map((info, infoIndex) => { + return ( + + ) + }) + } + + + + { + result.rows.length <= 0 ? <> + + : + result.rows.map((row, rowIndex) => { + return ( + + { + result.columnNames.map((info, infoIndex) => { + return ( + + ) + }) + } + + ) + }) + } + + +
{info}
데이터가 없습니다.
{row[info]}
+
+ + { + nextCursor && +
+ +
+ } + + } + + ) +} + +export default QueryResult;