From e032b23b4c43c49d250ccd8e4504bb4f2f4019e6 Mon Sep 17 00:00:00 2001 From: Jingcheng Yang Date: Fri, 16 Aug 2024 00:43:03 -0400 Subject: [PATCH] Improve the knowledge table. --- Makefile | 4 +- studio/src/components/Footer/index.tsx | 6 +- studio/src/components/Header/index.tsx | 16 +- studio/src/pages/Home/index.tsx | 2 +- studio/src/pages/KnowledgeTable/index.less | 106 +++- studio/src/pages/KnowledgeTable/index.tsx | 576 ++++++++++++--------- 6 files changed, 428 insertions(+), 282 deletions(-) diff --git a/Makefile b/Makefile index 8b591c5..8f483ef 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ build-biomedgps-studio: @cp studio/custom/route/rapex.ts frontend/config/routes.ts # @cd studio && yarn && yarn openapi || true # @cd frontend && yarn - @cd frontend && yarn build:biomedgps-embed && cd .. + @cd frontend && UMI_APP_VERSION=`git describe --tags --always` yarn build:biomedgps-embed && cd .. build-rapex-studio: @printf "Building studio based on openapi...\n" @@ -62,7 +62,7 @@ build-rapex-studio: @cp studio/custom/route/rapex.ts frontend/config/routes.ts # @cd studio && yarn && yarn openapi || true # @cd frontend && yarn - @cd frontend && yarn build:rapex-embed && cd .. + @cd frontend && UMI_APP_VERSION=`git describe --tags --always` yarn build:rapex-embed && cd .. build-biomedgps: @cargo build --release diff --git a/studio/src/components/Footer/index.tsx b/studio/src/components/Footer/index.tsx index f706530..2d15b3d 100644 --- a/studio/src/components/Footer/index.tsx +++ b/studio/src/components/Footer/index.tsx @@ -1,14 +1,16 @@ import React, { useEffect, useState } from 'react'; import CookieConsent, { Cookies } from 'react-cookie-consent'; -import { GithubOutlined } from '@ant-design/icons'; +import { GithubOutlined, FieldTimeOutlined } from '@ant-design/icons'; import { DefaultFooter } from '@ant-design/pro-components'; import { Row } from 'antd'; +import type { MenuProps } from 'antd'; import './index.less'; const Footer: React.FC = () => { const currentYear = new Date().getFullYear(); const [cookieName, setCookieName] = useState('biomedgps-cookie-consent-form'); const [cookieEnabled, setCookieEnabled] = useState(undefined); + const version = process.env.UMI_APP_VERSION || '0.1.0'; useEffect(() => { const v = Cookies.get(cookieName); @@ -40,7 +42,7 @@ const Footer: React.FC = () => { return ( = (props) => { // key: 'user', // icon: , // }, - { - label: 'v20240406', - key: 'version', - icon: - }, + // { + // label: 'v20240406', + // key: 'version', + // icon: + // }, ] const items: MenuProps['items'] = [ @@ -155,6 +155,10 @@ const GlobalHeaderRight: React.FC = (props) => { return ( + + + + { isHeaderHidden() ? null : ( @@ -171,7 +175,7 @@ const GlobalHeaderRight: React.FC = (props) => { !isHeaderHidden() && ( isAuthEnabled() && !isAuthenticated ? ( ) : ( isAuthEnabled() ? diff --git a/studio/src/pages/Home/index.tsx b/studio/src/pages/Home/index.tsx index 1098869..fd14c76 100644 --- a/studio/src/pages/Home/index.tsx +++ b/studio/src/pages/Home/index.tsx @@ -233,7 +233,7 @@ const HomePage: React.FC = () => { - logo + {/* logo */}

