mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-09 05:28:13 +08:00
Pull config values into dataset overview drawer (#1814)
This PR adds multiple data to the dataset overview drawer provided by the config endpoint and formats them accordingly. The appearance of this new data is contingent on `dataPortalProps.corpora_schema_version === "1.0.0"` For QA launch cellxgene with a remixed dataset and click on the button in the upper left-hand corner or the updated button in the info menu.  ~~Review opening is blocked by merge of #1805~~ --- Closes #1319
This commit is contained in:
@@ -39,6 +39,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
"react/jsx-no-target-blank": "off",
|
||||||
"eslint-comments/require-description": ["error"],
|
"eslint-comments/require-description": ["error"],
|
||||||
"no-magic-numbers": "off",
|
"no-magic-numbers": "off",
|
||||||
"no-nested-ternary": "off",
|
"no-nested-ternary": "off",
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import React, { PureComponent } from "react";
|
import React, { PureComponent } from "react";
|
||||||
import { connect, shallowEqual } from "react-redux";
|
import { connect, shallowEqual } from "react-redux";
|
||||||
import { Drawer, H3, H1, UL, Classes } from "@blueprintjs/core";
|
import { Drawer } from "@blueprintjs/core";
|
||||||
import Async from "react-async";
|
import Async from "react-async";
|
||||||
|
|
||||||
|
import InfoFormat from "./infoFormat";
|
||||||
import {
|
import {
|
||||||
selectableCategoryNames,
|
selectableCategoryNames,
|
||||||
createCategorySummaryFromDfCol,
|
createCategorySummaryFromDfCol,
|
||||||
@@ -14,6 +16,7 @@ import {
|
|||||||
datasetTitle: state.config?.displayNames?.dataset ?? "",
|
datasetTitle: state.config?.displayNames?.dataset ?? "",
|
||||||
aboutURL: state.config?.links?.["about-dataset"],
|
aboutURL: state.config?.links?.["about-dataset"],
|
||||||
isOpen: state.controls.datasetDrawer,
|
isOpen: state.controls.datasetDrawer,
|
||||||
|
dataPortalProps: state.config?.["corpora_props"] ?? {},
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
class InfoDrawer extends PureComponent {
|
class InfoDrawer extends PureComponent {
|
||||||
@@ -60,7 +63,14 @@ class InfoDrawer extends PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { position, aboutURL, datasetTitle, schema, isOpen } = this.props;
|
const {
|
||||||
|
position,
|
||||||
|
aboutURL,
|
||||||
|
datasetTitle,
|
||||||
|
schema,
|
||||||
|
isOpen,
|
||||||
|
dataPortalProps,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
@@ -74,7 +84,10 @@ class InfoDrawer extends PureComponent {
|
|||||||
watchProps={{ schema }}
|
watchProps={{ schema }}
|
||||||
>
|
>
|
||||||
<Async.Pending>
|
<Async.Pending>
|
||||||
<InfoFormat skeleton {...{ datasetTitle, aboutURL }} />
|
<InfoFormat
|
||||||
|
skeleton
|
||||||
|
{...{ datasetTitle, aboutURL, dataPortalProps }}
|
||||||
|
/>
|
||||||
</Async.Pending>
|
</Async.Pending>
|
||||||
<Async.Rejected>
|
<Async.Rejected>
|
||||||
{(error) => {
|
{(error) => {
|
||||||
@@ -87,7 +100,12 @@ class InfoDrawer extends PureComponent {
|
|||||||
const { singleValueCategories } = asyncProps;
|
const { singleValueCategories } = asyncProps;
|
||||||
return (
|
return (
|
||||||
<InfoFormat
|
<InfoFormat
|
||||||
{...{ datasetTitle, aboutURL, singleValueCategories }}
|
{...{
|
||||||
|
datasetTitle,
|
||||||
|
aboutURL,
|
||||||
|
singleValueCategories,
|
||||||
|
dataPortalProps,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
@@ -97,56 +115,4 @@ class InfoDrawer extends PureComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const NUM_CATEGORIES = 8;
|
|
||||||
|
|
||||||
const singleValueCategoriesPlaceholder = Array.from(Array(NUM_CATEGORIES)).map(
|
|
||||||
(_, index) => {
|
|
||||||
return [index, index];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const InfoFormat = ({
|
|
||||||
datasetTitle,
|
|
||||||
singleValueCategories = new Map(singleValueCategoriesPlaceholder),
|
|
||||||
aboutURL = "thisisabouthtelengthofaurl",
|
|
||||||
skeleton = false,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div style={{ margin: 24 }}>
|
|
||||||
<H1 className={skeleton ? Classes.SKELETON : null}>{datasetTitle}</H1>
|
|
||||||
{singleValueCategories.size > 0 && (
|
|
||||||
<>
|
|
||||||
<H3 className={skeleton ? Classes.SKELETON : null}>
|
|
||||||
Dataset Metadata
|
|
||||||
</H3>
|
|
||||||
<UL>
|
|
||||||
{Array.from(singleValueCategories).map((pair) => {
|
|
||||||
if (!pair[1] || pair[1] === "") return null;
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
className={skeleton ? Classes.SKELETON : null}
|
|
||||||
key={pair[0]}
|
|
||||||
>{`${pair[0]}: ${pair[1]}`}</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</UL>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{aboutURL && (
|
|
||||||
<>
|
|
||||||
<H3 className={skeleton ? Classes.SKELETON : null}>More Info</H3>
|
|
||||||
<a
|
|
||||||
className={skeleton ? Classes.SKELETON : null}
|
|
||||||
href={aboutURL}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{aboutURL}
|
|
||||||
</a>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default InfoDrawer;
|
export default InfoDrawer;
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
import { H3, H1, UL, Classes } from "@blueprintjs/core";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const renderContributors = (contributors, affiliations, skeleton) => {
|
||||||
|
// eslint-disable-next-line no-constant-condition -- Temp removed contributor section to avoid publishing PII
|
||||||
|
if (contributors?.length === 0 && true) return null;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<H3 className={skeleton ? Classes.SKELETON : null}>Contributors</H3>
|
||||||
|
<p className={skeleton ? Classes.SKELETON : null}>
|
||||||
|
{contributors.map((contributor) => {
|
||||||
|
const { email, name, institution } = contributor;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span key={name}>
|
||||||
|
{name}
|
||||||
|
{email && `(${email})`}
|
||||||
|
<sup>{affiliations.indexOf(institution) + 1}</sup>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
{renderAffiliations(affiliations, skeleton)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildAffiliations = (contributors = []) => {
|
||||||
|
const affiliations = [];
|
||||||
|
contributors.forEach((contributor) => {
|
||||||
|
const { institution } = contributor;
|
||||||
|
if (affiliations.indexOf(institution) === -1) {
|
||||||
|
affiliations.push(institution);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return affiliations;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderAffiliations = (affiliations, skeleton) => {
|
||||||
|
if (affiliations.length === 0) return null;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<H3 className={skeleton ? Classes.SKELETON : null}>Affiliations</H3>
|
||||||
|
<UL>
|
||||||
|
{affiliations.map((item, index) => (
|
||||||
|
<div
|
||||||
|
id={`#afil${index}`}
|
||||||
|
key={item}
|
||||||
|
className={skeleton ? Classes.SKELETON : null}
|
||||||
|
>
|
||||||
|
<sup>{index + 1}</sup>
|
||||||
|
{" "}
|
||||||
|
{item}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</UL>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderDOILink = (type, doi, skeleton) => {
|
||||||
|
if (!doi) return null;
|
||||||
|
return (
|
||||||
|
doi && (
|
||||||
|
<>
|
||||||
|
<H3 className={skeleton ? Classes.SKELETON : null}>{type}</H3>
|
||||||
|
<p className={skeleton ? Classes.SKELETON : null}>
|
||||||
|
<a href={doi} target="_blank" rel="noopener">
|
||||||
|
{doi}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderOrganism = (organism, skeleton) => {
|
||||||
|
if (!organism) return null;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<H3 className={skeleton ? Classes.SKELETON : null}>Organism</H3>
|
||||||
|
<p className={skeleton ? Classes.SKELETON : null}>{organism}</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderSingleValueCategories = (singleValueCategories, skeleton) => {
|
||||||
|
if (singleValueCategories.size === 0) return null;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<H3 className={skeleton ? Classes.SKELETON : null}>Dataset Metadata</H3>
|
||||||
|
<UL>
|
||||||
|
{Array.from(singleValueCategories).map((pair) => {
|
||||||
|
if (!pair[1] || pair[1] === "") return null;
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={skeleton ? Classes.SKELETON : null}
|
||||||
|
key={pair[0]}
|
||||||
|
>{`${pair[0]}: ${pair[1]}`}</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</UL>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderLinks = (projectLinks, aboutURL, skeleton) => {
|
||||||
|
if (!projectLinks && !aboutURL) return null;
|
||||||
|
if (projectLinks)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<H3 className={skeleton ? Classes.SKELETON : null}>Project Links</H3>
|
||||||
|
<UL>
|
||||||
|
{projectLinks.map((link) => {
|
||||||
|
if (link.link_type === "SUMMARY") return null;
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={link.link_name}
|
||||||
|
className={skeleton ? Classes.SKELETON : null}
|
||||||
|
>
|
||||||
|
<a href={link.link_url} target="_blank" rel="noopener">
|
||||||
|
{link.link_name}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</UL>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<H3 className={skeleton ? Classes.SKELETON : null}>More Info</H3>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
className={skeleton ? Classes.SKELETON : null}
|
||||||
|
href={aboutURL}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
{aboutURL}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const NUM_CATEGORIES = 8;
|
||||||
|
|
||||||
|
const singleValueCategoriesPlaceholder = Array.from(Array(NUM_CATEGORIES)).map(
|
||||||
|
(_, index) => {
|
||||||
|
return [index, index];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const InfoFormat = React.memo(
|
||||||
|
({
|
||||||
|
datasetTitle,
|
||||||
|
singleValueCategories = new Map(singleValueCategoriesPlaceholder),
|
||||||
|
aboutURL = "thisisabouthtelengthofaurl",
|
||||||
|
dataPortalProps = {},
|
||||||
|
skeleton = false,
|
||||||
|
}) => {
|
||||||
|
if (dataPortalProps.corpora_schema_version === "1.0.0") {
|
||||||
|
dataPortalProps = {};
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
publication_doi: doi,
|
||||||
|
preprint_doi: preprintDOI,
|
||||||
|
organism,
|
||||||
|
contributors,
|
||||||
|
project_links: projectLinks,
|
||||||
|
} = dataPortalProps;
|
||||||
|
|
||||||
|
const affiliations = buildAffiliations(contributors);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ margin: 24, overflow: "auto" }}>
|
||||||
|
<H1 className={skeleton ? Classes.SKELETON : null}>
|
||||||
|
{title ?? datasetTitle}
|
||||||
|
</H1>
|
||||||
|
{renderContributors(contributors, affiliations, skeleton)}
|
||||||
|
{renderDOILink("DOI", doi, skeleton)}
|
||||||
|
{renderDOILink("Preprint DOI", preprintDOI, skeleton)}
|
||||||
|
{renderOrganism(organism, skeleton)}
|
||||||
|
{renderSingleValueCategories(singleValueCategories, skeleton)}
|
||||||
|
{renderLinks(projectLinks, aboutURL, skeleton)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default InfoFormat;
|
||||||
@@ -84,7 +84,7 @@ class TermsPrompt extends React.PureComponent {
|
|||||||
}}
|
}}
|
||||||
href={tosURL}
|
href={tosURL}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener"
|
||||||
>
|
>
|
||||||
terms of service
|
terms of service
|
||||||
</a>
|
</a>
|
||||||
@@ -106,7 +106,7 @@ class TermsPrompt extends React.PureComponent {
|
|||||||
}}
|
}}
|
||||||
href={privacyURL}
|
href={privacyURL}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener"
|
||||||
>
|
>
|
||||||
privacy policy
|
privacy policy
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
Vendored
-1
@@ -21,7 +21,6 @@ const Controls = (
|
|||||||
scatterplotYYaccessor: null,
|
scatterplotYYaccessor: null,
|
||||||
graphRenderCounter: 0 /* integer as <Component key={graphRenderCounter} - a change in key forces a remount */,
|
graphRenderCounter: 0 /* integer as <Component key={graphRenderCounter} - a change in key forces a remount */,
|
||||||
|
|
||||||
singletonHover: false,
|
|
||||||
datasetDrawer: false,
|
datasetDrawer: false,
|
||||||
},
|
},
|
||||||
action
|
action
|
||||||
|
|||||||
Reference in New Issue
Block a user