diff --git a/package-lock.json b/package-lock.json
index 7ba99706f..ae69b1d78 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,14 +5,15 @@
"requires": true,
"packages": {
"": {
+ "name": "bento_web",
"version": "0.2.0",
"license": "LGPL-3.0-only",
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.12.1",
"antd": "^3.26.19",
- "cross-fetch": "^3.0.6",
+ "cross-fetch": "^3.1.5",
"file-saver": "^2.0.5",
- "igv": "https://github.com/bento-platform/igv.js/archive/bento.2020-10-30.tar.gz",
+ "igv": "^2.10.1",
"iso8601-duration": "^1.2.0",
"prop-types": "^15.7.2",
"query-string": "^6.13.7",
@@ -20,7 +21,6 @@
"react-dom": "^16.14.0",
"react-json-view": "^1.21.1",
"react-redux": "^7.2.2",
- "react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-router-prop-types": "^1.0.5",
"react-syntax-highlighter": "^13.5.3",
@@ -43,6 +43,7 @@
"css-loader": "^5.0.0",
"eslint": "^7.12.0",
"eslint-plugin-react": "^7.21.5",
+ "file-loader": "^6.2.0",
"html-webpack-plugin": "^4.5.0",
"style-loader": "^2.0.0",
"webpack": "^4.44.2",
@@ -1784,9 +1785,9 @@
"dev": true
},
"node_modules/@types/json-schema": {
- "version": "7.0.7",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
- "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==",
+ "version": "7.0.11",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
+ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/@types/node": {
@@ -3619,11 +3620,11 @@
}
},
"node_modules/cross-fetch": {
- "version": "3.1.4",
- "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz",
- "integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
+ "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
"dependencies": {
- "node-fetch": "2.6.1"
+ "node-fetch": "2.6.7"
}
},
"node_modules/cross-spawn": {
@@ -5130,6 +5131,58 @@
"node": "^10.12.0 || >=12.0.0"
}
},
+ "node_modules/file-loader": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
+ "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==",
+ "dev": true,
+ "dependencies": {
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^4.0.0 || ^5.0.0"
+ }
+ },
+ "node_modules/file-loader/node_modules/loader-utils": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
+ "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
+ "dev": true,
+ "dependencies": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=8.9.0"
+ }
+ },
+ "node_modules/file-loader/node_modules/schema-utils": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
+ "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
"node_modules/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
@@ -5835,10 +5888,9 @@
}
},
"node_modules/igv": {
- "version": "2.6.8",
- "resolved": "https://github.com/bento-platform/igv.js/archive/bento.2020-10-30.tar.gz",
- "integrity": "sha1-ZIYfMCook25+sMMgtmE2cCGuwoU= sha512-B0XbuB0keZVzTZO0+m/8vtI9IU3gApgCEAiy/zcsrvO8q2ftD5rt3+IM51QmQsrOLCSaDFzwoLEzT9kdi1Vm/Q==",
- "license": "MIT"
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/igv/-/igv-2.13.1.tgz",
+ "integrity": "sha512-XqFqHLfDxgl6WSk6Cy2hSMfjg5mhzWFRxk+6joTKkZHXcprSvfYNNLB+ggjrTLkNPBf1p9JikHqwirEIJIdSqg=="
},
"node_modules/immutable": {
"version": "3.7.6",
@@ -6901,11 +6953,22 @@
}
},
"node_modules/node-fetch": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
- "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
+ "version": "2.6.7",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
+ "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
"engines": {
"node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
}
},
"node_modules/node-libs-browser": {
@@ -10287,6 +10350,11 @@
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
"integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
},
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ },
"node_modules/ts-essentials": {
"version": "2.0.12",
"resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-2.0.12.tgz",
@@ -11175,6 +11243,11 @@
"node": ">=0.10"
}
},
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ },
"node_modules/webpack": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz",
@@ -11353,6 +11426,15 @@
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz",
"integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA=="
},
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -12735,9 +12817,9 @@
"dev": true
},
"@types/json-schema": {
- "version": "7.0.7",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
- "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==",
+ "version": "7.0.11",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
+ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"@types/node": {
@@ -14320,11 +14402,11 @@
}
},
"cross-fetch": {
- "version": "3.1.4",
- "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz",
- "integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
+ "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
"requires": {
- "node-fetch": "2.6.1"
+ "node-fetch": "2.6.7"
}
},
"cross-spawn": {
@@ -15555,6 +15637,40 @@
"flat-cache": "^3.0.4"
}
},
+ "file-loader": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
+ "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0"
+ },
+ "dependencies": {
+ "loader-utils": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
+ "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
+ "dev": true,
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ }
+ },
+ "schema-utils": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
+ "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+ "dev": true,
+ "requires": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ }
+ }
+ }
+ },
"file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
@@ -16092,8 +16208,9 @@
"dev": true
},
"igv": {
- "version": "https://github.com/bento-platform/igv.js/archive/bento.2020-10-30.tar.gz",
- "integrity": "sha1-ZIYfMCook25+sMMgtmE2cCGuwoU= sha512-B0XbuB0keZVzTZO0+m/8vtI9IU3gApgCEAiy/zcsrvO8q2ftD5rt3+IM51QmQsrOLCSaDFzwoLEzT9kdi1Vm/Q=="
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/igv/-/igv-2.13.1.tgz",
+ "integrity": "sha512-XqFqHLfDxgl6WSk6Cy2hSMfjg5mhzWFRxk+6joTKkZHXcprSvfYNNLB+ggjrTLkNPBf1p9JikHqwirEIJIdSqg=="
},
"immutable": {
"version": "3.7.6",
@@ -16931,9 +17048,12 @@
}
},
"node-fetch": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
- "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
+ "version": "2.6.7",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
+ "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+ "requires": {
+ "whatwg-url": "^5.0.0"
+ }
},
"node-libs-browser": {
"version": "2.2.1",
@@ -19735,6 +19855,11 @@
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
"integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
},
+ "tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ },
"ts-essentials": {
"version": "2.0.12",
"resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-2.0.12.tgz",
@@ -20536,6 +20661,11 @@
}
}
},
+ "webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ },
"webpack": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz",
@@ -20658,6 +20788,15 @@
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz",
"integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA=="
},
+ "whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "requires": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
diff --git a/package.json b/package.json
index 31bdc8eb6..b922765af 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.12.1",
"antd": "^3.26.19",
- "cross-fetch": "^3.0.6",
+ "cross-fetch": "^3.1.5",
"file-saver": "^2.0.5",
"igv": "^2.10.1",
"iso8601-duration": "^1.2.0",
@@ -40,6 +40,7 @@
"eslint-plugin-react": "^7.21.5",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^4.5.0",
+ "prettier": "^2.7.1",
"style-loader": "^2.0.0",
"webpack": "^4.44.2",
"webpack-cli": "^4.1.0",
@@ -67,6 +68,7 @@
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
- "singleQuote": false
+ "singleQuote": false,
+ "printWidth": 120
}
}
diff --git a/src/components/ServiceList.js b/src/components/ServiceList.js
index 42bdd1f73..324fae5f7 100644
--- a/src/components/ServiceList.js
+++ b/src/components/ServiceList.js
@@ -42,7 +42,9 @@ const serviceColumns = (isOwner) => [
{
title: "URL",
dataIndex: "serviceInfo.url",
- render: (url) => {`${url}/service-info`},
+ // url is undefined when service-registry does not receive replies from
+ // the container.
+ render: (url) => url ? {`${url}/service-info`} : "N/A",
},
{
title: "Status",
diff --git a/src/components/datasets/DatasetForm.js b/src/components/datasets/DatasetForm.js
index ccfebd9b0..dd968cfed 100644
--- a/src/components/datasets/DatasetForm.js
+++ b/src/components/datasets/DatasetForm.js
@@ -1,51 +1,57 @@
-import React, {Component} from "react";
+import React from "react";
import PropTypes from "prop-types";
-import {Form, Input} from "antd";
+import { Form, Input } from "antd";
+const { Item } = Form;
import DataUseInput from "../DataUseInput";
-import {DATA_USE_PROP_TYPE_SHAPE, INITIAL_DATA_USE_VALUE} from "../../duo";
-import {simpleDeepCopy} from "../../utils/misc";
+import { DATA_USE_PROP_TYPE_SHAPE, INITIAL_DATA_USE_VALUE } from "../../duo";
+import { simpleDeepCopy } from "../../utils/misc";
-
-class DatasetForm extends Component {
- render() {
- return
- {this.props.form.getFieldDecorator("title", {
- initialValue: (this.props.initialValue || {title: ""}).title || "",
- rules: [{required: true}, {min: 3}]
+const DatasetForm = ({ style, initialValue, form }) => {
+ return (
+
-
- {this.props.form.getFieldDecorator("description", {
- initialValue: (this.props.initialValue || {description: ""}).description || "",
- rules: [{required: true}]
+
+ -
+ {form.getFieldDecorator("description", {
+ initialValue: (initialValue || { description: "" }).description || "",
+ rules: [{ required: true }],
})()}
-
-
- {this.props.form.getFieldDecorator("contact_info", {
- initialValue: (this.props.initialValue || {contact_info: ""}).contact_info || "",
- })()}
-
-
- {this.props.form.getFieldDecorator("data_use", {
- initialValue: ((this.props.initialValue ||
- {data_use: simpleDeepCopy(INITIAL_DATA_USE_VALUE)}).data_use ||
- simpleDeepCopy(INITIAL_DATA_USE_VALUE)),
- rules: [{required: true}, (rule, value, callback) => {
- if (!(value.consent_code || {}).primary_category) {
- callback(["Please specify one primary consent code"]);
- return;
- }
- callback([]);
- }]
+
+ -
+ {form.getFieldDecorator("contact_info", {
+ initialValue: (initialValue || { contact_info: "" }).contact_info || "",
+ })()}
+
+ -
+ {form.getFieldDecorator("data_use", {
+ initialValue:
+ (
+ initialValue || {
+ data_use: simpleDeepCopy(INITIAL_DATA_USE_VALUE),
+ }
+ ).data_use || simpleDeepCopy(INITIAL_DATA_USE_VALUE),
+ rules: [
+ { required: true },
+ (rule, value, callback) => {
+ if (!(value.consent_code || {}).primary_category) {
+ callback(["Please specify one primary consent code"]);
+ return;
+ }
+ callback([]);
+ },
+ ],
})()}
-
- ;
- }
-}
+
+
+ );
+};
DatasetForm.propTypes = {
style: PropTypes.object,
@@ -53,8 +59,8 @@ DatasetForm.propTypes = {
title: PropTypes.string,
description: PropTypes.string,
contact_info: PropTypes.string,
- data_use: DATA_USE_PROP_TYPE_SHAPE, // TODO: Shared shape for data use
- })
+ data_use: DATA_USE_PROP_TYPE_SHAPE, // TODO: Shared shape for data use
+ }),
};
-export default Form.create({name: "dataset_form"})(DatasetForm);
+export default Form.create({ name: "dataset_form" })(DatasetForm);
diff --git a/src/components/datasets/DatasetTables.js b/src/components/datasets/DatasetTables.js
index a1cca80e1..1260d6032 100644
--- a/src/components/datasets/DatasetTables.js
+++ b/src/components/datasets/DatasetTables.js
@@ -1,10 +1,9 @@
-import React, {Component} from "react";
+import React, { useState } from "react";
import PropTypes from "prop-types";
-import {bindActionCreators} from "redux";
-import {connect} from "react-redux";
+import { useSelector, useDispatch } from "react-redux";
-import {Button, Col, Row, Table, Typography} from "antd";
+import { Button, Col, Row, Table, Typography } from "antd";
import TableAdditionModal from "./table/TableAdditionModal";
import TableDeletionModal from "./table/TableDeletionModal";
@@ -12,173 +11,181 @@ import TableDeletionModal from "./table/TableDeletionModal";
import {
addProjectTable,
deleteProjectTableIfPossible,
- fetchProjectsWithDatasetsAndTables
+ fetchProjectsWithDatasetsAndTables,
} from "../../modules/metadata/actions";
-import {nop} from "../../utils/misc";
-import {fetchTableSummaryIfPossible} from "../../modules/tables/actions";
+import { nop } from "../../utils/misc";
+import { fetchTableSummaryIfPossible } from "../../modules/tables/actions";
import TableSummaryModal from "./table/TableSummaryModal";
-import {datasetPropTypesShape, projectPropTypesShape, serviceInfoPropTypesShape} from "../../propTypes";
+import { datasetPropTypesShape, projectPropTypesShape } from "../../propTypes";
+const NA_TEXT = N/A;
-const NA_TEXT = (N/A);
+const DatasetTables = ({ isPrivate, project, dataset, onTableIngest, isFetchingTables }) => {
+ const dispatch = useDispatch();
-class DatasetTables extends Component {
- constructor(props) {
- super(props);
+ const chordServicesByArtifact = useSelector((state) => state.chordServices.itemsByArtifact);
+ const serviceInfoByArtifact = useSelector((state) => state.services.itemsByArtifact);
- this.state = {
- additionModalVisible: false,
- deletionModalVisible: false,
- tableSummaryModalVisible: false,
- selectedTable: null,
- };
+ const [additionModalVisible, setAdditionModalVisible] = useState(false);
+ const [deletionModalVisible, setDeletionModalVisible] = useState(false);
+ const [tableSummaryModalVisible, setTableSummaryModalVisible] = useState(false);
+ const [selectedTable, setSelectedTable] = useState(null);
- this.handleAdditionClick = this.handleAdditionClick.bind(this);
- this.handleAdditionCancel = this.handleAdditionCancel.bind(this);
- this.handleAdditionSubmit = this.handleAdditionSubmit.bind(this);
-
- this.handleTableDeletionClick = this.handleTableDeletionClick.bind(this);
- this.handleTableDeletionCancel = this.handleTableDeletionCancel.bind(this);
- this.handleTableDeletionSubmit = this.handleTableDeletionSubmit.bind(this);
-
- this.showTableSummaryModal = this.showTableSummaryModal.bind(this);
- }
-
- handleAdditionClick() {
- this.setState({additionModalVisible: true});
- }
-
- handleAdditionCancel() {
- this.setState({additionModalVisible: false});
- }
-
- async handleAdditionSubmit(values) {
+ const handleAdditionSubmit = async (values) => {
const [serviceArtifact, dataTypeID] = values.dataType.split(":");
- const serviceInfo = this.props.serviceInfoByArtifact[serviceArtifact];
- await this.props.addProjectTable(this.props.dataset.identifier, serviceInfo, dataTypeID, values.name);
-
- await this.props.fetchProjectsWithDatasetsAndTables(); // TODO: If needed / only this project...
-
- this.setState({additionModalVisible: false});
- }
-
- handleTableDeletionClick(t) {
- this.setState({deletionModalVisible: true, selectedTable: t});
- }
-
- handleTableDeletionCancel() {
- this.setState({deletionModalVisible: false});
- }
-
- async handleTableDeletionSubmit() {
- if (this.state.selectedTable === null) return;
- await this.props.deleteProjectTable(this.state.selectedTable);
-
- await this.props.fetchProjectsWithDatasetsAndTables(); // TODO: If needed / only this project...
-
- this.setState({deletionModalVisible: false});
- }
-
- showTableSummaryModal(table) {
- this.props.fetchTableSummaryIfPossible(this.props.chordServicesByArtifact[table.service_artifact],
- this.props.serviceInfoByArtifact[table.service_artifact], table.table_id); // TODO
- this.setState({tableSummaryModalVisible: true, selectedTable: table});
- }
-
- render() {
- const tableListColumns = [
- {
- title: "ID",
- dataIndex: "table_id",
- render: (tableID, t) => this.props.isPrivate
- ? this.showTableSummaryModal(t)}>{tableID}
- : tableID,
- },
- {
- title: "Name",
- dataIndex: "name",
- render: n => (n ? n : NA_TEXT),
- defaultSortOrder: "ascend",
- sorter: (a, b) => (a.name && b.name)
- ? a.name.localeCompare(b.name)
- : a.table_id.localeCompare(b.table_id)
- },
- {title: "Data Type", dataIndex: "data_type"},
- ...(this.props.isPrivate ? [
+ const serviceInfo = serviceInfoByArtifact[serviceArtifact];
+ await dispatch(addProjectTable(project, dataset.identifier, serviceInfo, dataTypeID, values.name));
+
+ await dispatch(fetchProjectsWithDatasetsAndTables()); // TODO: If needed / only this project...
+
+ setAdditionModalVisible(false);
+ };
+
+ const handleTableDeletionClick = (t) => {
+ setDeletionModalVisible(true);
+ setSelectedTable(t);
+ };
+
+ const handleTableDeletionSubmit = async () => {
+ if (selectedTable === null) return;
+ await dispatch(deleteProjectTableIfPossible(project, selectedTable));
+ await dispatch(fetchProjectsWithDatasetsAndTables()); // TODO: If needed / only this project...
+
+ setDeletionModalVisible(false);
+ };
+
+ const showTableSummaryModal = (table) => {
+ dispatch(
+ fetchTableSummaryIfPossible(
+ chordServicesByArtifact[table.service_artifact],
+ serviceInfoByArtifact[table.service_artifact],
+ table.table_id
+ )
+ );
+ setTableSummaryModalVisible(true);
+ setSelectedTable(table);
+ };
+
+ const tableListColumns = [
+ {
+ title: "ID",
+ dataIndex: "table_id",
+ render: (tableID, t) =>
+ isPrivate ? (
+ showTableSummaryModal(t)}>
+ {tableID}
+
+ ) : (
+ {tableID}
+ ),
+ },
+ {
+ title: "Name",
+ dataIndex: "name",
+ render: (n) => (n ? n : NA_TEXT),
+ defaultSortOrder: "ascend",
+ sorter: (a, b) => (a.name && b.name ? a.name.localeCompare(b.name) : a.table_id.localeCompare(b.table_id)),
+ },
+ { title: "Data Type", dataIndex: "data_type" },
+ ...(isPrivate
+ ? [
{
title: "Actions",
key: "actions",
- width: 230, /*330,*/
- render: t => (
-
-
-
-
- {/* TODO: Edit Table Name: v0.2 */}
- {/**/}
- {t.manageable !== false ? (
-
- ) : null}
-
- )
- }
- ] : [])
- ];
-
- const dataset = this.props.dataset || {};
- const tables = (dataset.tables || []).map(t => ({...t, name: t.name || null}));
- return <>
+ width: 230 /*330,*/,
+ render: (t) => (
+
+
+
+
+ {/* TODO: Edit Table Name: v0.2 */}
+ {/**/}
+ {t.manageable !== false ? (
+
+
+
+ ) : null}
+
+ ),
+ },
+ ]
+ : []),
+ ];
+
+ dataset = dataset || {};
+ const tables = (dataset.tables || []).map((t) => ({
+ ...t,
+ name: t.name || null,
+ }));
+ return (
+ <>
Tables
- {this.props.isPrivate ? (
-
+ {isPrivate ? (
+
{/* TODO: Implement v0.2
- {(this.props.strayTables || []).length > 0 ? (
+ {(strayTables || []).length > 0 ? (
) : null} */}
-
) : null}
-
(TODO: List of files)} TODO: Implement v0.2
- columns={tableListColumns}
- loading={this.props.isFetchingTables} />
-
- this.setState({tableSummaryModalVisible: false})} />
-
- this.handleAdditionSubmit(vs)}
- onCancel={() => this.handleAdditionCancel()} />
-
- this.handleTableDeletionSubmit()}
- onCancel={() => this.handleTableDeletionCancel()} />
- >;
- }
-}
+ (TODO: List of files)} TODO: Implement v0.2
+ columns={tableListColumns}
+ loading={isFetchingTables}
+ />
+
+ setTableSummaryModalVisible(false)}
+ />
+
+ setAdditionModalVisible(false)}
+ />
+
+ setDeletionModalVisible(false)}
+ />
+ >
+ );
+};
DatasetTables.propTypes = {
isPrivate: PropTypes.bool,
@@ -186,48 +193,6 @@ DatasetTables.propTypes = {
dataset: datasetPropTypesShape,
onTableIngest: PropTypes.func,
isFetchingTables: PropTypes.bool,
-
- chordServicesByArtifact: PropTypes.objectOf(PropTypes.shape({
- apt_dependencies: PropTypes.arrayOf(PropTypes.string),
- data_service: PropTypes.bool,
- manageable_tables: PropTypes.string,
- python_callable: PropTypes.string,
- python_module: PropTypes.string,
- repository: PropTypes.string,
- run_environment: PropTypes.objectOf(PropTypes.string),
- service_runnable: PropTypes.string,
- type: PropTypes.shape({
- artifact: PropTypes.string.isRequired,
- language: PropTypes.string,
- organization: PropTypes.string,
- }),
- wsgi: PropTypes.bool,
-
- post_stop_commands: PropTypes.arrayOf(PropTypes.string),
- post_start_commands: PropTypes.arrayOf(PropTypes.string),
- pre_install_commands: PropTypes.arrayOf(PropTypes.string),
- pre_start_commands: PropTypes.arrayOf(PropTypes.string),
- })),
- serviceInfoByArtifact: PropTypes.objectOf(serviceInfoPropTypesShape),
-
- addProjectTable: PropTypes.func,
- deleteProjectTable: PropTypes.func,
- fetchProjectsWithDatasetsAndTables: PropTypes.func,
- fetchTableSummaryIfPossible: PropTypes.func,
};
-const mapStateToProps = state => ({
- chordServicesByArtifact: state.chordServices.itemsByArtifact,
- serviceInfoByArtifact: state.services.itemsByArtifact,
-});
-
-const mapDispatchToProps = (dispatch, ownProps) => ({
- addProjectTable: (ds, s, dt, name) => dispatch(addProjectTable(ownProps.project, ds, s, dt, name)),
- deleteProjectTable: table => dispatch(deleteProjectTableIfPossible(ownProps.project, table)),
- ...bindActionCreators({
- fetchProjectsWithDatasetsAndTables,
- fetchTableSummaryIfPossible,
- }, dispatch),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(DatasetTables);
+export default DatasetTables;
diff --git a/src/components/explorer/ExplorerDatasetSearch.js b/src/components/explorer/ExplorerDatasetSearch.js
index 67e570eac..ddfbc0a46 100644
--- a/src/components/explorer/ExplorerDatasetSearch.js
+++ b/src/components/explorer/ExplorerDatasetSearch.js
@@ -46,7 +46,7 @@ const SEARCH_RESULT_COLUMNS = [
dataIndex: "biosamples",
render: samples => <>
{samples.length} Sample{samples.length === 1 ? "" : "s"}{samples.length ? ": " : ""}
- {samples.map(b => b.id).join(", ")}
+ {samples.join(", ")}
>,
sorter: (a, b) => a.biosamples.length - b.biosamples.length,
sortDirections: ["descend", "ascend", "descend"],
@@ -54,8 +54,8 @@ const SEARCH_RESULT_COLUMNS = [
{
title: "Experiments",
dataIndex: "experiments",
- render: experiments => <>{experiments.length} Experiment{experiments.length === 1 ? "" : "s"}>,
- sorter: (a, b) => a.experiments.length - b.experiments.length,
+ render: experiments => <>{experiments} Experiment{experiments === 1 ? "" : "s"}>,
+ sorter: (a, b) => a.experiments - b.experiments,
sortDirections: ["descend", "ascend", "descend"],
},
];
@@ -117,7 +117,7 @@ class ExplorerDatasetSearch extends Component {
? (this.state.currentPage * this.state.pageSize) - this.state.pageSize + 1
: 0;
- console.log("search results: " + this.props.searchResults);
+ console.log("search results: ", this.props.searchResults);
return <>
Explore Dataset {selectedDataset.title}
@@ -142,9 +142,9 @@ class ExplorerDatasetSearch extends Component {
onClick={() => this.setState({tracksModalVisible: true})}
disabled={true}>
Visualize Tracks */}
- this.setState({summaryModalVisible: true})}>View Summary
+ onClick={() => this.setState({summaryModalVisible: true})}>View Summary */}
{
// expand here accordingly
const isDownloadable = (result) =>
- result.file_format?.toLowerCase() === "vcf" ||
- result.file_format?.toLowerCase() === "cram";
+ ["vcf", "cram", "bw", "bigwig"].includes(result.file_format?.toLowerCase());
IndividualExperiments.propTypes = {
individual: individualPropTypesShape,
diff --git a/src/components/explorer/SearchTracksModal.js b/src/components/explorer/SearchTracksModal.js
index 881c72ac3..ece710b15 100644
--- a/src/components/explorer/SearchTracksModal.js
+++ b/src/components/explorer/SearchTracksModal.js
@@ -6,8 +6,6 @@ import {explorerSearchResultsPropTypesShape} from "../../propTypes";
import GenomeBrowser from "./GenomeBrowser";
const SearchTracksModal = ({searchResults, ...props}) => {
- console.log("searchResults", searchResults);
-
const variants = searchResults.results.results.variant || [];
// TODO: Display some basic statistics about n. of variants/tracks/etc.
diff --git a/src/components/overview/VariantsSummary.js b/src/components/overview/VariantsSummary.js
index 255c149fb..600b17c68 100644
--- a/src/components/overview/VariantsSummary.js
+++ b/src/components/overview/VariantsSummary.js
@@ -1,111 +1,25 @@
-import React, {Component} from "react";
-import {connect} from "react-redux";
-import PropTypes from "prop-types";
-import {Col, Row, Spin, Statistic, Typography, Icon} from "antd";
+import React from "react";
+import { useSelector } from "react-redux";
+import { Col, Row, Spin, Statistic, Typography, Icon } from "antd";
-const mapStateToProps = state => ({
- tableSummaries: state.tableSummaries,
-});
+const VariantsSummary = ( ) => {
-const actionCreators = {};
+ const fetchingTableSummaries = useSelector(state => state.tableSummaries?.isFetching);
+ const numVCFs =
+ useSelector((state) => state.overviewSummary.data?.data_type_specific?.experiment_results.file_format.VCF) || 0;
-class VariantsSummary extends Component {
-
- static propTypes = {
- tableSummaries : PropTypes.shape({
- isFetching: PropTypes.bool,
- summariesByServiceArtifactAndTableID: PropTypes.object,
- }),
- };
-
- constructor(props) {
- super(props);
- this.state = {
- activeIndex: 0,
- sexChartActiveIndex: 0,
- diseaseChartActiveIndex: 0,
- biosamplesChartActiveIndex: 0,
- phenotypicFeaturesThresholdSliderValue: 0.01,
- chartPadding: "1rem",
- chartWidthHeight: 500,
- chartLabelPaddingTop: 3,
- chartLabelPaddingLeft: 3,
- };
- }
-
- render() {
- const fetchingTableSummaries = this.props.tableSummaries?.isFetching;
- const variantTableSummaries =
- this.props.tableSummaries?.summariesByServiceArtifactAndTableID?.variant;
-
- let numVCFs = 0;
- Object.values(variantTableSummaries || []).forEach(s => {
- numVCFs += s.data_type_specific.vcf_files;
- });
-
- return <>
+ return (
+ <>
Variants
-
+
- }
- value={numVCFs} />
+ } value={numVCFs} />
- >;
- }
-
- updateDimensions = () => {
- if (window.innerWidth < 576) { //xs
- this.setState({
- chartPadding: "0rem",
- chartWidthHeight: window.innerWidth,
- chartLabelPaddingTop: 3,
- chartLabelPaddingLeft: 3
- });
- } else if (window.innerWidth < 768) { // sm
- this.setState({
- chartPadding: "1rem",
- chartWidthHeight: window.innerWidth,
- chartLabelPaddingTop: 3,
- chartLabelPaddingLeft: 6 });
- } else if (window.innerWidth < 992) { // md
- this.setState({
- chartPadding: "2rem",
- chartWidthHeight: window.innerWidth,
- chartLabelPaddingTop: 3,
- chartLabelPaddingLeft: 5 });
- } else if (window.innerWidth < 1200) { // lg
- this.setState({
- chartPadding: "4rem",
- chartWidthHeight: window.innerWidth / 2,
- chartLabelPaddingTop: 3,
- chartLabelPaddingLeft: 6 });
- } else if (window.innerWidth < 1600) { // xl
- this.setState({
- chartPadding: "6rem",
- chartWidthHeight: window.innerWidth / 2,
- chartLabelPaddingTop: 3,
- chartLabelPaddingLeft: 7 });
- } else {
- this.setState({
- chartPadding: "10rem",
- chartWidthHeight: window.innerWidth / 2,
- chartLabelPaddingTop: 5,
- chartLabelPaddingLeft: 7 }); // > xl
- }
- }
-
- componentDidMount() {
- this.updateDimensions();
- window.addEventListener("resize", this.updateDimensions);
- }
-
- componentWillUnmount() {
- window.removeEventListener("resize", this.updateDimensions);
- }
-}
+ >
+ );
+};
-export default connect(mapStateToProps, actionCreators)(VariantsSummary);
+export default VariantsSummary;
diff --git a/src/modules/explorer/actions.js b/src/modules/explorer/actions.js
index 2958e378e..21141db4b 100644
--- a/src/modules/explorer/actions.js
+++ b/src/modules/explorer/actions.js
@@ -2,6 +2,8 @@ import {createNetworkActionTypes, networkAction} from "../../utils/actions";
import {jsonRequest} from "../../utils/requests";
import {extractQueriesFromDataTypeForms} from "../../utils/search";
+import FileSaver from "file-saver";
+
export const PERFORM_SEARCH = createNetworkActionTypes("EXPLORER.PERFORM_SEARCH");
export const PERFORM_INDIVIDUAL_CSV_DOWNLOAD = createNetworkActionTypes("EXPLORER.PERFORM_INDIVIDUAL_CSV_DOWNLOAD");
export const ADD_DATA_TYPE_QUERY_FORM = "EXPLORER.ADD_DATA_TYPE_QUERY_FORM";
@@ -26,13 +28,6 @@ const performSearch = networkAction((datasetID, dataTypeQueries, excludeFromAuto
err: "Error performing search",
}));
-const performIndividualCSVDownload = networkAction((individualsUrl) => () => ({
- types: PERFORM_INDIVIDUAL_CSV_DOWNLOAD,
- url: individualsUrl,
- parse: r => r.blob(), // Parse the CSV as a binary blob rather than e.g. a JSON file
- err: "Error performing individual csv download",
-}));
-
export const performSearchIfPossible = (datasetID) => (dispatch, getState) => {
if (getState().explorer.fetchingSearchByDatasetID[datasetID]) return;
@@ -57,21 +52,30 @@ export const performSearchIfPossible = (datasetID) => (dispatch, getState) => {
export const performIndividualsDownloadCSVIfPossible = (datasetId, individualIds, allSearchResults) =>
(dispatch, getState) => {
- console.log("Initiating PerformIndividualsDownloadCSVIfPossible");
-
- let dataUrl = `${getState().services.itemsByArtifact.metadata.url}/api/individuals?format=csv`;
-
- // build query string
- // TODO: This should use the actual JS API for URL construction
- if (individualIds.length > 0) { // Get only selected results
- dataUrl += ("&page_size=" + individualIds.length);
- individualIds.forEach(id => dataUrl += `&id=${id}`);
- } else { // Get all search results
- dataUrl += ("&page_size=" + allSearchResults.length);
- allSearchResults.forEach(sr => dataUrl += `&id=${sr.key}`);
- }
- return dispatch(performIndividualCSVDownload(dataUrl));
+ const ids = individualIds.length ? individualIds : allSearchResults.map(sr => sr.key);
+
+ const myHeaders = new Headers();
+ myHeaders.append("Content-Type", "application/json");
+
+ const raw = JSON.stringify({
+ "id": ids,
+ "format": "csv"
+ });
+
+ const requestOptions = {
+ method: "POST",
+ headers: myHeaders,
+ body: raw,
+ redirect: "follow"
+ };
+
+ fetch(`${getState().services.itemsByArtifact.metadata.url}/api/batch/individuals`, requestOptions)
+ .then(response => response.blob())
+ .then(result => {
+ FileSaver.saveAs(result, "data.csv");
+ })
+ .catch(error => console.log("error", error));
};
@@ -124,7 +128,8 @@ export const neutralizeAutoQueryPageTransition = () => ({
const performFreeTextSearch = networkAction((datasetID, term) => (dispatch, getState) => ({
types: FREE_TEXT_SEARCH,
params: {datasetID},
- url: `${getState().services.metadataService.url}/api/individuals?search=${term}&page_size=10000`,
+ url: `${getState().services.metadataService.url}/api/individuals?search=${term}&page_size=10000` +
+ "&format=bento_search_result",
err: `Error searching in all records with term ${term}`,
}));
diff --git a/src/modules/explorer/reducers.js b/src/modules/explorer/reducers.js
index 9baf197d1..4c6b97558 100644
--- a/src/modules/explorer/reducers.js
+++ b/src/modules/explorer/reducers.js
@@ -178,7 +178,7 @@ export const explorer = (
...state.searchResultsByDatasetID,
[action.datasetID]: {
results: freeTextResults(action.data),
- searchFormattedResults: freeTextSearchFormattedResults(action.data.results),
+ searchFormattedResults: tableSearchResults(action.data),
},
},
};
@@ -204,64 +204,19 @@ export const explorer = (
// helpers
const tableSearchResults = (searchResults) => {
- const results = (searchResults || {}).results || {};
- const tableResultSet = {};
-
- // Collect all experiments (which have a biosample, not necessarily unique
- // - one can perform multiple experiments on the same biosample) in arrays
- // by their biosample ID.
- const experimentsByBiosample = {};
- (results.experiment ?? []).forEach(experiment => {
- const biosampleID = experiment.biosample;
- if (!experimentsByBiosample.hasOwnProperty(biosampleID)) {
- experimentsByBiosample[biosampleID] = [];
- }
- experimentsByBiosample[biosampleID].push(experiment);
- });
-
- // First, collect different data from phenopackets and sort them into an object for an individual
- (results.phenopacket || []).forEach(p => {
- const individualID = p.subject.id;
- if (!tableResultSet.hasOwnProperty(individualID)) {
- // We DO NOT initialize diseases, PFs, experiments etc. here because we may have
- // multiple phenopackets on the same individual, and we want to combine this
- // into one record for the individual. It gets populated separately below.
- tableResultSet[individualID] = {
- key: individualID,
- individual: p.subject,
- biosamples: {}, // Only includes biosamples from the phenopackets that matched the search query.
- diseases: {}, // Only includes diseases from "
- phenotypic_features: {},
- experiments: {}, // Filled from all the experiments conducted on the biosamples
- };
- }
-
- // Accumulate biosamples based on individual ID and experiments by that biosample
- // ID, de-duplicating in the process to event overlap if multiple phenopackets
- // have the same biosample(s) or something weird.
- (p.biosamples ?? []).forEach(b => {
- tableResultSet[individualID].biosamples[b.id] = b;
- (experimentsByBiosample[b.id] ?? []).forEach(e => tableResultSet[individualID].experiments[e.id] = e);
- });
+ const results = (searchResults || {}).results || [];
- // Accumulate diseases and phenotypic features in the same manner.
- (p.diseases ?? []).forEach(d => tableResultSet[individualID].diseases[d.id] = d);
- (p.phenotypic_features ?? []).forEach(pf => tableResultSet[individualID].phenotypic_features[pf.type.id] = pf);
+ return results.map((p) => {
+ return {
+ key: p.subject_id,
+ individual: {
+ id: p.subject_id,
+ alternate_ids: p.alternate_ids ?? []
+ },
+ biosamples: p.biosamples,
+ experiments: p.num_experiments
+ };
});
-
- // Now that the biosamples/diseases/PFs/experiments are de-duplicated, turn
- // them into arrays and sort them in a consistent manner. Also flatten the
- // stuff-by-individual object into a sorted array.
- return Object.values(tableResultSet).map(i => ({
- ...i,
- biosamples: Object.values(i.biosamples).sort((b1, b2) => b1.id.localeCompare(b2.id)),
- diseases: Object.values(i.diseases).sort(
- (d1, d2) => d1.id.toString().localeCompare(d2.id.toString())),
- phenotypic_features: Object.values(i.phenotypic_features).sort(
- (pf1, pf2) => pf1.type.id.localeCompare(pf2.type.id)),
- experiments: Object.values(i.experiments).sort(
- (e1, e2) => e1.id.toString().localeCompare(e2.id.toString())),
- })).sort((i1, i2) => i1.key.localeCompare(i2.key));
};
@@ -284,28 +239,3 @@ function freeTextResults(_searchResults) {
}
};
}
-
-// produces searchFormattedResults (input for results table and other components)
-// free-text equivalent to tableSearchResults() above
-function freeTextSearchFormattedResults(searchResults) {
- return searchResults.map(r => {
- return {
- key: r.id,
- biosamples: (r.phenopackets || []).flatMap((p) => p.biosamples),
- diseases: r.phenopackets[0].diseases, //TO FIX, multiple phenopackets possible
- experiments: (r.phenopackets || []).flatMap((p) =>
- (p.biosamples || []).flatMap((b) => b?.experiments ? [b.experiments] : [])
- ),
- phenotypic_features: r.phenopackets[0].phenotypic_features || [], //TO FIX, as above
- individual: {
- age: r.age,
- created: r.created,
- date_of_birth: r.date_of_birth,
- id: r.id,
- karyotypic_sex: r.karyotypic_sex,
- taxonomy: r.taxonomy,
- updated: r.updated,
- },
- };
- });
-}
diff --git a/src/propTypes.js b/src/propTypes.js
index abd02f88f..cb96d075b 100644
--- a/src/propTypes.js
+++ b/src/propTypes.js
@@ -361,10 +361,12 @@ export const explorerSearchResultsPropTypesShape = PropTypes.shape({
}),
searchFormattedResults: PropTypes.arrayOf(PropTypes.shape({
key: PropTypes.string.isRequired,
- individual: individualPropTypesShape,
- biosamples: PropTypes.arrayOf(biosamplePropTypesShape),
- diseases: PropTypes.arrayOf(diseasePropTypesShape),
- experiments: PropTypes.arrayOf(experimentPropTypesShape), // TODO
+ individual: PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ alternate_ids: PropTypes.arrayOf(PropTypes.string)
+ }),
+ biosamples: PropTypes.arrayOf(PropTypes.string),
+ experiments: PropTypes.number
})),
});