You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@age.apache.org by hb...@apache.org on 2022/11/01 23:53:55 UTC
[age-viewer] branch main updated: Selecting a graph feature and removed graph path input (#46)
This is an automated email from the ASF dual-hosted git repository.
hbshin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/age-viewer.git
The following commit(s) were added to refs/heads/main by this push:
new 8073244 Selecting a graph feature and removed graph path input (#46)
8073244 is described below
commit 807324426cbf1163d4219c126a482846ffad15a4
Author: marodins <67...@users.noreply.github.com>
AuthorDate: Tue Nov 1 16:53:50 2022 -0700
Selecting a graph feature and removed graph path input (#46)
* removed graph input
* sql query to get all graph names in database
* prepare class GraphRepository to handle multiple graphs/graph names
* implemented methods to retreive metadata for multiple graphs of AGE flavor
* refactor metadata retrieval and remove flavor check
* added dropdown and implemented reducer for database slice
* implement graph change method and update state mapping
* select graph feature implemented
* update query
* CodeReview SidebarComponents.jsx
Update to SelectBox
Co-authored-by: Hanbyeol Shin / David Shin / 신한별 <76...@users.noreply.github.com>
---
backend/sql/get_graph_names/AGE.sql | 1 +
backend/src/models/GraphRepository.js | 26 +++++---
backend/src/services/databaseService.js | 39 ++++++++---
.../components/contents/presentations/Editor.jsx | 1 +
.../frame/presentations/ServerConnectFrame.jsx | 7 +-
.../components/sidebar/containers/SidebarHome.js | 27 ++++----
.../sidebar/presentations/SidebarComponents.jsx | 36 +++++++++-
.../sidebar/presentations/SidebarHome.jsx | 23 ++++++-
frontend/src/features/database/DatabaseSlice.js | 8 ++-
frontend/src/features/database/MetadataSlice.js | 76 +++++++++-------------
10 files changed, 160 insertions(+), 84 deletions(-)
diff --git a/backend/sql/get_graph_names/AGE.sql b/backend/sql/get_graph_names/AGE.sql
new file mode 100644
index 0000000..1ce9ff4
--- /dev/null
+++ b/backend/sql/get_graph_names/AGE.sql
@@ -0,0 +1 @@
+SELECT * FROM ag_catalog.ag_graph;
diff --git a/backend/src/models/GraphRepository.js b/backend/src/models/GraphRepository.js
index a5eaa2e..bd75b4c 100644
--- a/backend/src/models/GraphRepository.js
+++ b/backend/src/models/GraphRepository.js
@@ -16,17 +16,17 @@
* specific language governing permissions and limitations
* under the License.
*/
-
import Flavors from '../config/Flavors';
import PgConfig from '../config/Pg'
import pg from 'pg';
import types from 'pg-types';
import {setAGETypes} from '../tools/AGEParser';
+import { getQuery } from '../tools/SQLFlavorManager';
class GraphRepository {
- constructor({host, port, database, graph, user, password, flavor} = {}) {
+ constructor({host, port, database, graph, user, password, flavor, graphs=[]} = {}) {
if (!flavor) {
throw new Error('Flavor is required.');
}
@@ -34,6 +34,7 @@ class GraphRepository {
this._host = host;
this._port = port;
this._database = database;
+ this._graphs = graphs;
this._graph = graph;
this._user = user;
this._password = password;
@@ -44,7 +45,6 @@ class GraphRepository {
host,
port,
database,
- graph,
user,
password,
flavor
@@ -89,6 +89,13 @@ class GraphRepository {
return result;
}
+ async initGraphNames(){
+ const { rows } = await this.execute(getQuery(this.flavor, 'get_graph_names'));
+ this._graphs = rows.map((item)=>item.name);
+ // set current graph to first name
+ this.setCurrentGraph(this._graphs[0]);
+ }
+
/**
* Get connectionInfo
*/
@@ -97,11 +104,9 @@ class GraphRepository {
this._pool = GraphRepository.newConnectionPool(this.getPoolConnectionInfo());
}
const client = await this._pool.connect();
- if (this.flavor === 'AGE') {
- await setAGETypes(client, types);
- } else {
- await client.query(`set graph_path = ${this._graph}`);
- }
+
+ await setAGETypes(client, types);
+
return client;
}
@@ -149,10 +154,15 @@ class GraphRepository {
database: this._database,
user: this._user,
password: this._password,
+ graphs: this._graphs,
graph: this._graph,
flavor: this.flavor,
};
}
+
+ setCurrentGraph(name){
+ this._graph = name;
+ }
}
module.exports = GraphRepository;
diff --git a/backend/src/services/databaseService.js b/backend/src/services/databaseService.js
index 5dbbc7d..48cc43d 100644
--- a/backend/src/services/databaseService.js
+++ b/backend/src/services/databaseService.js
@@ -27,17 +27,40 @@ class DatabaseService {
this._graphRepository = null;
}
- async getMetaData() {
+ async getMetaData(graphName) {
+ await this._graphRepository.initGraphNames();
+ const {graphs} = this._graphRepository.getConnectionInfo();
+ if(graphName){
+ if(graphs.includes(graphName)){
+ return await this.getMetaDataSingle(graphName);
+ }else{
+ throw new Error('graph does not exist');
+ }
+
+ }else{
+ return await this.getMetaDataMultiple(graphs);
+ }
+
+ }
+
+ async getMetaDataMultiple(graphs){
+ const metadata = {};
+ await Promise.all(graphs.map(async(gname)=>{
+ metadata[gname] = await this.getMetaDataSingle(gname);
+ }))
+ return metadata;
+ }
+
+ async getMetaDataSingle(curGraph){
let metadata = {};
-
+ const {database} = this.getConnectionInfo();
try {
- let connectionInfo = this.getConnectionInfo();
- let {nodes, edges} = await this.readMetaData();
+ let {nodes, edges} = await this.readMetaData(curGraph);
metadata.nodes = nodes;
metadata.edges = edges;
metadata.propertyKeys = await this.getPropertyKeys();
- metadata.graph = connectionInfo.graph;
- metadata.database = connectionInfo.database;
+ metadata.graph = curGraph;
+ metadata.database = database;
metadata.role = await this.getRole();
} catch (error) {
throw error;
@@ -72,9 +95,9 @@ class DatabaseService {
return queryResult.rows;
}
- async readMetaData(){
+ async readMetaData(graphName){
let gr = this._graphRepository;
- let queryResult = await gr.execute(util.format(getQuery(gr.flavor, 'meta_data'), this.getConnectionInfo().graph));
+ let queryResult = await gr.execute(util.format(getQuery(gr.flavor, 'meta_data'), graphName));
return this.parseMeta(queryResult[1].rows);
}
/*
diff --git a/frontend/src/components/contents/presentations/Editor.jsx b/frontend/src/components/contents/presentations/Editor.jsx
index 4c66ac1..dafff88 100644
--- a/frontend/src/components/contents/presentations/Editor.jsx
+++ b/frontend/src/components/contents/presentations/Editor.jsx
@@ -55,6 +55,7 @@ const Editor = ({
};
const onClick = () => {
+ console.log('in editor presentation command is ', command);
const refKey = uuid();
if (command.toUpperCase().startsWith(':PLAY')) {
dispatch(() => addFrame(command, 'Contents', refKey));
diff --git a/frontend/src/components/frame/presentations/ServerConnectFrame.jsx b/frontend/src/components/frame/presentations/ServerConnectFrame.jsx
index 457d439..8e5434d 100644
--- a/frontend/src/components/frame/presentations/ServerConnectFrame.jsx
+++ b/frontend/src/components/frame/presentations/ServerConnectFrame.jsx
@@ -75,14 +75,14 @@ const ServerConnectFrame = ({
<Row>
<Col span={6}>
<h3>Connect to Database</h3>
- <p>Database access might require and authenticated connection.</p>
+ <p>Database access might require an authenticated connection.</p>
</Col>
<Col span={18}>
<div className={styles.FrameWrapper}>
<Form
initialValues={FormInitialValue}
layout="vertical"
- onFinish={(values) => connectToDatabase(values)}
+ onFinish={connectToDatabase}
>
<Form.Item name="flavor" label="Database Type" rules={[{ required: true }]}>
<Select
@@ -101,9 +101,6 @@ const ServerConnectFrame = ({
<Form.Item name="database" label="Database Name" rules={[{ required: true }]}>
<Input placeholder="postgres" />
</Form.Item>
- <Form.Item name="graph" label="Graph Path" rules={[{ required: true }]}>
- <Input placeholder="postgres" />
- </Form.Item>
<Form.Item name="user" label="User Name" rules={[{ required: true }]}>
<Input placeholder="postgres" />
</Form.Item>
diff --git a/frontend/src/components/sidebar/containers/SidebarHome.js b/frontend/src/components/sidebar/containers/SidebarHome.js
index 860495b..3bf91bf 100644
--- a/frontend/src/components/sidebar/containers/SidebarHome.js
+++ b/frontend/src/components/sidebar/containers/SidebarHome.js
@@ -21,20 +21,25 @@ import { connect } from 'react-redux';
import SidebarHome from '../presentations/SidebarHome';
import { setCommand } from '../../../features/editor/EditorSlice';
import { addFrame, trimFrame } from '../../../features/frame/FrameSlice';
-import { getMetaData } from '../../../features/database/MetadataSlice';
+import { changeGraph } from '../../../features/database/DatabaseSlice';
+import { getMetaData, changeCurrentGraph } from '../../../features/database/MetadataSlice';
-const mapStateToProps = (state) => ({
- edges: state.metadata.edges,
- nodes: state.metadata.nodes,
- propertyKeys: state.metadata.propertyKeys,
- dbname: state.metadata.dbname,
- graph: state.metadata.graph,
- role: state.metadata.role,
- command: state.editor.command,
-});
+const mapStateToProps = (state) => {
+ const currentGraphData = state.metadata.graphs[state.metadata.currentGraph] || '';
+ return {
+ graphs: Object.entries(state.metadata.graphs).map(([k, v]) => [k, v.id]),
+ edges: currentGraphData.edges,
+ nodes: currentGraphData.nodes,
+ propertyKeys: currentGraphData.propertyKeys,
+ dbname: state.metadata.dbname,
+ status: state.metadata.status,
+ role: currentGraphData.role,
+ command: state.editor.command,
+ };
+};
const mapDispatchToProps = {
- setCommand, addFrame, trimFrame, getMetaData,
+ setCommand, addFrame, trimFrame, getMetaData, changeCurrentGraph, changeGraph,
};
export default connect(mapStateToProps, mapDispatchToProps)(SidebarHome);
diff --git a/frontend/src/components/sidebar/presentations/SidebarComponents.jsx b/frontend/src/components/sidebar/presentations/SidebarComponents.jsx
index fcc34a0..644d7fd 100644
--- a/frontend/src/components/sidebar/presentations/SidebarComponents.jsx
+++ b/frontend/src/components/sidebar/presentations/SidebarComponents.jsx
@@ -18,6 +18,7 @@
*/
import React from 'react';
+import { Select } from 'antd';
import PropTypes from 'prop-types';
const StyleTextRight = {
@@ -75,4 +76,37 @@ SubLabelLeftWithLink.propTypes = {
label: PropTypes.string.isRequired,
};
-export { SubLabelRight, SubLabelLeft, SubLabelLeftWithLink };
+const GraphSelectDropdown = ({ graphs, changeCurrentGraph, changeGraphDB }) => {
+ const selectStyle = {
+ marginTop: '1rem',
+ };
+ const handleGraphClick = (e) => {
+ const graphName = graphs.find((graph) => graph[1] === e)[0];
+ changeCurrentGraph({ id: e });
+ changeGraphDB({ graphName });
+ };
+
+ const options = (
+ graphs.map(([gname, graphId]) => (<option value={graphId}>{gname}</option>))
+ );
+ return (
+ <div>
+ <Select onChange={handleGraphClick} placeholder="Select Graph" style={selectStyle}>
+ {options}
+ </Select>
+ <b>
+ Current Graph
+ </b>
+ </div>
+ );
+};
+
+GraphSelectDropdown.propTypes = {
+ graphs: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)).isRequired,
+ changeCurrentGraph: PropTypes.func.isRequired,
+ changeGraphDB: PropTypes.func.isRequired,
+};
+
+export {
+ SubLabelRight, SubLabelLeft, SubLabelLeftWithLink, GraphSelectDropdown,
+};
diff --git a/frontend/src/components/sidebar/presentations/SidebarHome.jsx b/frontend/src/components/sidebar/presentations/SidebarHome.jsx
index 80b35bc..5acd6bf 100644
--- a/frontend/src/components/sidebar/presentations/SidebarHome.jsx
+++ b/frontend/src/components/sidebar/presentations/SidebarHome.jsx
@@ -25,7 +25,9 @@ import uuid from 'react-uuid';
import { connect, useDispatch } from 'react-redux';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faRedo, faTimes } from '@fortawesome/free-solid-svg-icons';
-import { VerticalLine, SubLabelLeft, SubLabelRight } from './SidebarComponents';
+import {
+ VerticalLine, SubLabelLeft, SubLabelRight, GraphSelectDropdown,
+} from './SidebarComponents';
const genLabelQuery = (eleType, labelName, database) => {
function age() {
@@ -292,12 +294,15 @@ DBMSText.propTypes = {
const SidebarHome = ({
edges,
nodes,
+ graphs,
propertyKeys,
setCommand,
command,
trimFrame,
addFrame,
getMetaData,
+ changeCurrentGraph,
+ changeGraph,
}) => {
const dispatch = useDispatch();
const { confirm } = Modal;
@@ -378,6 +383,19 @@ const SidebarHome = ({
</button>
<br />
<b>Close Session</b>
+ <div style={{
+ border: '1px solid #C4C4C4',
+ opacity: '1',
+ width: '80%',
+ height: '0',
+ margin: '3px auto',
+ }}
+ />
+ <GraphSelectDropdown
+ graphs={graphs}
+ changeCurrentGraph={changeCurrentGraph}
+ changeGraphDB={changeGraph}
+ />
</div>
</div>
</div>
@@ -402,6 +420,9 @@ SidebarHome.propTypes = {
trimFrame: PropTypes.func.isRequired,
addFrame: PropTypes.func.isRequired,
getMetaData: PropTypes.func.isRequired,
+ changeCurrentGraph: PropTypes.func.isRequired,
+ graphs: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)).isRequired,
+ changeGraph: PropTypes.func.isRequired,
};
export default SidebarHome;
diff --git a/frontend/src/features/database/DatabaseSlice.js b/frontend/src/features/database/DatabaseSlice.js
index 04c6efe..7955100 100644
--- a/frontend/src/features/database/DatabaseSlice.js
+++ b/frontend/src/features/database/DatabaseSlice.js
@@ -78,6 +78,10 @@ const DatabaseSlice = createSlice({
status: 'init',
},
reducers: {
+ changeGraph: (state, action) => ({
+ ...state,
+ graph: action.payload.graphName,
+ }),
},
extraReducers: {
[connectToDatabase.fulfilled]: (state, action) => ({
@@ -132,8 +136,6 @@ const DatabaseSlice = createSlice({
}),
},
});
+export const { changeGraph } = DatabaseSlice.actions;
-/*
-export const { } = DatabaseSlice.actions
-*/
export default DatabaseSlice.reducer;
diff --git a/frontend/src/features/database/MetadataSlice.js b/frontend/src/features/database/MetadataSlice.js
index 0e37d0a..9158c24 100644
--- a/frontend/src/features/database/MetadataSlice.js
+++ b/frontend/src/features/database/MetadataSlice.js
@@ -18,6 +18,7 @@
*/
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
+import uuid from 'react-uuid';
export const getMetaData = createAsyncThunk(
'database/getMetaData',
@@ -25,20 +26,21 @@ export const getMetaData = createAsyncThunk(
try {
const response = await fetch('/api/v1/db/meta');
if (response.ok) {
- let allCountEdge = 0;
- let allCountNode = 0;
const ret = await response.json();
+ Object.keys(ret).forEach((gname) => {
+ let allCountEdge = 0;
+ let allCountNode = 0;
+ ret[gname].nodes.forEach((item) => {
+ allCountNode += item.cnt;
+ });
- ret.nodes.forEach((item) => {
- allCountNode += item.cnt;
+ ret[gname].edges.forEach((item) => {
+ allCountEdge += item.cnt;
+ });
+ ret[gname].nodes.unshift({ label: '*', cnt: allCountNode });
+ ret[gname].edges.unshift({ label: '*', cnt: allCountEdge });
+ ret[gname].id = uuid();
});
-
- ret.edges.forEach((item) => {
- allCountEdge += item.cnt;
- });
-
- ret.nodes.unshift({ label: '*', cnt: allCountNode });
- ret.edges.unshift({ label: '*', cnt: allCountEdge });
return ret;
}
throw response;
@@ -76,55 +78,35 @@ export const getMetaChartData = createAsyncThunk(
const MetadataSlice = createSlice({
name: 'metadata',
initialState: {
- edges: [],
- nodes: [],
- propertyKeys: [],
+ graphs: {},
status: 'init',
dbname: '',
- graph: '',
- role: {
- user_name: '',
- role_name: '',
- },
- rows: [],
+ currentGraph: '',
},
reducers: {
- resetMetaData: () => ({
- edges: [],
- nodes: [],
- propertyKeys: [],
- status: 'init',
- dbname: '',
- graph: '',
- role: {
- user_name: '',
- role_name: '',
- },
-
+ resetMetaData: (state) => (state.initialState),
+ changeCurrentGraph: (state, action) => ({
+ ...state,
+ currentGraph: Object.entries(state.graphs)
+ .find(([, data]) => data.id === action.payload.id)[0],
}),
},
extraReducers: {
[getMetaData.fulfilled]: (state, action) => {
if (action.payload) {
- return Object.assign(state, {
- edges: action.payload.edges,
- nodes: action.payload.nodes,
- propertyKeys: action.payload.propertyKeys,
+ return {
+ ...state,
+ graphs: action.payload,
status: 'connected',
dbname: action.payload.database,
- graph: action.payload.graph,
- role: action.payload.role,
- });
+ currentGraph: state.currentGraph !== '' ? state.currentGraph : Object.keys(action.payload)[0],
+ };
}
- return Object.assign(state, {
- edges: [],
- nodes: [],
- propertyKeys: [],
+ return {
+ ...state,
status: 'disconnected',
dbname: action.payload.database,
- graph: action.payload.graph,
- role: action.payload.role,
- });
+ };
},
[getMetaChartData.fulfilled]: (state, action) => {
if (action.payload) {
@@ -135,6 +117,6 @@ const MetadataSlice = createSlice({
},
});
-export const { resetMetaData } = MetadataSlice.actions;
+export const { resetMetaData, changeCurrentGraph } = MetadataSlice.actions;
export default MetadataSlice.reducer;