From 31c761f6892993ac1f8b2ba6c84d8f883db437f4 Mon Sep 17 00:00:00 2001 From: Nut He <18328704+hetao92@users.noreply.github.com> Date: Tue, 8 Mar 2022 15:20:04 +0800 Subject: [PATCH] feat: support open in explorer --- .github/workflows/nightly.yml | 4 +- app/config/locale/en-US.json | 4 +- app/pages/Console/ExportModal.tsx | 148 +++++++++++++++++++++++++ app/pages/Console/OutputBox/index.less | 1 + app/pages/Console/OutputBox/index.tsx | 40 ++++++- app/pages/Console/index.less | 9 ++ app/pages/Console/index.tsx | 36 +++++- scripts/deb/start.sh | 1 + 8 files changed, 234 insertions(+), 9 deletions(-) create mode 100644 app/pages/Console/ExportModal.tsx diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index a2958fda..6967529c 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -2,10 +2,10 @@ name: Studio nightly Package on: push: branches: - - master + - v3.3.0-dev jobs: call-workflow: - uses: vesoft-inc/nebula-studio/.github/workflows/build.yml@master + uses: vesoft-inc/nebula-studio/.github/workflows/build.yml@v3.3.0-dev secrets: oss_endpoint: ${{ secrets.OSS_ENDPOINT }} oss_id: ${{ secrets.OSS_ID }} diff --git a/app/config/locale/en-US.json b/app/config/locale/en-US.json index 2a2e10d3..f77ebfe4 100644 --- a/app/config/locale/en-US.json +++ b/app/config/locale/en-US.json @@ -24,7 +24,7 @@ "import": "Import", "ask": "Are you sure to proceed?", "output": "Export CSV File", - "openInExplore": "Open In Explore", + "openInExplore": "Open In Explorer", "schema": "Schema", "create": "Create", "serialNumber": "No.", @@ -137,7 +137,7 @@ "execTime": "Execution Time", "exportVertex": "Please choose the column representing vertex IDs from the table", "exportEdge": "Please choose the columns representing source vertex ID, destination vertex ID, and rank of an edge", - "showSubgraphs": "View Subgraphs", + "showSubgraphs": "Open in Explorer", "deleteHistory": "Clear History", "cypherParam": "Cypher Parameter", "favorites": "Favorites", diff --git a/app/pages/Console/ExportModal.tsx b/app/pages/Console/ExportModal.tsx new file mode 100644 index 00000000..115678d9 --- /dev/null +++ b/app/pages/Console/ExportModal.tsx @@ -0,0 +1,148 @@ +import { Button, Form, Input, Modal, Radio, Select } from 'antd'; +import React from 'react'; +import intl from 'react-intl-universal'; +import { observer } from 'mobx-react-lite'; +import { useStore } from '@app/stores'; + +import './index.less'; +const Option = Select.Option; + +const layout = { + labelCol: { span: 10 }, + wrapperCol: { span: 8 }, +}; + +interface IProps { + data: any; + visible: boolean; + onClose: () => void; + onExplorer: (params: { + space: string; + vertexes: any[], + edges: any[] + }) => void +} +const ExportModal = (props: IProps) => { + const { data, visible, onClose, onExplorer } = props; + const { schema: { currentSpace } } = useStore(); + const { headers, tables } = data; + const handleExport = (values) => { + const { type, vertexId, srcId, dstId, edgeType, rank } = values; + const vertexes = + type === 'vertex' + ? tables + .map(vertex => { + if (vertex.type === 'vertex') { + return vertex.vid; + } else { + return vertex[vertexId].toString(); + } + }) + .filter(vertexId => vertexId !== '') + : tables + .map(edge => [edge[srcId], edge[dstId]]) + .flat() + .filter(id => id !== ''); + const edges = + type === 'edge' + ? tables + .map(edge => ({ + srcId: edge[srcId], + dstId: edge[dstId], + rank: rank !== '' && rank !== undefined ? edge[rank] : 0, + edgeType, + })) + .filter(edge => edge.srcId !== '' && edge.dstId !== '') + : []; + onExplorer({ + space: currentSpace, + vertexes, + edges + }); + }; + if(!data) { + return; + } + return ( + +
+ + + + {intl.get('import.vertexText')} + + + {intl.get('common.edge')} + + + + + {({ getFieldValue }) => { + const type = getFieldValue('type'); + return type === 'vertex' ? <> +

{intl.get('console.exportVertex')}

+ + + + : <> +

{intl.get('console.exportEdge')}

+ + + + + + + + + + + + + ; + }} +
+ + + +
+
+ ); +}; +export default observer(ExportModal); diff --git a/app/pages/Console/OutputBox/index.less b/app/pages/Console/OutputBox/index.less index c7864b31..0cabd898 100644 --- a/app/pages/Console/OutputBox/index.less +++ b/app/pages/Console/OutputBox/index.less @@ -113,6 +113,7 @@ .output-footer { display: flex; align-items: center; + justify-content: space-between; padding: 15px 18px; width: 100%; height: 65px; diff --git a/app/pages/Console/OutputBox/index.tsx b/app/pages/Console/OutputBox/index.tsx index d405c1ea..ee48d2b9 100644 --- a/app/pages/Console/OutputBox/index.tsx +++ b/app/pages/Console/OutputBox/index.tsx @@ -1,4 +1,4 @@ -import { Table, Tabs, Tooltip } from 'antd'; +import { Button, Table, Tabs, Tooltip } from 'antd'; import { BigNumber } from 'bignumber.js'; import React, { useCallback, useEffect, useState } from 'react'; import intl from 'react-intl-universal'; @@ -8,6 +8,7 @@ import { trackEvent } from '@app/utils/stat'; import { v4 as uuidv4 } from 'uuid'; import Icon from '@app/components/Icon'; import Graphviz from './Graphviz'; +import { parseSubGraph } from '@app/utils/parseData'; import './index.less'; import classNames from 'classnames'; @@ -17,14 +18,21 @@ interface IProps { gql: string; result: any; onHistoryItem: (gql: string) => void; + onExplorer?: (params: { + space: string; + vertexes: any[], + edges: any[] + }) => void; + onResultConfig?: (data: any) => void } const OutputBox = (props: IProps) => { - const { gql, result: { code, data, message }, onHistoryItem, index } = props; - const { global, console } = useStore(); + const { gql, result: { code, data, message }, onHistoryItem, index, onExplorer, onResultConfig } = props; + const { global, console, schema } = useStore(); const [visible, setVisible] = useState(true); const { username, host } = global; const { results, update, favorites, updateFavorites } = console; + const { spaceVidType, currentSpace } = schema; const [columns, setColumns] = useState([]); const [dataSource, setDataSource] = useState([]); const [isFavorited, setIsFavorited] = useState(false); @@ -165,6 +173,31 @@ const OutputBox = (props: IProps) => { link.click(); document.body.removeChild(link); }; + + const handleExplore = () => { + if ( + data.tables.filter( + item => + item._verticesParsedList || + item._edgesParsedList || + item._pathsParsedList, + ).length > 0 + ) { + parseToGraph(); + } else { + onResultConfig!(data); + } + }; + + const parseToGraph = () => { + const { vertexes, edges } = parseSubGraph(data.tables, spaceVidType); + onExplorer!({ + space: currentSpace, + vertexes, + edges + }); + }; + return

onHistoryItem(gql)}> @@ -266,6 +299,7 @@ const OutputBox = (props: IProps) => { {`${intl.get('console.execTime')} ${data.timeCost / 1000000} (s)`} + {onExplorer && }

)} } diff --git a/app/pages/Console/index.less b/app/pages/Console/index.less index dea7475d..e3928e52 100644 --- a/app/pages/Console/index.less +++ b/app/pages/Console/index.less @@ -145,6 +145,15 @@ } } +.export-node-modal { + .select-type { + display: inline-flex; + } + .ant-modal-body { + text-align: center; + } +} + .historyList, .favoriteList { height: 80%; overflow: hidden; diff --git a/app/pages/Console/index.tsx b/app/pages/Console/index.tsx index b250a30d..6b351bab 100644 --- a/app/pages/Console/index.tsx +++ b/app/pages/Console/index.tsx @@ -2,7 +2,7 @@ import { Button, Select, Tooltip, message } from 'antd'; import React, { useEffect, useRef, useState } from 'react'; import intl from 'react-intl-universal'; import { observer } from 'mobx-react-lite'; -import { trackPageView } from '@app/utils/stat'; +import { trackPageView, trackEvent } from '@app/utils/stat'; import { useStore } from '@app/stores'; import Instruction from '@app/components/Instruction'; import Icon from '@app/components/Icon'; @@ -12,6 +12,7 @@ import { maxLineNum } from '@app/config/nebulaQL'; import HistoryBtn from './HistoryBtn'; import FavoriteBtn from './FavoriteBtn'; import CypherParameterBox from './CypherParameterBox'; +import ExportModal from './ExportModal'; import './index.less'; const Option = Select.Option; @@ -26,12 +27,22 @@ const getHistory = () => { return []; }; -const Console = () => { +interface IProps { + onExplorer?: (params: { + space: string; + vertexes: any[], + edges: any[] + }) => void +} +const Console = (props: IProps) => { const { schema, console, global } = useStore(); + const { onExplorer } = props; const { spaces, getSpaces, switchSpace, currentSpace } = schema; const { runGQL, currentGQL, results, runGQLLoading, getParams, update, paramsMap } = console; const { username, host } = global; const [isUpDown, setUpDown] = useState(false); + const [modalVisible, setModalVisible] = useState(false); + const [modalData, setModalData] = useState(null); const editor = useRef(null); useEffect(() => { trackPageView('/console'); @@ -93,6 +104,20 @@ const Console = () => { const addParam = (param: string) => { update({ currentGQL: currentGQL + ` $${param}` }); }; + + const handleResultConfig = (data: any) => { + setModalData(data); + setModalVisible(true); + }; + + const handleExplorer = (data) => { + if(!onExplorer) { + return; + } + onExplorer!(data); + !modalVisible && setModalVisible(false); + trackEvent('navigation', 'view_explore', 'from_console_btn'); + }; return (
@@ -146,7 +171,9 @@ const Console = () => { index={index} result={item} gql={item.gql} + onExplorer={onExplorer ? handleExplorer : undefined} onHistoryItem={gql => updateGql(gql)} + onResultConfig={handleResultConfig} /> )) : { />}
+ {modalVisible && setModalVisible(false)} + onExplorer={handleExplorer} />}
); }; diff --git a/scripts/deb/start.sh b/scripts/deb/start.sh index 0a9052d6..8c3f598f 100644 --- a/scripts/deb/start.sh +++ b/scripts/deb/start.sh @@ -1,4 +1,5 @@ #!/bin/bash +mkdir -p /usr/lib/systemd/system cd /usr/local/nebula-graph-studio/ cp ./lib/nebula-graph-studio.service /usr/lib/systemd/system/ sudo systemctl daemon-reload && sudo systemctl enable nebula-graph-studio.service && sudo systemctl start nebula-graph-studio.service \ No newline at end of file