The profile tab for each of the entities (target, disease, drug and evidence) is broken up into sections. Each of these sections is encapsulated in one directory under src/sections/<entity>/
. To add a new one, follow this structure:
src
└── sections
└── <entity>
└── <sectionId>
├── index.js
├── Summary.js
├── Body.js
├── Description.js
├── <sectionId>SummaryFragment.gql
└── <sectionId>Query.gql
The following describe all the files in detail, following the simple TEP section from the target
entity page as an example.
The queries should be imported as a separate .gql
file whenever possible, for improved code quality and testing.
There are generally two queries: one for the Summary and one for the actual widget. Some page sections will have both, some will only have the Summary query.
Note that the Summary query is a Fragment
: all fragments are then bundled together in one query for the page.
Below we also use examples from the Indications section on the drug
entity page to show the Body query.
The index.js
defines the section information as well as exporting its Body
and Summary
components:
export const definition = {
id: 'tep',
name: 'Target Enabling Packages',
// Optional, if not present it will default to the first character of the
// first two words in the name.
shortName: 'TEP',
// hasData takes the data content of the summary query as its parameter, to
// determine if there is data for this section (and therefore whether to
// display its body or not.
hasData: data => !!data.tep,
// Optional, set to true if the section uses an external API instead.
external: false,
};
export { default as Summary } from './Summary';
export { default as Body } from './Body';
This component is in charge of handling the data acquisition for the summary, and must render the contents of the section's summary in the page's table of contents:
If the section uses Open Target's GraphQL API, it must include a fragments
field with the query. This is used to add the data to the context provider used by the profile page. The query should be imported as a separate .gql file whenever possible.
const TEP_SUMMARY_FRAGMENT = loader('./TepSummaryFragment.gql');
function Summary({ definition }) {
// ...
}
Summary.fragments = {
TepSummaryFragment: TEP_SUMMARY_FRAGMENT,
};
The summary component will be used in the entity's profile page, and will bring the following props:
id
: id of the entity (eg:ENSG00000158578
forALAS2
).label
: label of the entity, in case of a target it will contain theapprovedSymbol
field from the API (ALAS2
).definition
: thedefinition
object exported byindex.js
.
The render must be done in using the render prop renderSummary
of a root SummaryItem
component. It must be passed the definition for the section, and takes a data
parameter, which will contain the request's data
field. It is safe to assume there will be data in there, as the SummaryItem
takes care of the loading and error states.
For this to assumption to work when using custom requests, the SummaryItem
component must be passed an object with the requests' state and data structured as follows:
Field | Type | Description |
---|---|---|
loading | bool |
true for an in-flight request |
error | object | null |
should be null if no error |
data | object | null |
It is also worth noting that the for sections using Open Target's GraphQL API, the summary should use the usePlatformApi
hook to store the summary data in the context provider, which later on can be retrieved from the section's body if it is needed. Passing the query as a parameter is optional, and it filters the object contents to only return the part relevant to this section.
function Summary({ definition }) {
const request = usePlatformApi(TEP_SUMMARY_FRAGMENT);
return (
<SummaryItem
definition={definition}
request={request}
renderSummary={() => 'Available'}
/>
);
}
This is the component in charge of rendering the section's main contents. It is also responsible for acquiring its data.
Just like the in the Summary component, the Body is used in the entity's profile page and brings the same id
, label
and definition
props. It also uses a renderBody
to render its contents. It has the same definition
and request
props, and now includes a renderDescription
prop to take care of the titlebar description.
In this example, it is enough for the body to use the data fetched by the summary, so it uses the usePlatformApi
hook like the summary:
const Section = ({ definition, label: symbol }) => {
const request = usePlatformApi(Summary.fragments.TepSummaryFragment);
return (
<SectionItem
definition={definition}
request={request}
renderDescription={() => <Description symbol={symbol} />}
renderBody={data => (
<>
Learn more about the{' '}
<Link to={data.tep.uri} external>
{data.tep.name} TEP
</Link>
.
</>
)}
/>
);
};
Other cases might need a specific separate query: this can be loaded with the useQuery
hook:
const INDICATIONS_QUERY = loader('./IndicationsQuery.gql');
function Body({ definition, id: chemblId, label: name }) {
const request = useQuery(INDICATIONS_QUERY, { variables: { chemblId } });
return (
<SectionItem
definition={definition}
request={request}
renderDescription={() => <Description name={name} />}
renderBody={data => {
const { rows } = data.drug.indications;
return <DataTable columns={columns} rows={rows} />;
}}
/>
);
}
This component contains the description shown in the section's body's title bar:
function Description({ symbol }) {
return (
<>
Critical mass of reagents and knowledge allowing for the rapid biochemical
and chemical exploration of <strong>{symbol}</strong>. Source:{' '}
<Link external to="https://www.thesgc.org/tep">
SGC
</Link>
.
</>
);
}
This is the gql
query fragment for the summary described above.
fragment TepSummaryFragment on Target {
id
tep {
uri
name
}
}
This is the gql
query for the Body of this section.
query IndicationsQuery($chemblId: String!) {
drug(chemblId: $chemblId) {
id
indications {
rows {
maxPhaseForIndication
disease {
id
name
therapeuticAreas {
id
name
}
}
references {
ids
source
}
}
}
}
}
- Indications: Uses Platform-API with separate queries for summary and body.
- EuropePmc: Uses both internal and external APIs; evidence page queries requires both gene id and disease id.
- Baseline Expression: Platform-API in the summary; both Platform-API and external API in the body, uses a query inside each subcomponent.
Profile or evidence sections/widgets often include tables with a "publications" column. The PublicationsDrawer
component allows to display minimal information in the table cell, which can be expanded on user click to show publications details in a clean "drawer" that slides in from the side of the page.
Using the PublicationsDrawer
component is really straightforward and ensures a consitent and unified publications display across the platform.
The component takes one prop entries
, an array containing Objects in the format {name, url, group}
.
Below is an example from the CancerGeneCensus evidence table.
// column definition
const columns = [
// ...
{
label: 'Literature',
renderCell: ({ literature }) => {
const literatureList =
literature?.reduce((acc, id) => {
if (id === 'NA') return acc;
return [
...acc,
{
name: id,
url: epmcUrl(id),
group: 'literature',
},
];
}, []) || [];
return <PublicationsDrawer entries={literatureList} />;
},
}
];
// ...
function Body({ definition, id, label }) {
return (
<SectionItem
// ...
return (
<DataTable
columns={columns}
// ...
/>
)
/>
}