diff --git a/apps/platform/src/components/AssociationsToolkit/Table/ColoredCell.jsx b/apps/platform/src/components/AssociationsToolkit/Table/ColoredCell.jsx deleted file mode 100644 index 3954bfb68..000000000 --- a/apps/platform/src/components/AssociationsToolkit/Table/ColoredCell.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import { getScale } from "../utils"; -import Tooltip from "./AssocTooltip"; - -const getClassName = ({ globalScore, hasValue }) => { - if (globalScore) return "data-global-score"; - if (hasValue) return "data-score"; - return "data-empty"; -}; - -function ColoredCell({ - scoreValue, - onClick, - rounded = true, - globalScore, - cell, - isAssociations = true, - hasValue = false, - tablePrefix = null, - colorScale, -}) { - const onClickHandler = onClick ? () => onClick(cell, tablePrefix) : () => ({}); - const backgroundColor = hasValue ? colorScale(scoreValue) : "#fafafa"; - const borderColor = hasValue ? colorScale(scoreValue) : "#e0dede"; - const className = getClassName({ globalScore, hasValue }); - const scoreText = hasValue ? `Score: ${scoreValue.toFixed(2)}` : "No data"; - - const style = { - height: "24px", - width: "24px", - borderRadius: rounded ? "50%" : 0, - backgroundColor, - border: `1px solid ${borderColor}`, - }; - - return ( - -
- - ); -} - -export default ColoredCell; diff --git a/apps/platform/src/components/AssociationsToolkit/AdvanceOptionsMenu.jsx b/apps/platform/src/components/AssociationsToolkit/components/AdvanceOptionsMenu.jsx similarity index 95% rename from apps/platform/src/components/AssociationsToolkit/AdvanceOptionsMenu.jsx rename to apps/platform/src/components/AssociationsToolkit/components/AdvanceOptionsMenu.jsx index 47533c20c..ecd162546 100644 --- a/apps/platform/src/components/AssociationsToolkit/AdvanceOptionsMenu.jsx +++ b/apps/platform/src/components/AssociationsToolkit/components/AdvanceOptionsMenu.jsx @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { styled } from "@mui/material/styles"; import { Tooltip } from "ui"; -import useAotfContext from "./hooks/useAotfContext"; +import useAotfContext from "../hooks/useAotfContext"; const PopoverContent = styled("div")({ padding: "15px", @@ -15,8 +15,6 @@ function DataMenu() { const [anchorEl, setAnchorEl] = useState(null); const { - enableIndirect, - setEnableIndirect, activeHeadersControlls, setActiveHeadersControlls, displayedTable, @@ -44,6 +42,7 @@ function DataMenu() { variant="outlined" disableElevation disabled={isPrioritisation} + sx={{ height: 1, maxHeight: "45px" }} > diff --git a/apps/platform/src/components/AssociationsToolkit/AotfApiPlayground.tsx b/apps/platform/src/components/AssociationsToolkit/components/AotfApiPlayground.tsx similarity index 78% rename from apps/platform/src/components/AssociationsToolkit/AotfApiPlayground.tsx rename to apps/platform/src/components/AssociationsToolkit/components/AotfApiPlayground.tsx index 328b90a1a..56d12cb37 100644 --- a/apps/platform/src/components/AssociationsToolkit/AotfApiPlayground.tsx +++ b/apps/platform/src/components/AssociationsToolkit/components/AotfApiPlayground.tsx @@ -1,5 +1,5 @@ import { ApiPlaygroundDrawer } from "ui"; -import useAotfContext from "./hooks/useAotfContext"; +import useAotfContext from "../hooks/useAotfContext"; function AotfApiPlayground() { const { @@ -28,6 +28,8 @@ function AotfApiPlayground() { aggregationFilters, }; - return ; + return ( + + ); } export default AotfApiPlayground; diff --git a/apps/platform/src/components/AssociationsToolkit/CopyUrlButton.jsx b/apps/platform/src/components/AssociationsToolkit/components/CopyUrlButton.jsx similarity index 100% rename from apps/platform/src/components/AssociationsToolkit/CopyUrlButton.jsx rename to apps/platform/src/components/AssociationsToolkit/components/CopyUrlButton.jsx diff --git a/apps/platform/src/components/AssociationsToolkit/DataDownloader.jsx b/apps/platform/src/components/AssociationsToolkit/components/DataDownloader.jsx similarity index 97% rename from apps/platform/src/components/AssociationsToolkit/DataDownloader.jsx rename to apps/platform/src/components/AssociationsToolkit/components/DataDownloader.jsx index 72f4ed883..7456097c4 100644 --- a/apps/platform/src/components/AssociationsToolkit/DataDownloader.jsx +++ b/apps/platform/src/components/AssociationsToolkit/components/DataDownloader.jsx @@ -30,18 +30,18 @@ import { makeStyles } from "@mui/styles"; import { faCaretDown, faCloudArrowDown } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip, useConfigContext } from "ui"; -import useBatchDownloader from "./hooks/useBatchDownloader"; -import useAotfContext from "./hooks/useAotfContext"; -import OriginalDataSources from "./static_datasets/dataSourcesAssoc"; -import prioritizationCols from "./static_datasets/prioritisationColumns"; +import useBatchDownloader from "../hooks/useBatchDownloader"; +import useAotfContext from "../hooks/useAotfContext"; +import OriginalDataSources from "../static_datasets/dataSourcesAssoc"; +import prioritizationCols from "../static_datasets/prioritisationColumns"; import { getRowsQuerySelector, getExportedColumns, getExportedPrioritisationColumns, createBlob, getFilteredColumnArray, -} from "./utils/downloads"; -import config from "../../config"; +} from "../utils/downloads"; +import config from "../../../config"; import CopyUrlButton from "./CopyUrlButton"; const { isPartnerPreview } = config.profile; @@ -282,6 +282,7 @@ function DataDownloader() { onClick={handleClickBTN} variant="outlined" disableElevation + sx={{ height: 1, maxHeight: "45px" }} > diff --git a/apps/platform/src/components/AssociationsToolkit/DataUploader/DataUploader.jsx b/apps/platform/src/components/AssociationsToolkit/components/DataUploader/DataUploader.jsx similarity index 98% rename from apps/platform/src/components/AssociationsToolkit/DataUploader/DataUploader.jsx rename to apps/platform/src/components/AssociationsToolkit/components/DataUploader/DataUploader.jsx index 4529aa63c..97aafb776 100644 --- a/apps/platform/src/components/AssociationsToolkit/DataUploader/DataUploader.jsx +++ b/apps/platform/src/components/AssociationsToolkit/components/DataUploader/DataUploader.jsx @@ -36,9 +36,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link, Tooltip } from "ui"; import * as XLSX from "xlsx"; -import useAotfContext from "../hooks/useAotfContext"; +import useAotfContext from "../../hooks/useAotfContext"; import ValidationQuery from "./ValidationQuery.gql"; -import client from "../../../client"; +import client from "../../../../client"; import NestedItem from "./NestedItem"; const BorderAccordion = styled(Accordion)(({ theme }) => ({ @@ -245,7 +245,7 @@ const FileExample = ({ entity = "target", runAction }) => { ); }; -function DataUploader({ fileStem }) { +function DataUploader() { const [activeStep, setActiveStep] = useState(0); const [queryTermsResults, setQueryTermsResults] = useState(null); const { entityToGet, pinnedEntries, setPinnedEntries } = useAotfContext(); @@ -407,6 +407,7 @@ function DataUploader({ fileStem }) { onClick={handleClickBTN} variant="outlined" disableElevation + sx={{ height: 1, maxHeight: "45px" }} > diff --git a/apps/platform/src/components/AssociationsToolkit/DataUploader/NestedItem.jsx b/apps/platform/src/components/AssociationsToolkit/components/DataUploader/NestedItem.jsx similarity index 100% rename from apps/platform/src/components/AssociationsToolkit/DataUploader/NestedItem.jsx rename to apps/platform/src/components/AssociationsToolkit/components/DataUploader/NestedItem.jsx diff --git a/apps/platform/src/components/AssociationsToolkit/DataUploader/ValidationQuery.gql b/apps/platform/src/components/AssociationsToolkit/components/DataUploader/ValidationQuery.gql similarity index 100% rename from apps/platform/src/components/AssociationsToolkit/DataUploader/ValidationQuery.gql rename to apps/platform/src/components/AssociationsToolkit/components/DataUploader/ValidationQuery.gql diff --git a/apps/platform/src/components/AssociationsToolkit/DataUploader/index.js b/apps/platform/src/components/AssociationsToolkit/components/DataUploader/index.js similarity index 100% rename from apps/platform/src/components/AssociationsToolkit/DataUploader/index.js rename to apps/platform/src/components/AssociationsToolkit/components/DataUploader/index.js diff --git a/apps/platform/src/components/AssociationsToolkit/HeaderControls/HeaderControls.jsx b/apps/platform/src/components/AssociationsToolkit/components/HeaderControls/HeaderControls.jsx similarity index 95% rename from apps/platform/src/components/AssociationsToolkit/HeaderControls/HeaderControls.jsx rename to apps/platform/src/components/AssociationsToolkit/components/HeaderControls/HeaderControls.jsx index 5816f8097..77cf454fa 100644 --- a/apps/platform/src/components/AssociationsToolkit/HeaderControls/HeaderControls.jsx +++ b/apps/platform/src/components/AssociationsToolkit/components/HeaderControls/HeaderControls.jsx @@ -3,13 +3,13 @@ import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { styled } from "@mui/material/styles"; -import dataSources from "../static_datasets/dataSourcesAssoc"; +import dataSources from "../../static_datasets/dataSourcesAssoc"; import Slider from "./SliderControl"; import Required from "./RequiredControl"; import { GridContainer } from "../layout"; -import useAotfContext from "../hooks/useAotfContext"; +import useAotfContext from "../../hooks/useAotfContext"; const CloseContainer = styled("div")({ position: "absolute", diff --git a/apps/platform/src/components/AssociationsToolkit/HeaderControls/RequiredControl.jsx b/apps/platform/src/components/AssociationsToolkit/components/HeaderControls/RequiredControl.jsx similarity index 91% rename from apps/platform/src/components/AssociationsToolkit/HeaderControls/RequiredControl.jsx rename to apps/platform/src/components/AssociationsToolkit/components/HeaderControls/RequiredControl.jsx index 28e1a37b1..8b572a397 100644 --- a/apps/platform/src/components/AssociationsToolkit/HeaderControls/RequiredControl.jsx +++ b/apps/platform/src/components/AssociationsToolkit/components/HeaderControls/RequiredControl.jsx @@ -1,8 +1,8 @@ import { useState, useEffect } from "react"; import { Checkbox } from "@mui/material"; import { styled } from "@mui/material/styles"; -import useAotfContext from "../hooks/useAotfContext"; -import { checkBoxPayload, getControlChecked } from "../utils"; +import useAotfContext from "../../hooks/useAotfContext"; +import { checkBoxPayload, getControlChecked } from "../../utils"; const OTCheckbox = styled(Checkbox)` padding: 0; diff --git a/apps/platform/src/components/AssociationsToolkit/HeaderControls/SliderControl.jsx b/apps/platform/src/components/AssociationsToolkit/components/HeaderControls/SliderControl.jsx similarity index 94% rename from apps/platform/src/components/AssociationsToolkit/HeaderControls/SliderControl.jsx rename to apps/platform/src/components/AssociationsToolkit/components/HeaderControls/SliderControl.jsx index 7ddca22b5..266f922fd 100644 --- a/apps/platform/src/components/AssociationsToolkit/HeaderControls/SliderControl.jsx +++ b/apps/platform/src/components/AssociationsToolkit/components/HeaderControls/SliderControl.jsx @@ -2,8 +2,8 @@ import { useState, useEffect } from "react"; import { Slider } from "@mui/material"; import { styled } from "@mui/material/styles"; -import useAotfContext from "../hooks/useAotfContext"; -import { getWightSourceDefault } from "../utils"; +import useAotfContext from "../../hooks/useAotfContext"; +import { getWightSourceDefault } from "../../utils"; const OTSlider = styled(Slider)({ root: { diff --git a/apps/platform/src/components/AssociationsToolkit/HeaderControls/index.ts b/apps/platform/src/components/AssociationsToolkit/components/HeaderControls/index.ts similarity index 100% rename from apps/platform/src/components/AssociationsToolkit/HeaderControls/index.ts rename to apps/platform/src/components/AssociationsToolkit/components/HeaderControls/index.ts diff --git a/apps/platform/src/components/AssociationsToolkit/SearchInput.jsx b/apps/platform/src/components/AssociationsToolkit/components/SearchInput.jsx similarity index 96% rename from apps/platform/src/components/AssociationsToolkit/SearchInput.jsx rename to apps/platform/src/components/AssociationsToolkit/components/SearchInput.jsx index 725d2e4b0..01a722a1f 100644 --- a/apps/platform/src/components/AssociationsToolkit/SearchInput.jsx +++ b/apps/platform/src/components/AssociationsToolkit/components/SearchInput.jsx @@ -4,7 +4,7 @@ import { styled } from "@mui/material/styles"; import { faXmark, faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useDebounce } from "ui"; -import useAotfContext from "./hooks/useAotfContext"; +import useAotfContext from "../hooks/useAotfContext"; const InputContainer = styled(Grid)({ marginRight: "15px", diff --git a/apps/platform/src/components/AssociationsToolkit/Table/AggregationsRow.jsx b/apps/platform/src/components/AssociationsToolkit/components/Table/AggregationsRow.jsx similarity index 92% rename from apps/platform/src/components/AssociationsToolkit/Table/AggregationsRow.jsx rename to apps/platform/src/components/AssociationsToolkit/components/Table/AggregationsRow.jsx index b4e0766b2..931038e83 100644 --- a/apps/platform/src/components/AssociationsToolkit/Table/AggregationsRow.jsx +++ b/apps/platform/src/components/AssociationsToolkit/components/Table/AggregationsRow.jsx @@ -2,9 +2,9 @@ import { useState } from "react"; import { styled, Grid } from "@mui/material"; import AggregationsTooltip from "./AssocTooltip"; -import associationsColumns from "../static_datasets/dataSourcesAssoc"; -import prioritizationColumns from "../static_datasets/prioritisationColumns"; -import { groupViewColumnsBy } from "../utils"; +import associationsColumns from "../../static_datasets/dataSourcesAssoc"; +import prioritizationColumns from "../../static_datasets/prioritisationColumns"; +import { groupViewColumnsBy } from "../../utils"; import { GridContainer } from "../layout"; const AggregationsContainer = styled(GridContainer)({ diff --git a/apps/platform/src/components/AssociationsToolkit/Table/AssocTooltip.jsx b/apps/platform/src/components/AssociationsToolkit/components/Table/AssocTooltip.jsx similarity index 100% rename from apps/platform/src/components/AssociationsToolkit/Table/AssocTooltip.jsx rename to apps/platform/src/components/AssociationsToolkit/components/Table/AssocTooltip.jsx diff --git a/apps/platform/src/components/AssociationsToolkit/Table/CellName.jsx b/apps/platform/src/components/AssociationsToolkit/components/Table/CellName.jsx similarity index 90% rename from apps/platform/src/components/AssociationsToolkit/Table/CellName.jsx rename to apps/platform/src/components/AssociationsToolkit/components/Table/CellName.jsx index 5d5bbf2c2..a6a69c7d9 100644 --- a/apps/platform/src/components/AssociationsToolkit/Table/CellName.jsx +++ b/apps/platform/src/components/AssociationsToolkit/components/Table/CellName.jsx @@ -10,6 +10,7 @@ import { Popover, Box, Fade, + Skeleton, } from "@mui/material"; import { faThumbTack, @@ -20,8 +21,8 @@ import { } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useHistory } from "react-router-dom"; -import useAotfContext from "../hooks/useAotfContext"; -import { ENTITIES } from "../utils"; +import useAotfContext from "../../hooks/useAotfContext"; +import { ENTITIES } from "../../utils"; import { grey } from "@mui/material/colors"; const StyledMenuItem = styled(MenuItem)({ @@ -73,7 +74,13 @@ const ContextMenuContainer = styled("div", { function CellName({ cell, colorScale }) { const history = useHistory(); const contextMenuRef = useRef(); - const { entityToGet, pinnedEntries, setPinnedEntries, id: currentEntityId } = useAotfContext(); + const { + entityToGet, + pinnedEntries, + setPinnedEntries, + id: currentEntityId, + handleSetInteractors, + } = useAotfContext(); const { loading } = cell.table.getState(); const name = cell.getValue(); const { id } = cell.row; @@ -119,7 +126,11 @@ function CellName({ cell, colorScale }) { history.push(evidenceURL); }; - if (loading) return null; + const loadingWidth = entityToGet === ENTITIES.TARGET ? 50 : 150; + const loadingMargin = entityToGet === ENTITIES.TARGET ? 12 : 2; + + if (loading) + return ; return ( @@ -174,14 +185,16 @@ function CellName({ cell, colorScale }) { Unpin {entityToGet} )} + {entityToGet === ENTITIES.TARGET && } {entityToGet === ENTITIES.TARGET && ( - Target network associations + Target interactors )} + diff --git a/apps/platform/src/components/AssociationsToolkit/Table/SectionRender.jsx b/apps/platform/src/components/AssociationsToolkit/components/Table/SectionRender.jsx similarity index 92% rename from apps/platform/src/components/AssociationsToolkit/Table/SectionRender.jsx rename to apps/platform/src/components/AssociationsToolkit/components/Table/SectionRender.jsx index b5aff630d..bcfa5ec80 100644 --- a/apps/platform/src/components/AssociationsToolkit/Table/SectionRender.jsx +++ b/apps/platform/src/components/AssociationsToolkit/components/Table/SectionRender.jsx @@ -1,10 +1,10 @@ import { Suspense } from "react"; import { styled } from "@mui/material/styles"; import { LoadingBackdrop } from "ui"; -import { ENTITIES } from "../utils"; +import { ENTITIES } from "../../utils"; -import targetSections from "../../../sections/targetSections"; -import evidenceSections from "../../../sections/evidenceSections"; +import targetSections from "../../../../sections/targetSections"; +import evidenceSections from "../../../../sections/evidenceSections"; const LoadingContainer = styled("div")({ margin: "25px 0", diff --git a/apps/platform/src/components/AssociationsToolkit/Table/TableAssociations.jsx b/apps/platform/src/components/AssociationsToolkit/components/Table/TableAssociations.jsx similarity index 80% rename from apps/platform/src/components/AssociationsToolkit/Table/TableAssociations.jsx rename to apps/platform/src/components/AssociationsToolkit/components/Table/TableAssociations.jsx index 8992edc58..206b238b4 100644 --- a/apps/platform/src/components/AssociationsToolkit/Table/TableAssociations.jsx +++ b/apps/platform/src/components/AssociationsToolkit/components/Table/TableAssociations.jsx @@ -9,27 +9,20 @@ import { import { styled, Skeleton, Typography, Box } from "@mui/material"; -import dataSourcesCols from "../static_datasets/dataSourcesAssoc"; -import prioritizationCols from "../static_datasets/prioritisationColumns"; +import dataSourcesCols from "../../static_datasets/dataSourcesAssoc"; +import prioritizationCols from "../../static_datasets/prioritisationColumns"; import AggregationsTooltip from "./AssocTooltip"; -import ColoredCell from "./ColoredCell"; +import TableCell from "./TableCell"; import HeaderControls from "../HeaderControls"; import CellName from "./CellName"; import TableHeader from "./TableHeader"; import TableFooter from "./TableFooter"; import TableBody from "./TableBody"; -import useAotfContext from "../hooks/useAotfContext"; +import useAotfContext from "../../hooks/useAotfContext"; -import { - DISPLAY_MODE, - ENTITIES, - cellHasValue, - getScale, - isPartnerPreview, - tableCSSVariables, -} from "../utils"; +import { cellHasValue, getScale, isPartnerPreview, tableCSSVariables } from "../../utils"; const TableElement = styled("main")({ maxWidth: "1600px", @@ -68,23 +61,19 @@ function getDatasources({ expanderHandler, displayedTable, colorScale }) { isPrivate, docsLink, cell: cell => { - const { prefix, loading } = cell.table.getState(); - if (loading) return ; const hasValue = cellHasValue(cell.getValue()); return hasValue ? ( - ) : ( - + ); }, }); @@ -110,6 +99,7 @@ function TableAssociations() { handleSortingChange, pinnedData, pinnedLoading, + pinnedEntries, } = useAotfContext(); const rowNameEntity = entity === "target" ? "name" : "approvedSymbol"; @@ -137,22 +127,17 @@ function TableAssociations() { columnHelper.accessor(row => row.score, { id: "score", header: Association Score, - cell: row => { - const { loading } = row.table.getState(); - if (loading) return ; - return ( - - - - ); - }, + cell: cell => ( + + + + ), }), ], }), @@ -226,14 +211,16 @@ function TableAssociations() { {/* Weights controlls */} -
- {/* BODY CONTENT */} + + {/* BODY CONTENT */} + {pinnedEntries.length > 0 && ( + )} + + {pinnedEntries.length > 0 && } - {pinnedData.length > 0 && } + - -
{/* FOOTER */} diff --git a/apps/platform/src/components/AssociationsToolkit/Table/TableBody.jsx b/apps/platform/src/components/AssociationsToolkit/components/Table/TableBody.jsx similarity index 73% rename from apps/platform/src/components/AssociationsToolkit/Table/TableBody.jsx rename to apps/platform/src/components/AssociationsToolkit/components/Table/TableBody.jsx index 8eb529ad2..5de0db81e 100644 --- a/apps/platform/src/components/AssociationsToolkit/Table/TableBody.jsx +++ b/apps/platform/src/components/AssociationsToolkit/components/Table/TableBody.jsx @@ -1,11 +1,14 @@ import { Fragment } from "react"; import { flexRender } from "@tanstack/react-table"; -import { ClickAwayListener, Fade, Box } from "@mui/material"; +import { ClickAwayListener, Fade, Box, Typography } from "@mui/material"; import { v1 } from "uuid"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons"; +import { grey } from "@mui/material/colors"; -import useAotfContext from "../hooks/useAotfContext"; +import useAotfContext from "../../hooks/useAotfContext"; -import { getCellId } from "../utils"; +import { getCellId } from "../../utils"; import { RowContainer, RowsContainer, TableBodyContent, GridContainer } from "../layout"; import { SectionRender, SectionRendererWrapper } from "./SectionRender"; @@ -30,17 +33,57 @@ function ExpandableContainer({ rowExpanded, isExpandedInTable, loading, children return {children}; } +function EmptyMessage() { + return ( + + + + + + No results found + + + Try adjust your search or filter to find what you looking for. + + + ); +} + function TableBody({ core, cols }) { const { id, entity, entityToGet, displayedTable, resetExpandler, expanded } = useAotfContext(); const { rows } = core.getRowModel(); - if (rows.length < 1) return null; + const { prefix, loading } = core.getState(); + + if (prefix === "pinned" && rows.length < 1) return null; + + if (rows.length < 1) return ; const flatCols = ["name", ...cols.map(c => c.id)]; const rowNameEntity = entity === "target" ? "name" : "approvedSymbol"; const highLevelHeaders = core.getHeaderGroups()[0].headers; - const { prefix, loading } = core.getState(); const isExpandedInTable = expanded[3] === prefix && flatCols.includes(expanded[1]); const handleClickAway = e => { @@ -56,7 +99,10 @@ function TableBody({ core, cols }) { {rows.map(row => ( - + 0} + rowExpanded={getRowActive(row, isExpandedInTable)} + > {highLevelHeaders.map(columnGroup => ( + prop !== "borderColor" && prop !== "backgroundColor" && prop !== "shape", +})(({ backgroundColor = "var(--background-color)", borderColor = "var(--grey-mid)", shape }) => ({ + background: backgroundColor.toString(), + border: `1px solid ${borderColor}`, + borderRadius: shape === "circular" ? "50%" : 0, + height: "24px", + width: "24px", + boxSizing: "border-box", + "&:hover": { + cursor: "pointer", + boxShadow: "0px 0px 3px 1px rgba(0, 0, 0, 0.35)", + }, + "@media only screen and (max-width: 1050px)": { + height: "20px", + width: "20px", + }, +})); + +const defaultCell = { + getValue: () => false, + table: { + getState: () => ({ prefix: "", loading: false }), + }, +}; + +function TableCell({ onClick, shape = "circular", cell = defaultCell, colorScale }) { + const { prefix, loading } = cell.table.getState(); + const cellValue = cell.getValue(); + const hasValue = cellHasValue(cellValue); + const borderColor = hasValue ? colorScale(cellValue) : "#e0dede"; + const backgroundColor = hasValue ? colorScale(cellValue) : "#fafafa"; + const onClickHandler = onClick ? () => onClick(cell, prefix) : () => ({}); + + if (loading) return ; + + if (!hasValue) + return ( + + + + ); + + const scoreText = `Score: ${cellValue.toFixed(2)}`; + + return ( + + + + ); +} + +export default TableCell; diff --git a/apps/platform/src/components/AssociationsToolkit/Table/TableFooter.jsx b/apps/platform/src/components/AssociationsToolkit/components/Table/TableFooter.jsx similarity index 87% rename from apps/platform/src/components/AssociationsToolkit/Table/TableFooter.jsx rename to apps/platform/src/components/AssociationsToolkit/components/Table/TableFooter.jsx index 1d23bb6d3..df4f1893d 100644 --- a/apps/platform/src/components/AssociationsToolkit/Table/TableFooter.jsx +++ b/apps/platform/src/components/AssociationsToolkit/components/Table/TableFooter.jsx @@ -1,8 +1,8 @@ import { useEffect } from "react"; import { Alert, TablePagination, Typography } from "@mui/material"; -import useAotfContext from "../hooks/useAotfContext"; -import ColoredCell from "./ColoredCell"; -import { getLegend } from "../utils"; +import useAotfContext from "../../hooks/useAotfContext"; +import TableCell from "./TableCell"; +import { getLegend } from "../../utils"; function TableFooter({ table }) { const { @@ -46,7 +46,7 @@ function TableFooter({ table }) { > No data - +
@@ -69,6 +69,12 @@ function TableFooter({ table }) { rowsPerPage={table.getState().pagination.pageSize} page={pagination.pageIndex} labelRowsPerPage="Associations per page" + backIconButtonProps={{ + disableFocusRipple: true, + }} + nextIconButtonProps={{ + disableFocusRipple: true, + }} onPageChange={(e, index) => { if (!loading) { table.setPageIndex(index); diff --git a/apps/platform/src/components/AssociationsToolkit/Table/TableHeader.jsx b/apps/platform/src/components/AssociationsToolkit/components/Table/TableHeader.jsx similarity index 98% rename from apps/platform/src/components/AssociationsToolkit/Table/TableHeader.jsx rename to apps/platform/src/components/AssociationsToolkit/components/Table/TableHeader.jsx index 5890de2d5..c93acaa43 100644 --- a/apps/platform/src/components/AssociationsToolkit/Table/TableHeader.jsx +++ b/apps/platform/src/components/AssociationsToolkit/components/Table/TableHeader.jsx @@ -5,7 +5,7 @@ import { faArrowDownWideShort, faBook, faLock } from "@fortawesome/free-solid-sv import { Grid } from "@mui/material"; import AggregationsRow from "./AggregationsRow"; -import useAotfContext from "../hooks/useAotfContext"; +import useAotfContext from "../../hooks/useAotfContext"; import { GridContainer } from "../layout"; const getHeaderContainerClassName = ({ id }) => { diff --git a/apps/platform/src/components/AssociationsToolkit/TargetPrioritisationSwitch.jsx b/apps/platform/src/components/AssociationsToolkit/components/TargetPrioritisationSwitch.jsx similarity index 66% rename from apps/platform/src/components/AssociationsToolkit/TargetPrioritisationSwitch.jsx rename to apps/platform/src/components/AssociationsToolkit/components/TargetPrioritisationSwitch.jsx index e1e95bc16..d07201ede 100644 --- a/apps/platform/src/components/AssociationsToolkit/TargetPrioritisationSwitch.jsx +++ b/apps/platform/src/components/AssociationsToolkit/components/TargetPrioritisationSwitch.jsx @@ -1,8 +1,8 @@ import ToggleButton from "@mui/material/ToggleButton"; import ToggleButtonGroup from "@mui/material/ToggleButtonGroup"; -import { DISPLAY_MODE } from "./utils"; -import useAotfContext from "./hooks/useAotfContext"; +import { DISPLAY_MODE } from "../utils"; +import useAotfContext from "../hooks/useAotfContext"; function TargetPrioritisationSwitch() { const { displayedTable, setDisplayedTable } = useAotfContext(); @@ -24,10 +24,14 @@ function TargetPrioritisationSwitch() { exclusive onChange={handleChange} aria-label="Visualizations" - size="small" + size="large" > - Target-disease association - Target prioritisation factors + + Target-disease association + + + Target prioritisation factors + ); } diff --git a/apps/platform/src/components/AssociationsToolkit/layout.jsx b/apps/platform/src/components/AssociationsToolkit/components/layout.tsx similarity index 78% rename from apps/platform/src/components/AssociationsToolkit/layout.jsx rename to apps/platform/src/components/AssociationsToolkit/components/layout.tsx index 97367943f..16d4425a9 100644 --- a/apps/platform/src/components/AssociationsToolkit/layout.jsx +++ b/apps/platform/src/components/AssociationsToolkit/components/layout.tsx @@ -10,14 +10,12 @@ const LoadingContainer = styled("div")({ gap: "15px", }); -export function AotFLoader() { - return ( - - - Loading associations - - ); -} +export const AotFLoader: React.FC = () => ( + + + Loading associations + +); const baseGridContainerStyles = { display: "grid", @@ -30,9 +28,13 @@ const baseGridContainerStyles = { const boxShadow = "0px 3px 15px -3px rgba(0,0,0,0.1)"; +type GridContainerProps = { + columnsCount: number; +}; + export const GridContainer = styled("div", { shouldForwardProp: prop => prop !== "columnsCount", -})(({ columnsCount }) => ({ +})(({ columnsCount }) => ({ ...baseGridContainerStyles, gridTemplateColumns: `repeat(${columnsCount}, 1fr)`, })); @@ -40,7 +42,7 @@ export const GridContainer = styled("div", { export const TableBodyContent = styled("div")({ display: "flex", flexDirection: "column", - margin: "6px 0", + margin: "0", }); export const RowsContainer = styled("div")({ @@ -48,9 +50,14 @@ export const RowsContainer = styled("div")({ flexDirection: "column", }); +type RowContainerProps = { + rowExpanded: boolean; + isSubRow: boolean; +}; + export const RowContainer = styled("div", { - shouldForwardProp: prop => prop !== "rowExpanded", -})(({ rowExpanded }) => ({ + shouldForwardProp: prop => prop !== "rowExpanded" && prop !== "isSubRow", +})(({ rowExpanded, isSubRow }) => ({ top: "148px", position: rowExpanded ? "sticky" : "initial", padding: rowExpanded ? "0.1em 0 0.1em 0" : "0.1em 0 0.1em 0", @@ -77,7 +84,7 @@ export const ControlsSection = styled("section")` margin-bottom: 30px; display: flex; justify-content: space-between; - align-items: center; + align-items: start; flex-wrap: wrap; `; diff --git a/apps/platform/src/components/AssociationsToolkit/context/AssociationsStateContext.jsx b/apps/platform/src/components/AssociationsToolkit/context/AssociationsStateContext.jsx index 654090cb4..70885a118 100644 --- a/apps/platform/src/components/AssociationsToolkit/context/AssociationsStateContext.jsx +++ b/apps/platform/src/components/AssociationsToolkit/context/AssociationsStateContext.jsx @@ -1,4 +1,4 @@ -import { createContext, useState, useMemo, useEffect } from "react"; +import { createContext, useState, useMemo, useEffect, useReducer, useRef } from "react"; import { isEqual } from "lodash"; import { useStateParams } from "ui"; import dataSources from "../static_datasets/dataSourcesAssoc"; @@ -8,28 +8,41 @@ import { getCellId, checkBoxPayload, ENTITIES, - DEFAULT_TABLE_PAGINATION_STATE, DEFAULT_TABLE_SORTING_STATE, DISPLAY_MODE, } from "../utils"; import useAssociationsData from "../hooks/useAssociationsData"; +import { aotfReducer, createInitialState } from "./aotfReducer"; +import { onPaginationChange, resetPagination } from "./aotfActions"; const AssociationsStateContext = createContext(); const initialIndirect = entity => entity !== ENTITIES.TARGET; +/** + * Associations on the fly state Provider + */ function AssociationsStateProvider({ children, entity, id, query }) { - const [{ pageIndex, pageSize }, setPagination] = useState(DEFAULT_TABLE_PAGINATION_STATE); - - const pagination = useMemo( - () => ({ - pageIndex, - pageSize, - }), - [pageIndex, pageSize] + const [state, dispatch] = useReducer( + aotfReducer, + { query, parentEntity: entity, parentId: id }, + createInitialState ); + const hasComponentBeenRender = useRef(false); + + useEffect(() => { + if (hasComponentBeenRender.current) { + resetDatasourceControls(); + setPinExpanded([]); + setActiveHeadersControlls(false); + setSorting(DEFAULT_TABLE_SORTING_STATE); + dispatch(resetPagination()); + } + hasComponentBeenRender.current = true; + }, [id]); + // Table Controls // [rowId, columnId, codebaseSectionId, tablePrefix] // eg. ['ENSG00000087085', 'hasHighQualityChemicalProbes', 'chemicalProbes', 'pinned'] @@ -37,6 +50,7 @@ function AssociationsStateProvider({ children, entity, id, query }) { const [pinExpanded, setPinExpanded] = useState([]); const [tableExpanded, setTableExpanded] = useState({}); const [tablePinExpanded, setTablePinExpanded] = useState({}); + const [facetFilterIds, setFacetFilterIds] = useState([]); // Data controls const [enableIndirect, setEnableIndirect] = useState(initialIndirect(entity)); @@ -68,14 +82,15 @@ function AssociationsStateProvider({ children, entity, id, query }) { query, options: { id, - index: pageIndex, - size: pageSize, + index: state.pagination.pageIndex, + size: state.pagination.pageSize, filter: searhFilter, sortBy: sorting[0].id, enableIndirect, datasources: dataSourcesWeights, entity, aggregationFilters: dataSourcesRequired, + facetFilters: facetFilterIds, }, }); @@ -95,6 +110,7 @@ function AssociationsStateProvider({ children, entity, id, query }) { datasources: dataSourcesWeights, aggregationFilters: dataSourcesRequired, rowsFilter: pinnedEntries.toSorted(), + facetFilters: facetFilterIds, }, }); @@ -140,13 +156,12 @@ function AssociationsStateProvider({ children, entity, id, query }) { const resetToInitialPagination = () => { setTableExpanded({}); setExpanded([]); - setPagination(DEFAULT_TABLE_PAGINATION_STATE); + dispatch(resetPagination()); }; - const handlePaginationChange = newPagination => { - setTableExpanded({}); - setExpanded([]); - setPagination(newPagination); + const handlePaginationChange = updater => { + const newPagination = updater(state.pagination); + dispatch(onPaginationChange(newPagination)); }; const handleSortingChange = newSortingFunc => { @@ -160,7 +175,6 @@ function AssociationsStateProvider({ children, entity, id, query }) { const handleSearchInputChange = newSearchFilter => { if (newSearchFilter !== searhFilter) { - setPagination(DEFAULT_TABLE_PAGINATION_STATE); setSearhFilter(newSearchFilter); } }; @@ -196,6 +210,7 @@ function AssociationsStateProvider({ children, entity, id, query }) { const contextVariables = useMemo( () => ({ + dispatch, query, id, entity, @@ -205,7 +220,7 @@ function AssociationsStateProvider({ children, entity, id, query }) { loading, initialLoading, tableExpanded, - pagination, + pagination: state.pagination, expanded, activeHeadersControlls, enableIndirect, @@ -223,6 +238,7 @@ function AssociationsStateProvider({ children, entity, id, query }) { pinnedCount, pinExpanded, pinnedEntries, + facetFilterIds, handleActiveRow, resetToInitialPagination, setPinnedEntries, @@ -241,8 +257,11 @@ function AssociationsStateProvider({ children, entity, id, query }) { setActiveHeadersControlls, resetExpandler, handleAggregationClick, + setFacetFilterIds, + state, }), [ + dispatch, activeHeadersControlls, count, data, @@ -262,7 +281,7 @@ function AssociationsStateProvider({ children, entity, id, query }) { initialLoading, loading, modifiedSourcesDataControls, - pagination, + state, pinExpanded, pinnedCount, pinnedData, @@ -276,6 +295,8 @@ function AssociationsStateProvider({ children, entity, id, query }) { sorting, tableExpanded, tablePinExpanded, + facetFilterIds, + setFacetFilterIds, ] ); diff --git a/apps/platform/src/components/AssociationsToolkit/context/aotfActions.ts b/apps/platform/src/components/AssociationsToolkit/context/aotfActions.ts new file mode 100644 index 000000000..55710addf --- /dev/null +++ b/apps/platform/src/components/AssociationsToolkit/context/aotfActions.ts @@ -0,0 +1,21 @@ +import { Action, ActionType, Pagination, RowInteractorsKey } from "../types"; + +export function onPaginationChange(pagination: Pagination): Action { + return { + type: ActionType.PAGINATE, + pagination: pagination, + }; +} + +export function resetPagination(): Action { + return { + type: ActionType.RESET_PAGINATION, + }; +} + +export function setInteractors(id: RowInteractorsKey, source: string): Action { + return { + type: ActionType.SET_INTERACTORS, + payload: { id, source }, + }; +} diff --git a/apps/platform/src/components/AssociationsToolkit/context/aotfReducer.ts b/apps/platform/src/components/AssociationsToolkit/context/aotfReducer.ts new file mode 100644 index 000000000..806213039 --- /dev/null +++ b/apps/platform/src/components/AssociationsToolkit/context/aotfReducer.ts @@ -0,0 +1,94 @@ +import { DocumentNode } from "graphql"; +import { DEFAULT_TABLE_PAGINATION_STATE, DEFAULT_TABLE_SORTING_STATE } from "../utils"; +import { Action, ActionType, ENTITY, State, TABLE_VIEW } from "../types"; + +/***************** + * INITIAL STATE * + *****************/ + +export const initialState: State = { + pagination: DEFAULT_TABLE_PAGINATION_STATE, + loading: false, + query: null, + parentId: "", + enableIndirect: false, + sorting: DEFAULT_TABLE_SORTING_STATE, + parentEntity: null, // TODO: review initial state + rowEntity: null, + tableView: TABLE_VIEW.MAIN, + isMainView: true, + searchFilter: "", + advanceOptionsOpen: false, + pinnedEntities: [], + bodyData: [], + pinnedData: [], + interactors: new Map(), +}; + +type InitialStateParams = { + parentEntity: ENTITY; + parentId: string; + query: DocumentNode; +}; + +export function createInitialState({ parentEntity, parentId, query }: InitialStateParams): State { + const rowEntity = parentEntity === ENTITY.TARGET ? ENTITY.DISEASE : ENTITY.TARGET; + const state = { ...initialState, query, parentId, parentEntity, rowEntity }; + return state; +} + +export function aotfReducer(state: State = initialState, action: Action): State { + if (typeof state === undefined) { + throw Error("State provied to aotfReducer is undefined"); + } + switch (action.type) { + case ActionType.PAGINATE: { + return { + ...state, + pagination: action.pagination, + }; + } + case ActionType.RESET_PAGINATION: { + return { + ...state, + pagination: DEFAULT_TABLE_PAGINATION_STATE, + }; + } + case ActionType.SORTING: { + return { + ...state, + sorting: action.sorting, + }; + } + case ActionType.TEXT_SEARCH: { + return { + ...state, + pagination: DEFAULT_TABLE_PAGINATION_STATE, + searchFilter: action.searchFilter, + }; + } + case ActionType.SET_INTERACTORS: { + const currentInteractors = state.interactors; + if (typeof currentInteractors === "undefined" || !currentInteractors) return { ...state }; + const payloadInteractor = action.payload; + + // Todo: review + if (currentInteractors.has(payloadInteractor.id)) { + const row = currentInteractors.get(payloadInteractor.id); + row?.push(payloadInteractor.source); + currentInteractors.set(payloadInteractor.id, row); + } + + currentInteractors.set(payloadInteractor.id, [payloadInteractor.source]); + + return { + ...state, + interactors: currentInteractors, + }; + } + default: { + throw Error("Unknown action: " + action); + return state; + } + } +} diff --git a/apps/platform/src/components/AssociationsToolkit/hooks/useAssociationsData.js b/apps/platform/src/components/AssociationsToolkit/hooks/useAssociationsData.js index ecf48f478..af0960fb4 100644 --- a/apps/platform/src/components/AssociationsToolkit/hooks/useAssociationsData.js +++ b/apps/platform/src/components/AssociationsToolkit/hooks/useAssociationsData.js @@ -2,6 +2,16 @@ import { useEffect, useState } from "react"; import client from "../../../client"; import { ENTITIES } from "../utils"; +import { v1 } from "uuid"; + +const INITIAL_ROW_COUNT = 30; + +const getEmptyRow = () => ({ + dataSources: {}, + score: 0, + disease: { id: v1() }, + target: { id: v1() }, +}); /*********** * HELPERS * @@ -43,7 +53,7 @@ const getDataRowMetadata = (parentEntity, row, fixedEntity) => { default: return { targetSymbol, diseaseName }; } - return { targetSymbol, diseaseName }; + return { targetSymbol, diseaseName, id }; }; const getAllDataCount = (fixedEntity, data) => { @@ -107,10 +117,19 @@ const getAssociatedTargetsData = data => { }); }; +//TODO: review +const getInitialLoadingData = () => { + const arr = []; + for (let i = 0; i < INITIAL_ROW_COUNT; i++) { + arr.push(getEmptyRow()); + } + return arr; +}; + const INITIAL_USE_ASSOCIATION_STATE = { - loading: false, + loading: true, error: false, - data: [], + data: getInitialLoadingData(), initialLoading: true, count: 0, }; @@ -128,16 +147,16 @@ function useAssociationsData({ sortBy = "score", aggregationFilters = [], enableIndirect = false, - datasources = null, + datasources = [], rowsFilter = [], entity, + facetFilters = [], }, }) { const [state, setState] = useState(INITIAL_USE_ASSOCIATION_STATE); useEffect(() => { let isCurrent = true; - setState({ ...state, loading: true }); const fetchData = async () => { const resData = await client.query({ query, @@ -154,10 +173,12 @@ function useAssociationsData({ name: el.name, path: el.path, })), + facetFilters, }, }); const parsedData = getAssociationsData(entity, resData.data); const dataCount = getAllDataCount(entity, resData.data); + setState({ count: dataCount, data: parsedData, @@ -178,6 +199,7 @@ function useAssociationsData({ query, entity, aggregationFilters, + facetFilters, ]); return state; diff --git a/apps/platform/src/components/AssociationsToolkit/index.js b/apps/platform/src/components/AssociationsToolkit/index.js deleted file mode 100644 index 6148888a2..000000000 --- a/apps/platform/src/components/AssociationsToolkit/index.js +++ /dev/null @@ -1,13 +0,0 @@ -export { default as AdvanceOptionsMenu } from "./AdvanceOptionsMenu"; -export { default as TableAssociations } from "./Table/TableAssociations"; -export { default as TargetPrioritisationSwitch } from "./TargetPrioritisationSwitch"; -export { default as SearhInput } from "./SearchInput"; -export { default as DataDownloader } from "./DataDownloader"; -export { default as DataUploader } from "./DataUploader/DataUploader"; -export { default as AotfApiPlayground } from "./AotfApiPlayground"; -export { - default as AssociationsContext, - AssociationsStateProvider as AssociationsProvider, -} from "./context/AssociationsStateContext"; -export { default as useAotfContext } from "./hooks/useAotfContext"; -export * from "./layout"; diff --git a/apps/platform/src/components/AssociationsToolkit/index.ts b/apps/platform/src/components/AssociationsToolkit/index.ts new file mode 100644 index 000000000..747d4d9f7 --- /dev/null +++ b/apps/platform/src/components/AssociationsToolkit/index.ts @@ -0,0 +1,13 @@ +export { default as AdvanceOptionsMenu } from "./components/AdvanceOptionsMenu"; +export { default as TableAssociations } from "./components/Table/TableAssociations"; +export { default as TargetPrioritisationSwitch } from "./components/TargetPrioritisationSwitch"; +export { default as DataDownloader } from "./components/DataDownloader"; +export { default as DataUploader } from "./components/DataUploader/DataUploader"; +export { default as SearchInput } from "./components/SearchInput"; +export { default as AotfApiPlayground } from "./components/AotfApiPlayground"; +export { + default as AssociationsContext, + AssociationsStateProvider as AssociationsProvider, +} from "./context/AssociationsStateContext"; +export { default as useAotfContext } from "./hooks/useAotfContext"; +export * from "./components/layout"; diff --git a/apps/platform/src/components/AssociationsToolkit/static_datasets/prioritisationColumns.ts b/apps/platform/src/components/AssociationsToolkit/static_datasets/prioritisationColumns.ts index df274eb75..a6f36e34c 100644 --- a/apps/platform/src/components/AssociationsToolkit/static_datasets/prioritisationColumns.ts +++ b/apps/platform/src/components/AssociationsToolkit/static_datasets/prioritisationColumns.ts @@ -1,38 +1,9 @@ -/********* - * TYPES * - *********/ -type Aggregation = "Precedence" | "Tractability" | "Doability" | "Safety"; +import { Column, TargetPrioritisationAggregation } from "../types"; -type Aggregations = { - precedence: Aggregation; - tractability: Aggregation; - doability: Aggregation; - safety: Aggregation; -}; - -type Column = { - id: string; - label: string; - aggregation: Aggregation; - sectionId: string; - description: string; - docsLink: string; -}; - -const aggregations: Aggregations = { - precedence: "Precedence", - tractability: "Tractability", - doability: "Doability", - safety: "Safety", -}; - -/*********** - * COLUMNS * - ***********/ const maxClinicalTrialPhase: Column = { id: "maxClinicalTrialPhase", label: "Target in clinic", - aggregation: aggregations.precedence, + aggregation: TargetPrioritisationAggregation.PRECEDENCE, sectionId: "knownDrugs", description: "Target is in clinical trials for any indication", docsLink: "https://platform-docs.opentargets.org/target-prioritisation#target-in-clinic", @@ -41,7 +12,7 @@ const maxClinicalTrialPhase: Column = { const isInMembrane: Column = { id: "isInMembrane", label: "Membrane protein", - aggregation: aggregations.tractability, + aggregation: TargetPrioritisationAggregation.TRACTABILITY, sectionId: "subcellularLocation", description: "Target is annotated to be located in the cell membrane", docsLink: "https://platform-docs.opentargets.org/target-prioritisation#membrane-protein", @@ -50,7 +21,7 @@ const isInMembrane: Column = { const isSecreted: Column = { id: "isSecreted", label: "Secreted protein", - aggregation: aggregations.tractability, + aggregation: TargetPrioritisationAggregation.TRACTABILITY, sectionId: "subcellularLocation", description: "Target is annotated to be secreted", docsLink: "https://platform-docs.opentargets.org/target-prioritisation#secreted-protein", @@ -59,7 +30,7 @@ const isSecreted: Column = { const hasLigand: Column = { id: "hasLigand", label: "Ligand binder", - aggregation: aggregations.tractability, + aggregation: TargetPrioritisationAggregation.TRACTABILITY, sectionId: "tractability", description: "Target binds a specific ligand", docsLink: "https://platform-docs.opentargets.org/target-prioritisation#ligand-binder", @@ -68,7 +39,7 @@ const hasLigand: Column = { const hasSmallMoleculeBinder: Column = { id: "hasSmallMoleculeBinder", label: "Small molecule binder", - aggregation: aggregations.tractability, + aggregation: TargetPrioritisationAggregation.TRACTABILITY, sectionId: "tractability", description: "Target binds a small molecule", docsLink: "https://platform-docs.opentargets.org/target-prioritisation#small-molecule-binder", @@ -77,7 +48,7 @@ const hasSmallMoleculeBinder: Column = { const hasPocket: Column = { id: "hasPocket", label: "Predicted pockets", - aggregation: aggregations.tractability, + aggregation: TargetPrioritisationAggregation.TRACTABILITY, sectionId: "tractability", description: "Target has predicted pockets", docsLink: "https://platform-docs.opentargets.org/target-prioritisation#predicted-pockets", @@ -86,7 +57,7 @@ const hasPocket: Column = { const mouseOrthologMaxIdentityPercentage: Column = { id: "mouseOrthologMaxIdentityPercentage", label: "Mouse ortholog identity", - aggregation: aggregations.doability, + aggregation: TargetPrioritisationAggregation.DOABILITY, sectionId: "compGenomics", description: "Mouse ortholog maximum identity percentage", docsLink: "https://platform-docs.opentargets.org/target-prioritisation#mouse-ortholog-identity", @@ -95,7 +66,7 @@ const mouseOrthologMaxIdentityPercentage: Column = { const hasHighQualityChemicalProbes: Column = { id: "hasHighQualityChemicalProbes", label: "Chemical probes", - aggregation: aggregations.doability, + aggregation: TargetPrioritisationAggregation.DOABILITY, sectionId: "chemicalProbes", description: "Availability of high quality chemical probes for the target", docsLink: "https://platform-docs.opentargets.org/target-prioritisation#chemical-probes", @@ -104,7 +75,7 @@ const hasHighQualityChemicalProbes: Column = { const mouseKOScore: Column = { id: "mouseKOScore", label: "Mouse models", - aggregation: aggregations.safety, + aggregation: TargetPrioritisationAggregation.SAFETY, sectionId: "mousePhenotypes", description: "Availability of mouse knockout models for the target", docsLink: "https://platform-docs.opentargets.org/target-prioritisation#mouse-models", @@ -113,7 +84,7 @@ const mouseKOScore: Column = { const geneticConstraint: Column = { id: "geneticConstraint", label: "Genetic constraint", - aggregation: aggregations.safety, + aggregation: TargetPrioritisationAggregation.SAFETY, sectionId: "geneticConstraint", description: "Relative genetic constraint in natural populations derived from GnomAD", docsLink: "https://platform-docs.opentargets.org/target-prioritisation#genetic-constraint", @@ -122,7 +93,7 @@ const geneticConstraint: Column = { const geneEssentiality: Column = { id: "geneEssentiality", label: "Gene essentiality", - aggregation: aggregations.safety, + aggregation: TargetPrioritisationAggregation.SAFETY, sectionId: "depMapEssentiality", description: "Gene is defined as core essential by the DepMap portal", docsLink: "https://platform-docs.opentargets.org/target-prioritisation#gene-essentiality", @@ -131,7 +102,7 @@ const geneEssentiality: Column = { const hasSafetyEvent: Column = { id: "hasSafetyEvent", label: "Known safety events", - aggregation: aggregations.safety, + aggregation: TargetPrioritisationAggregation.SAFETY, sectionId: "safety", description: "Target associated with a curated adverse event", docsLink: "https://platform-docs.opentargets.org/target-prioritisation#known-adverse-events", @@ -140,7 +111,7 @@ const hasSafetyEvent: Column = { const isCancerDriverGene: Column = { id: "isCancerDriverGene", label: "Cancer driver gene", - aggregation: aggregations.safety, + aggregation: TargetPrioritisationAggregation.SAFETY, sectionId: "cancerHallmarks", description: "Target is classified as an Oncogene and/or Tumor Suppressor Gene", docsLink: "https://platform-docs.opentargets.org/target-prioritisation#cancer-driver-gene", @@ -149,7 +120,7 @@ const isCancerDriverGene: Column = { const paralogMaxIdentityPercentage: Column = { id: "paralogMaxIdentityPercentage", label: "Paralogues", - aggregation: aggregations.safety, + aggregation: TargetPrioritisationAggregation.SAFETY, sectionId: "compGenomics", description: "Paralog maximum identity percentage", docsLink: "https://platform-docs.opentargets.org/target-prioritisation#paralogues", @@ -158,7 +129,7 @@ const paralogMaxIdentityPercentage: Column = { const tissueSpecificity: Column = { id: "tissueSpecificity", label: "Tissue specificity", - aggregation: aggregations.safety, + aggregation: TargetPrioritisationAggregation.SAFETY, sectionId: "expressions", description: "HPA category types of elevated expression across tissues for the target", docsLink: "https://platform-docs.opentargets.org/target-prioritisation#tissue-specificity", @@ -167,7 +138,7 @@ const tissueSpecificity: Column = { const tissueDistribution: Column = { id: "tissueDistribution", label: "Tissue distribution", - aggregation: aggregations.safety, + aggregation: TargetPrioritisationAggregation.SAFETY, sectionId: "expressions", description: "HPA category types of detectable expression across tissues for the target", docsLink: "https://platform-docs.opentargets.org/target-prioritisation#tissue-distribution", diff --git a/apps/platform/src/components/AssociationsToolkit/types.ts b/apps/platform/src/components/AssociationsToolkit/types.ts new file mode 100644 index 000000000..525055dfc --- /dev/null +++ b/apps/platform/src/components/AssociationsToolkit/types.ts @@ -0,0 +1,87 @@ +import { DocumentNode } from "graphql"; + +export enum ENTITY { + TARGET = "target", + DISEASE = "disease", + DRUG = "drug", +} + +export enum TargetPrioritisationAggregation { + PRECEDENCE = "precedence", + TRACTABILITY = "tractability", + DOABILITY = "doability", + SAFETY = "safety", +} + +export type Column = { + id: string; + label: string; + aggregation: TargetPrioritisationAggregation; + sectionId: string; + description: string; + docsLink: string; + weight?: number | undefined; + private?: boolean; +}; + +/*************** + * STATE TYPES * + ***************/ + +export type Pagination = { pageIndex: number; pageSize: number }; +export enum TABLE_VIEW { + MAIN = "MAIN", + PRIORITISATION = "PRIORITISATION", +} +export type Sorting = { id: string; desc: boolean }[]; + +export type Data = [any] | []; // TODO: create data type (list of disease || target) + +export type RowInteractors = string[]; +export type RowInteractorsKey = string; + +export type Interactors = Map; + +export interface State { + sorting: Sorting; + loading: boolean; // TODO: more loaders? + enableIndirect: boolean; + query: DocumentNode | null; + pagination: Pagination; + parentId: string; + parentEntity: ENTITY | null; + rowEntity: ENTITY | null; + tableView: TABLE_VIEW.MAIN | TABLE_VIEW.PRIORITISATION; + searchFilter: string; + pinnedEntities: string[]; + advanceOptionsOpen: boolean; + isMainView: boolean; + bodyData: Data; + pinnedData: Data; + interactors: Interactors; +} + +/***************** + * ACTIONS TYPES * + *****************/ + +export enum ActionType { + PAGINATE = "PAGINATE", + SORTING = "SORTING", + TEXT_SEARCH = "TEXT_SEARCH", + SET_INTERACTORS = "SET_INTERACTORS", + RESET_PAGINATION = "RESET_PAGINATION", +} + +export type SetRowInteractorsPayload = { + id: RowInteractorsKey; + source: string; +}; + +export type Action = + | { type: ActionType.PAGINATE; pagination: Pagination } + | { type: ActionType.SORTING; sorting: Sorting } + | { type: ActionType.TEXT_SEARCH; searchFilter: string } + | { type: ActionType.PAGINATE; pagination: Pagination } + | { type: ActionType.SET_INTERACTORS; payload: SetRowInteractorsPayload } + | { type: ActionType.RESET_PAGINATION }; diff --git a/apps/platform/src/components/AssociationsToolkit/utils/index.js b/apps/platform/src/components/AssociationsToolkit/utils/index.js index c3b058df5..7db817f28 100644 --- a/apps/platform/src/components/AssociationsToolkit/utils/index.js +++ b/apps/platform/src/components/AssociationsToolkit/utils/index.js @@ -17,7 +17,7 @@ export const DEFAULT_TABLE_PAGINATION_STATE = { pageSize: DEFAULT_TABLE_PAGE_SIZE, }; -export const DEFAULT_TABLE_SORTING_STATE = [{ id: 'score', desc: true }]; +export const DEFAULT_TABLE_SORTING_STATE = [{ id: "score", desc: true }]; export const DISPLAY_MODE = { PRIORITISATION: "prioritisations", @@ -144,20 +144,21 @@ export const getScale = isAssoc => (isAssoc ? assocScale : prioritizationScale); /* --- CSS VARIABLES --- */ export const tableCSSVariables = { + "--primary-color": primaryColor, "--grey-lighter": "#f6f6f6", "--grey-light": "#ececec", - "--grey-mid": "#b8b8b8", - "--primary-color": primaryColor, + "--grey-mid": "#e0dede", + "--grey-dark": "#b8b8b8", + "--background-color": "#fafafa", "--text-color": "#5A5F5F", - "--aggregations-background-color": "var(--grey-light)", - "--aggregations-border-color": "var(--grey-mid)", - "--header-border-color": "var(--grey-light)", - // '--aggregations-color': 'var(--grey-mid)', - "--entities-border-color": "var(--grey-light)", "--table-header-min-width": "120px", "--table-header-max-width": "160px", "--table-left-column-width": "260px", - "--table-footer-border-color": "var(--grey-light)", "--row-hover-color": "var(--grey-light)", + "--aggregations-background-color": "var(--grey-light)", + "--aggregations-border-color": "var(--grey-dark)", + "--header-border-color": "var(--grey-light)", + "--entities-border-color": "var(--grey-light)", + "--table-footer-border-color": "var(--grey-light)", "--colums-controls-color": "var(--grey-lighter)", }; diff --git a/apps/platform/src/components/Facets/FacetsQuery.gql b/apps/platform/src/components/Facets/FacetsQuery.gql new file mode 100644 index 000000000..25f7d641f --- /dev/null +++ b/apps/platform/src/components/Facets/FacetsQuery.gql @@ -0,0 +1,13 @@ +query FacetSearchQuery($queryString: String!, $entityNames: [String!]) { + facets(queryString: $queryString, entityNames: $entityNames) { + hits { + id + highlights + label + category + entityIds + score + } + total + } +} diff --git a/apps/platform/src/components/Facets/FacetsSearch.tsx b/apps/platform/src/components/Facets/FacetsSearch.tsx new file mode 100644 index 000000000..71c645637 --- /dev/null +++ b/apps/platform/src/components/Facets/FacetsSearch.tsx @@ -0,0 +1,200 @@ +import { + Autocomplete, + Box, + Chip, + MenuItem, + Select, + SelectChangeEvent, + TextField, + Typography, +} from "@mui/material"; +import { ReactElement, useEffect, useState } from "react"; +import { Tooltip, useDebounce } from "ui"; + +import FACETS_SEARCH_QUERY from "./FacetsQuery.gql"; +import useAotfContext from "../AssociationsToolkit/hooks/useAotfContext"; +import client from "../../client"; +import FacetsSuggestion from "./FacetsSuggestion"; + +const TARGET_CATEGORIES = { + "All Categories": "All", + Names: "Approved Name", + Symbol: "Approved Symbol", + "ChEMBL Target Class": "ChEMBL Target Class", + "GO:BP": "GO:BP", + "GO:CC": "GO:CC", + "GO:MF": "GO:MF", + Reactome: "Reactome", + "Subcellular Location": "Subcellular Location", + "Target ID": "Target ID", + "Tractability Antibody": "Tractability Antibody", + "Tractability Other Modalities": "Tractability Other Modalities", + "Tractability PROTAC": "Tractability PROTAC", + "Tractability Small Molecule": "Tractability Small Molecule", +}; + +const DISEASE_CATEGORIES = { + "All Categories": "All", + Disease: "Disease", + "Therapeutic Area": "Therapeutic Area", +}; + +function FacetsSearch(): ReactElement { + const { entityToGet, setFacetFilterIds } = useAotfContext(); + const [inputValue, setInputValue] = useState(""); + const debouncedInputValue = useDebounce(inputValue, 400); + const [dataOptions, setDataOptions] = useState([]); + const [loading, setLoading] = useState(false); + const CATEGORIES = entityToGet === "disease" ? DISEASE_CATEGORIES : TARGET_CATEGORIES; + const [categoryFilterValue, setCategoryFilterValue] = useState(CATEGORIES["All Categories"]); + + async function getFacetsData() { + setDataOptions([]); + setLoading(true); + + const variables = { + queryString: inputValue, + entityNames: [entityToGet], + }; + + const resData = await client.query({ + query: FACETS_SEARCH_QUERY, + variables, + }); + + const filteredData = resData.data.facets.hits.filter( + e => + e.category === categoryFilterValue || categoryFilterValue === CATEGORIES["All Categories"] + ); + + setDataOptions(filteredData); + setLoading(false); + } + + useEffect(() => { + if (inputValue) getFacetsData(); + else setDataOptions([]); + }, [debouncedInputValue]); + + return ( + + + } + size="small" + loading={loading} + limitTags={2} + onChange={(event: any, newValue: any) => { + setFacetFilterIds(newValue.map(v => v.id)); + }} + filterOptions={x => x} + getOptionLabel={option => option?.label} + onInputChange={(event, newInputValue) => { + setInputValue(newInputValue); + }} + renderInput={params => ( + + )} + renderTags={(value: readonly string[], getTagProps) => + value.map((option: any, index: number) => ( + + + + + + )) + } + renderOption={(props, option) => { + const { category, highlights } = option; + return ( +
  • + + + + + + theme.palette.primary.main, + }} + > + in {category} + + + +
  • + ); + }} + /> +
    + ); +} +export default FacetsSearch; diff --git a/apps/platform/src/components/Facets/FacetsSuggestion.tsx b/apps/platform/src/components/Facets/FacetsSuggestion.tsx new file mode 100644 index 000000000..415d2635f --- /dev/null +++ b/apps/platform/src/components/Facets/FacetsSuggestion.tsx @@ -0,0 +1,36 @@ +import { ReactElement } from "react"; +import { faLightbulb } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Box } from "@mui/material"; +import { useAotfContext } from "../AssociationsToolkit"; + +const TARGET_EXAMPLE = "Measurement"; +const DISEASE_EXAMPLE = "Enzyme"; + +function FacetsSuggestion(): ReactElement { + const { entityToGet, entity } = useAotfContext(); + + return ( + + + theme.spacing(1), + }} + > + + Tip: + + + Please search by {entityToGet} or filter by {entityToGet} category. Example:{" "} + {entity === "disease" ? DISEASE_EXAMPLE : TARGET_EXAMPLE} + + + + ); +} +export default FacetsSuggestion; diff --git a/apps/platform/src/index.scss b/apps/platform/src/index.scss index 9239b452e..06d73fa65 100644 --- a/apps/platform/src/index.scss +++ b/apps/platform/src/index.scss @@ -255,20 +255,17 @@ code { box-sizing: border-box; } -.TAssociations .data-cell.active .data-score { - -webkit-box-shadow: 0px 0px 3px 1px rgba(92, 19, 19, 0.35); - box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.35); - border: 2px solid rgb(0, 0, 0) !important; - position: sticky; - top: 0; -} - .TAssociations .data-cell .data-score:hover, .TAssociations .data-cell .data-score:focus { -webkit-box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.35); box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.35); } +.TAssociations .data-cell.active .data-score { + box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.35); + border: 2px solid rgb(0, 0, 0) !important; +} + /* Agregations indicators */ .TAssociations .aggregation-indicator { @@ -359,10 +356,5 @@ code { max-width: 900px !important; min-width: 820px !important; } - & .data-score, - & .data-empty { - height: 22px !important; - width: 22px !important; - } } } diff --git a/apps/platform/src/pages/DiseasePage/DiseaseAssociations/DiseaseAssociations.jsx b/apps/platform/src/pages/DiseasePage/DiseaseAssociations/DiseaseAssociations.jsx deleted file mode 100644 index a27cf7155..000000000 --- a/apps/platform/src/pages/DiseasePage/DiseaseAssociations/DiseaseAssociations.jsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Box, Divider } from "@mui/material"; -import { - TableAssociations, - AdvanceOptionsMenu, - TargetPrioritisationSwitch, - AssociationsProvider, - SearhInput, - DataDownloader, - useAotfContext, - ControlsSection, - OptionsControlls, - AotFLoader, - DataUploader, - AotfApiPlayground, -} from "../../../components/AssociationsToolkit"; -import DISEASE_ASSOCIATIONS_QUERY from "./DiseaseAssociationsQuery.gql"; - -function AssociationsWrapper() { - const { initialLoading } = useAotfContext(); - - if (initialLoading) return ; - - return ( - <> - - - - - - - - - - - - - - - - - - ); -} - -/* DISEASE ASSOCIATION */ -function DiseaseAssociations({ efoId }) { - return ( - - - - ); -} - -export default DiseaseAssociations; diff --git a/apps/platform/src/pages/DiseasePage/DiseaseAssociations/DiseaseAssociations.tsx b/apps/platform/src/pages/DiseasePage/DiseaseAssociations/DiseaseAssociations.tsx new file mode 100644 index 000000000..647e355e0 --- /dev/null +++ b/apps/platform/src/pages/DiseasePage/DiseaseAssociations/DiseaseAssociations.tsx @@ -0,0 +1,49 @@ +import { ReactElement } from "react"; +import { Box, Divider } from "@mui/material"; +import { usePermissions } from "ui"; +import { + TableAssociations, + AdvanceOptionsMenu, + TargetPrioritisationSwitch, + AssociationsProvider, + DataDownloader, + ControlsSection, + DataUploader, + AotfApiPlayground, + SearchInput, +} from "../../../components/AssociationsToolkit"; +import { ENTITY } from "../../../components/AssociationsToolkit/types"; +import DISEASE_ASSOCIATIONS_QUERY from "./DiseaseAssociationsQuery.gql"; +import FacetsSearch from "../../../components/Facets/FacetsSearch"; + +type DiseaseAssociationsProps = { + efoId: string; +}; + +function DiseaseAssociations(pros: DiseaseAssociationsProps): ReactElement { + const { isPartnerPreview } = usePermissions(); + return ( + + + theme.spacing(2) }}> + {isPartnerPreview ? : } + + + + + + + + + + + + + ); +} + +export default DiseaseAssociations; diff --git a/apps/platform/src/pages/DiseasePage/DiseaseAssociations/DiseaseAssociationsQuery.gql b/apps/platform/src/pages/DiseasePage/DiseaseAssociations/DiseaseAssociationsQuery.gql index 377e3d9b7..f594148e1 100644 --- a/apps/platform/src/pages/DiseasePage/DiseaseAssociations/DiseaseAssociationsQuery.gql +++ b/apps/platform/src/pages/DiseasePage/DiseaseAssociations/DiseaseAssociationsQuery.gql @@ -8,6 +8,7 @@ query DiseaseAssociationsQuery( $enableIndirect: Boolean! $datasources: [DatasourceSettingsInput!] $rowsFilter: [String!] + $facetFilters: [String!] ) { disease(efoId: $id) { id @@ -20,6 +21,7 @@ query DiseaseAssociationsQuery( enableIndirect: $enableIndirect datasources: $datasources Bs: $rowsFilter + facetFilters: $facetFilters ) { count rows { diff --git a/apps/platform/src/pages/DiseasePage/DiseaseAssociations/index.js b/apps/platform/src/pages/DiseasePage/DiseaseAssociations/index.ts similarity index 100% rename from apps/platform/src/pages/DiseasePage/DiseaseAssociations/index.js rename to apps/platform/src/pages/DiseasePage/DiseaseAssociations/index.ts diff --git a/apps/platform/src/pages/DiseasePage/DiseasePage.jsx b/apps/platform/src/pages/DiseasePage/DiseasePage.jsx index 508ec8fbb..fab683050 100644 --- a/apps/platform/src/pages/DiseasePage/DiseasePage.jsx +++ b/apps/platform/src/pages/DiseasePage/DiseasePage.jsx @@ -8,9 +8,9 @@ import Header from "./Header"; import NotFoundPage from "../NotFoundPage"; import DISEASE_PAGE_QUERY from "./DiseasePage.gql"; +import Associations from "./DiseaseAssociations"; +import Profile from "./Profile"; -const Profile = lazy(() => import("./Profile")); -const Associations = lazy(() => import("./DiseaseAssociations")); const ClassicAssociations = lazy(() => import("./ClassicAssociations")); function DiseasePage() { diff --git a/apps/platform/src/pages/TargetPage/TargetAssociations/TargetAssociations.jsx b/apps/platform/src/pages/TargetPage/TargetAssociations/TargetAssociations.jsx deleted file mode 100644 index cde30e8c2..000000000 --- a/apps/platform/src/pages/TargetPage/TargetAssociations/TargetAssociations.jsx +++ /dev/null @@ -1,52 +0,0 @@ -import { useContext } from "react"; -import { Box, Divider } from "@mui/material"; -import { - TableAssociations, - AdvanceOptionsMenu, - AssociationsContext, - AssociationsProvider, - SearhInput, - DataDownloader, - ControlsSection, - OptionsControlls, - AotFLoader, - DataUploader, - AotfApiPlayground, -} from "../../../components/AssociationsToolkit"; -import TARGET_ASSOCIATIONS_QUERY from "./TargetAssociationsQuery.gql"; - -function AssociationsWrapper() { - const { initialLoading } = useContext(AssociationsContext); - - if (initialLoading) return ; - - return ( - <> - - - - - - - - - - - - - - - - ); -} - -/* TARGET ASSOCIATION */ -function TargetAssociations({ ensgId }) { - return ( - - - - ); -} - -export default TargetAssociations; diff --git a/apps/platform/src/pages/TargetPage/TargetAssociations/TargetAssociations.tsx b/apps/platform/src/pages/TargetPage/TargetAssociations/TargetAssociations.tsx new file mode 100644 index 000000000..94c828bf7 --- /dev/null +++ b/apps/platform/src/pages/TargetPage/TargetAssociations/TargetAssociations.tsx @@ -0,0 +1,42 @@ +import { ReactElement } from "react"; +import { Box, Divider } from "@mui/material"; +import { usePermissions } from "ui"; +import { + TableAssociations, + AdvanceOptionsMenu, + AssociationsProvider, + DataDownloader, + ControlsSection, + DataUploader, + AotfApiPlayground, + SearchInput, +} from "../../../components/AssociationsToolkit"; +import { ENTITY } from "../../../components/AssociationsToolkit/types"; +import TARGET_ASSOCIATIONS_QUERY from "./TargetAssociationsQuery.gql"; +import FacetsSearch from "../../../components/Facets/FacetsSearch"; + +type TargetAssociationsProps = { + ensgId: string; +}; + +function TargetAssociations({ ensgId }: TargetAssociationsProps): ReactElement { + const { isPartnerPreview } = usePermissions(); + return ( + + + theme.spacing(2) }}> + {isPartnerPreview ? : } + + + + + + + + + + + ); +} + +export default TargetAssociations; diff --git a/apps/platform/src/pages/TargetPage/TargetAssociations/TargetAssociationsQuery.gql b/apps/platform/src/pages/TargetPage/TargetAssociations/TargetAssociationsQuery.gql index d83fd66b1..0330bf2df 100644 --- a/apps/platform/src/pages/TargetPage/TargetAssociations/TargetAssociationsQuery.gql +++ b/apps/platform/src/pages/TargetPage/TargetAssociations/TargetAssociationsQuery.gql @@ -8,6 +8,7 @@ query TargetAssociationsQuery( $enableIndirect: Boolean! $datasources: [DatasourceSettingsInput!] $rowsFilter: [String!] + $facetFilters: [String!] ) { target(ensemblId: $id) { id @@ -20,6 +21,7 @@ query TargetAssociationsQuery( enableIndirect: $enableIndirect datasources: $datasources Bs: $rowsFilter + facetFilters: $facetFilters ) { count rows { diff --git a/apps/platform/src/pages/TargetPage/TargetAssociations/index.js b/apps/platform/src/pages/TargetPage/TargetAssociations/index.ts similarity index 100% rename from apps/platform/src/pages/TargetPage/TargetAssociations/index.js rename to apps/platform/src/pages/TargetPage/TargetAssociations/index.ts diff --git a/apps/platform/src/pages/TargetPage/TargetPage.jsx b/apps/platform/src/pages/TargetPage/TargetPage.jsx index 7a574860d..3774ce239 100644 --- a/apps/platform/src/pages/TargetPage/TargetPage.jsx +++ b/apps/platform/src/pages/TargetPage/TargetPage.jsx @@ -17,8 +17,9 @@ import NotFoundPage from "../NotFoundPage"; import { getUniprotIds } from "../../utils/global"; import TARGET_PAGE_QUERY from "./TargetPage.gql"; -const Profile = lazy(() => import("./Profile")); -const Associations = lazy(() => import("./TargetAssociations")); +import Profile from "./Profile"; +import Associations from "./TargetAssociations"; + const ClassicAssociations = lazy(() => import("./ClassicAssociations")); function TargetPage() { diff --git a/packages/sections/src/evidence/OTValidation/Body.jsx b/packages/sections/src/evidence/OTValidation/Body.jsx index 18bf92592..3c7ee8fb5 100644 --- a/packages/sections/src/evidence/OTValidation/Body.jsx +++ b/packages/sections/src/evidence/OTValidation/Body.jsx @@ -1,12 +1,11 @@ -import _ from "lodash"; -import classNames from "classnames"; import { useQuery } from "@apollo/client"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCheckCircle } from "@fortawesome/free-solid-svg-icons"; import { faTimesCircle } from "@fortawesome/free-regular-svg-icons"; -import { Box, Typography, Chip, Grid } from "@mui/material"; +import { Box, Chip } from "@mui/material"; import { makeStyles } from "@mui/styles"; -import { Link, SectionItem, Tooltip, ChipList, DataTable } from "ui"; +import { Link, SectionItem, ChipList, DataTable, Tooltip } from "ui"; +import { v1 } from "uuid"; import { definition } from "."; import Description from "./Description"; @@ -21,126 +20,34 @@ const useStyles = makeStyles(theme => ({ grey: { color: theme.palette.grey[300], }, - circleUp: { - marginRight: "10px", - }, - hypotesisBox: { - marginBottom: "2rem", - paddingBottom: "1rem", - borderBottom: `1px solid ${theme.palette.grey[300]}`, - }, bold: { fontWeight: 700, }, - // hypothesis status classes - hsLegendChip: { - width: theme.spacing(4), - }, - hsGreen: { - backgroundColor: "#407253 !important", // same as PPP green - border: `1px solid ${theme.palette.grey[600]} !important`, - }, - hsRed: { - backgroundColor: "#9e1316 !important", - border: `1px solid ${theme.palette.grey[600]} !important`, - }, hsWhite: { backgroundColor: "#ffffff !important", color: `${theme.palette.grey[600]} !important`, border: `1px solid ${theme.palette.grey[600]} !important`, }, - hsBlack: { - backgroundColor: "#000 !important", - border: `1px solid ${theme.palette.grey[600]} !important`, - }, - hsBlue: { - backgroundColor: "#3489ca !important", - border: `1px solid ${theme.palette.grey[600]} !important`, - }, - // in the unlikely case the hypothesis status is unavailable, - // we don't want to display the primary green (for PPP) - hsUndefined: { - backgroundColor: `${theme.palette.grey[500]} !important`, - border: `1px solid ${theme.palette.grey[600]} !important`, + hsDefault: { + backgroundColor: `${theme.palette.grey[300]} !important`, + color: `${theme.palette.grey[600]} !important`, }, })); -const isHit = (conf, validatedConf) => { - if (conf && validatedConf) { - return conf.toLowerCase() === validatedConf.toLowerCase(); - } - return conf.toLowerCase() === "significant"; +const ASSAYS_DISPLAY_NAME_MAPPING = { + "CellTiter-Glo": "CellTiterGlo", + Toxicity: "CellTox", + Confluence: "Cell Confluence", }; -function HitIcon({ isHitValue, classes }) { - return ( - - ); -} - -// Map response hypotheses status to style and labels -const hypothesesStatus = [ - { - status: "expected but not observed", - expected: true, - observed: false, - styles: "hsRed", - }, - { - status: "observed and expected", - expected: true, - observed: true, - styles: "hsGreen", - }, - { - status: "not expected and not observed", - expected: false, - observed: false, - styles: "hsBlack", - }, - { - status: "observed but not expected", - expected: false, - observed: true, - styles: "hsBlue", - }, -]; - const getColumns = classes => [ { id: "disease", label: "Reported disease", renderCell: row => {row.disease.name}, + sortable: true, filterValue: row => `${row.diseaseLabel}, ${row.diseaseId}`, }, - { - id: "projectDescription", - label: "OTAR primary project", - tooltip: <>Binary assessment of gene perturbation effect in primary project screen, - renderCell: row => ( - - {row.projectDescription} - - {row.projectId} - - - ), - filterValue: row => `${row.projectDescription}, ${row.projectId}`, - }, - { - id: "contrast", - label: "Contrast", - renderCell: row => ( - - {row.contrast} - - ), - filterValue: row => `${row.contrast}, ${row.studyOverview}`, - }, { id: "diseaseCellLines", label: "Cell line", @@ -177,24 +84,80 @@ const getColumns = classes => [ width: "16%", }, { - id: "resourceScore", - label: "Effect size", - renderCell: row => row.resourceScore, - numeric: true, - width: "8%", + id: "projectDescription", + label: "OTAR primary project", + renderCell: ({ primaryProjectId }) => ( + + {primaryProjectId} + + ), + filterValue: row => `${row.primaryProjectId}`, }, { - id: "confidence", - label: "OTVL hit", - tooltip: <>Binary assessment of gene perturbation effect in contrast, - renderCell: row => , + id: "primaryProjectHit", + label: "Primary project hit", + tooltip: <>Binary assessment of gene perturbation effect in primary project screen, + renderCell: ({ primaryProjectHit }) => ( + + ), + sortable: true, width: "8%", }, { - id: "projectHit", - label: "Primary project hit", - renderCell: row => , - width: "8%", + id: "assays", + label: "OTVL hit", + renderCell: ({ assays }) => { + let sortedAssays = [...assays]; + if (sortedAssays.length >= 2) { + sortedAssays.sort(function (a, b) { + if (a.shortName < b.shortName) { + return -1; + } + if (a.shortName > b.shortName) { + return 1; + } + return 0; + }); + } + return ( + <> + {sortedAssays.map(e => ( + theme.spacing(0.3) }} key={v1()}> + + + + + ))} + + ); + }, + }, + { + id: "assessment", + label: "OTVL assessment", + renderCell: ({ assessment }) => { + // TODO: temp solution, assessment key should contain array + const regex = + /(Evidence of dependency)|(Evidence of toxicity)|(No evidence of dependency)|(No evidence of toxicity)|(Multiple evidence of dependency)/g; + const listOfValues = assessment.match(regex); + return ( + <> + {listOfValues.map(e => ( + theme.spacing(1) }} key={e}> + {e} + + ))} + + ); + }, }, { id: "releaseVersion", @@ -212,17 +175,9 @@ const exportColumns = [ label: "disease id", exportValue: row => row.disease.id, }, - { - label: "project description", - exportValue: row => row.projectDescription, - }, { label: "project id", - exportValue: row => row.projectId, - }, - { - label: "contrast", - exportValue: row => row.contrast, + exportValue: row => row.primaryProjectId, }, { label: "study overview", @@ -237,16 +192,20 @@ const exportColumns = [ exportValue: row => row.biomarkerList.map(bm => bm.name), }, { - label: "effect size", - exportValue: row => row.resourceScore, + label: "VL hit", + exportValue: row => row.assays, }, { - label: "hit", - exportValue: row => isHit(row.confidence), + label: "primaryProjectHit", + exportValue: row => row.primaryProjectHit, }, { - label: "primary project hit", - exportValue: row => isHit(row.expectedConfidence), + label: "assessment", + exportValue: row => row.assessment, + }, + { + label: "effect size", + exportValue: row => row.resourceScore, }, ]; @@ -267,85 +226,8 @@ function Body({ id, label, entity }) { renderDescription={() => } renderBody={({ disease }) => { const { rows } = disease.otValidationSummary; - const hypothesis = _.uniqBy( - rows.reduce( - (prev, curr) => - prev.concat( - curr.validationHypotheses.map(vht => ({ - label: vht.name, - tooltip: vht.description, - customClass: - classes[ - hypothesesStatus.find(s => s.status === vht.status)?.styles || "hsUndefined" - ], - })) - ), - [] - ), - "label" - // sort alphabetically but move 'PAN-CO' at the end of the list - ).sort((a, b) => (b.label === "PAN-CO" || a.label < b.label ? -1 : 1)); - return ( <> - - - - This table provides an overarching summary of the target-disease association - in the context of the listed biomarkers, based on criteria described{" "} - - here - - , as informed by the target performance across the whole cell line panel. - Colour-coding indicates whether a dependency was expected to be associated - with a biomarker (based on Project SCORE data) and whether it was observed as - such (based on OTVL data). - - } - showHelpIcon - > - OTVL biomarker assessment for {label.symbol} - - - - {/** LEGEND */} - theme.spacing(2), - display: "flex", - flexDirection: "row", - flexWrap: "wrap", - }} - > - {hypothesesStatus.map(hs => ( - - - `0 ${theme.spacing(3)} 0 ${theme.spacing(1)}` }}> - - - {hs.expected ? "Expected" : "Not expected"} in OTAR primary project - - - - - {hs.observed ? "Observed" : "Not observed"} - in OTVL - - - - - ))} - - - {/** CHIP LIST */} - - - type ApiPlaygroundDrawerProps = { query: string; variables: any; + fullHeight: boolean; }; -function ApiPlaygroundDrawer({ query, variables }: ApiPlaygroundDrawerProps) { +function ApiPlaygroundDrawer({ + query, + variables, + fullHeight, +}: ApiPlaygroundDrawerProps): ReactElement { const [open, setOpen] = useState(false); function close(e: KeyboardEvent) { @@ -42,7 +47,12 @@ function ApiPlaygroundDrawer({ query, variables }: ApiPlaygroundDrawerProps) { {" "} {query ? ( - diff --git a/packages/ui/src/components/Header.jsx b/packages/ui/src/components/Header.jsx index 65f06eb96..54919ac43 100644 --- a/packages/ui/src/components/Header.jsx +++ b/packages/ui/src/components/Header.jsx @@ -48,10 +48,10 @@ function Header({ loading, Icon, title, subtitle = null, externalLinks, rightCon - {loading ? : title} + {loading ? : title} - {loading ? : subtitle} + {loading ? : subtitle}