Enter a gene/protein, disease, drug or symptom name to find and explain related known knowledges in our platform.
diff --git a/studio/src/pages/KnowledgeTable/index.less b/studio/src/pages/KnowledgeTable/index.less index ce71e9b..e30f933 100644 --- a/studio/src/pages/KnowledgeTable/index.less +++ b/studio/src/pages/KnowledgeTable/index.less @@ -1,41 +1,56 @@ .knowledge-table-wrapper { display: flex; width: 100%; - height: calc(100vh - 54px); - overflow: scroll; - padding: 0 15px; + height: 100%; + overflow: hidden; + flex-direction: column; + padding: 0; background-color: #fff; - .ant-collapse { - width: 100%; - - .ant-collapse-item:nth-child(1), - .ant-collapse-item:nth-child(2) { - .ant-collapse-header { - border-bottom: 1px solid #f0f0f0; - } - } + .menu-panel { + width: fit-content; + height: 100%; + display: flex; + flex-direction: column; + border-right: 1px solid #e8e8e8; - .ant-collapse-item:nth-child(2) { - .ant-collapse-header { - border-top: 1px solid #f0f0f0; - } + .ant-menu { + height: calc(100% - 40px); + border: unset; } - .ant-collapse-header-text { - font-size: 1.2em; + .ant-btn { + width: 80%; + margin-bottom: 8px !important; + margin: 0 auto; } + } - .ant-collapse-header, - .ant-collapse-content-box { - border-radius: 0 !important; - padding: 20px 0; - } + .plugins4kg, + .ant-empty { + width: 100%; + max-width: 1500px; + padding: 20px 10px; } } +.empty-knowledge-table-container { + width: 100%; +} + .knowledge-table-container { + width: calc(100% - 150px); +} + +.collapsed-knowledge-table-container { + width: calc(100% - 80px); +} + +.knowledge-table-container, +.collapsed-knowledge-table-container, +.empty-knowledge-table-container { display: flex; + justify-content: center; height: 100%; position: relative; background-color: #fff; @@ -62,7 +77,7 @@ // right: 10px; // position: absolute; z-index: 100; - top: -70px; + top: 10px; right: 0px; position: absolute; @@ -75,11 +90,48 @@ .ant-spin-container { width: 100%; height: 100%; + } + + .model-parameter { + background: #fff; + margin: 0 !important; + border-right: 1px solid #e8e8e8; + + .model-parameter-header { + background: #fff; + width: 100%; + display: flex; + padding: 0 20px; + line-height: unset; + flex-direction: column; + justify-content: center; + + p, + h3 { + margin: 0; + } + + p { + color: #999; + // Clip text to 1 line + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + .model-parameter-body { + height: calc(100% - 46px); + padding: 20px !important; + } - .ant-pagination { + .model-parameter-button { position: absolute; - top: -75px; - left: 170px; + bottom: 0; + left: 0; + width: 100%; + height: 46px; + border-radius: 0; } } } diff --git a/studio/src/pages/KnowledgeTable/index.tsx b/studio/src/pages/KnowledgeTable/index.tsx index d8206ae..5a17a23 100644 --- a/studio/src/pages/KnowledgeTable/index.tsx +++ b/studio/src/pages/KnowledgeTable/index.tsx @@ -1,7 +1,8 @@ import React, { useEffect, useState } from 'react'; import { history } from 'umi'; -import { Table, Row, Tag, Space, message, Popover, Button, Empty, Tooltip, Drawer, Spin, Select, Collapse } from 'antd'; -import { ArrowsAltOutlined, DownloadOutlined, ExpandAltOutlined, QuestionCircleOutlined, ShrinkOutlined } from '@ant-design/icons'; +import { Table, Row, Col, Form, Menu, Tabs, Tag, Space, message, Popover, Button, Empty, Tooltip, Drawer, Spin, Select } from 'antd'; +import { ArrowsAltOutlined, DownloadOutlined, ExpandAltOutlined, InfoCircleFilled, LinkOutlined, QuestionCircleOutlined, ShrinkOutlined, MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; +import { Header } from 'antd/lib/layout/layout'; import type { ColumnsType } from 'antd/es/table'; import { useLocation } from "react-router-dom"; import { fetchOneStepLinkedNodes, fetchRelationCounts, fetchRelationMetadata } from '@/services/swagger/KnowledgeGraph'; @@ -135,6 +136,29 @@ const KnowledgeTable: React.FC = (props) => { const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(30); const [refreshKey, setRefreshKey] = useState(0); + const [collapsed, setCollapsed] = useState(false); + + const [menuKey, setMenuKey] = useState('summary'); + const [form] = Form.useForm(); + + const menuItems = [ + { + key: 'summary', + label: 'Summary', + icon: , + onClick: () => { + setMenuKey('summary'); + }, + }, + { + key: 'knowledge', + label: 'Knowledge', + icon: , + onClick: () => { + setMenuKey('knowledge'); + }, + }, + ]; useEffect(() => { if (!isAuthenticated()) { @@ -242,42 +266,6 @@ const KnowledgeTable: React.FC = (props) => { // EdgeData const columns: ColumnsType = [ - { - title: 'Relation Type', - key: 'relation_type', - align: 'left', - dataIndex: 'relation_type', - fixed: 'left', - width: 350, - filters: sortBy(uniqBy(tableData.map((item) => { - return { - text: item.relation_type, - value: item.relation_type, - }; - }), 'value'), 'value'), - filterMode: 'menu', - filterSearch: true, - filterMultiple: true, - sorter: (a, b) => a.relation_type.localeCompare(b.relation_type), - onFilter: (value, record) => record.relation_type.indexOf(value) === 0, - render(text, record) { - return ( - - {text} -
- {relationTypeDescs[text] || 'Unknown'} -
- ); - } - }, - { - title: 'Resource', - dataIndex: 'resource', - key: 'resource', - align: 'center', - width: 100, - // fixed: 'left', - }, // { // title: 'PMID', // dataIndex: 'pmids', @@ -306,6 +294,7 @@ const KnowledgeTable: React.FC = (props) => { title: 'Source Name', dataIndex: 'source_name', key: 'source_name', + fixed: 'left', align: 'center', filters: sortBy(uniqBy(tableData.map((item) => { return { @@ -314,7 +303,7 @@ const KnowledgeTable: React.FC = (props) => { }; }), 'value'), 'value'), filterMode: 'menu', - // width: 200, + width: 300, filterSearch: true, sorter: (a, b) => a.source_name.localeCompare(b.source_name), onFilter: (value, record) => record.source_name.indexOf(value) === 0, @@ -349,10 +338,10 @@ const KnowledgeTable: React.FC = (props) => { {
} {(record.source_id.startsWith('ENTREZ:') || record.source_id.startsWith('DrugBank:')) ? { setActivatedNode(record.source_node) }}> - {record.source_id} + {record.source_type} | {record.source_id} : - {record.source_id} + {record.source_type} | {record.source_id} } ; @@ -376,32 +365,32 @@ const KnowledgeTable: React.FC = (props) => { // ); // } // }, - { - title: 'Source Type', - dataIndex: 'source_type', - width: 200, - align: 'center', - key: 'source_type', - filters: sortBy(uniqBy(tableData.map((item) => { - return { - text: item.source_type, - value: item.source_type, - }; - }), 'value'), 'value'), - filterMode: 'menu', - filterSearch: true, - sorter: (a, b) => a.source_type.localeCompare(b.source_type), - onFilter: (value, record) => record.source_type.indexOf(value) === 0, - render: (text) => { - return {text}; - } - }, + // { + // title: 'Source Type', + // dataIndex: 'source_type', + // width: 200, + // align: 'center', + // key: 'source_type', + // filters: sortBy(uniqBy(tableData.map((item) => { + // return { + // text: item.source_type, + // value: item.source_type, + // }; + // }), 'value'), 'value'), + // filterMode: 'menu', + // filterSearch: true, + // sorter: (a, b) => a.source_type.localeCompare(b.source_type), + // onFilter: (value, record) => record.source_type.indexOf(value) === 0, + // render: (text) => { + // return {text}; + // } + // }, { title: 'Target Name', dataIndex: 'target_name', align: 'center', key: 'target_name', - // width: 200, + width: 300, filters: sortBy(uniqBy(tableData.map((item) => { return { text: item.target_name, @@ -443,10 +432,10 @@ const KnowledgeTable: React.FC = (props) => { {
} {(record.target_id.startsWith('ENTREZ:') || record.target_id.startsWith('DrugBank:')) ? { setActivatedNode(record.target_node) }}> - {record.target_id} + {record.target_type} | {record.target_id} : - {record.target_id} + {record.target_type} | {record.target_id} } ; @@ -469,36 +458,71 @@ const KnowledgeTable: React.FC = (props) => { // ); // } // }, + // { + // title: 'Target Type', + // dataIndex: 'target_type', + // align: 'center', + // key: 'target_type', + // width: 200, + // filters: sortBy(uniqBy(tableData.map((item) => { + // return { + // text: item.target_type, + // value: item.target_type, + // }; + // }), 'value'), 'value'), + // filterMode: 'menu', + // filterSearch: true, + // sorter: (a, b) => a.target_type.localeCompare(b.target_type), + // onFilter: (value, record) => record.target_type.indexOf(value) === 0, + // render: (text) => { + // return {text}; + // } + // }, { - title: 'Target Type', - dataIndex: 'target_type', + title: 'Score', + dataIndex: 'score', align: 'center', - key: 'target_type', - width: 200, + key: 'score', + width: 100, + render: (text) => { + return {text.toFixed(3)}; + }, + sorter: (a, b) => a.score - b.score, + }, + { + title: 'Relation Type', + key: 'relation_type', + align: 'left', + dataIndex: 'relation_type', + // width: 350, filters: sortBy(uniqBy(tableData.map((item) => { return { - text: item.target_type, - value: item.target_type, + text: item.relation_type, + value: item.relation_type, }; }), 'value'), 'value'), filterMode: 'menu', filterSearch: true, - sorter: (a, b) => a.target_type.localeCompare(b.target_type), - onFilter: (value, record) => record.target_type.indexOf(value) === 0, - render: (text) => { - return {text}; + filterMultiple: true, + sorter: (a, b) => a.relation_type.localeCompare(b.relation_type), + onFilter: (value, record) => record.relation_type.indexOf(value) === 0, + render(text, record) { + return ( + + {text} +
+ {relationTypeDescs[text] || 'Unknown'} +
+ ); } }, { - title: 'Score', - dataIndex: 'score', + title: 'Resource', + dataIndex: 'resource', + key: 'resource', align: 'center', - key: 'score', width: 100, - render: (text) => { - return {text.toFixed(3)}; - }, - sorter: (a, b) => a.score - b.score, + // fixed: 'left', }, { title: 'Action', @@ -666,65 +690,53 @@ const KnowledgeTable: React.FC = (props) => { ; } - return (isAuthenticated()) && (total == 0 ? ( - - - -

- { - loading ? 'Loading Knowledges ' : 'No Knowledges ' - } - - for Your Query + const handleSubmit = (values: any) => { + } - { - nodeIds ? `( ${nodeIds.join(', ')} )` : '' - } -

- - - } - style={{ - height: '100%', - flexDirection: 'column', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - }} /> -
-
- ) : ( - - - {currentNodes.length > 0 && currentNodes.map((node, index) => { - return node?.nlabel == 'Gene' ? - - : null; - })} - - -
- - Selected {selectedRowKeys.length} items - - - - + const whichPanel = () => { + if (menuKey === 'summary') { + if (currentNodes.length == 0) { + return + } else if (currentNodes.length == 1) { + return currentNodes[0]?.nlabel == 'Gene' ? : 'Current Node is not a Gene, More Coming Soon...'; + } else { + return null; + // TODO: Implement the multiple nodes panel + // return + // { + // currentNodes.map((node, index) => { + // return node?.nlabel == 'Gene' ? + // + // : null; + // }) + // } + // + } + } else if (menuKey === 'knowledge') { + return <> + +
+

Query or Predict Knowledges

+

Please select the resources and relation types to filter the knowledges.

+
+
+ { @@ -772,118 +793,185 @@ const KnowledgeTable: React.FC = (props) => { ); })} - - */} + + + +
+ + Selected {selectedRowKeys.length} items + + - -
- getRowKey(record)} - expandable={{ - expandedRowRender: (record) => ( -

- Key Sentence {record.key_sentence || 'No Key Sentence'} -

- ), - }} - pagination={{ - showSizeChanger: true, - showQuickJumper: false, - pageSizeOptions: ['10', '20', '50', '100', '300', '500'], - current: page, - pageSize: pageSize, - total: total || 0, - position: ['topLeft'], - showTotal: (total) => { - return `Total ${total} records`; - }, - }} - onChange={(pagination) => { - setPage(pagination.current || 1); - setPageSize(pagination.pageSize || 10); - }} - >
- { - setActivatedNode(undefined); - }} - open={activatedNode !== undefined} - > + + + +
+ getRowKey(record)} + expandable={{ + expandedRowRender: (record) => ( +

+ Key Sentence {record.key_sentence || 'No Key Sentence'} +

+ ), + }} + pagination={{ + showSizeChanger: true, + showQuickJumper: false, + pageSizeOptions: ['10', '20', '50', '100', '300', '500'], + current: page, + pageSize: pageSize, + total: total || 0, + position: ['topLeft'], + showTotal: (total) => { + return `Total ${total} records`; + }, + }} + onChange={(pagination) => { + setPage(pagination.current || 1); + setPageSize(pagination.pageSize || 10); + }} + >
+ { + setActivatedNode(undefined); + }} + open={activatedNode !== undefined} + > + { + activatedNode ? + : + + } + + { + setDrawerVisible(false); + }} + destroyOnClose={true} + open={drawerVisible} + > + {(drawerVisible && edgeInfo) ? + + : } + + + + } + } + + return (isAuthenticated()) && (total == 0 ? ( + + + +

{ - activatedNode ? - : - + loading ? 'Loading Knowledges ' : 'No Knowledges ' } - - { - setDrawerVisible(false); - }} - destroyOnClose={true} - open={drawerVisible} - > - {(drawerVisible && edgeInfo) ? - - : } - - - - + + for Your Query + + { + nodeIds ? `( ${nodeIds.join(', ')} )` : '' + } +

+ + + } + style={{ + height: '100%', + flexDirection: 'column', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }} /> +
+
+ ) : ( + + + + + + + {whichPanel()} + )); };