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 = () => {
-
+ {/* */}
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 <>
+
+
+
+
-
-
+ {/*
*/}
+
+
+
+
+
+ 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()}
+
));
};