@@ -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}