mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-11 07:00:55 +08:00
Speed up dataset drawer rendering (#1926)
This commit is contained in:
@@ -1,13 +1,9 @@
|
|||||||
import React, { PureComponent } from "react";
|
import React, { PureComponent } from "react";
|
||||||
import { connect, shallowEqual } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Drawer } from "@blueprintjs/core";
|
import { Drawer } from "@blueprintjs/core";
|
||||||
import Async from "react-async";
|
|
||||||
|
|
||||||
import InfoFormat from "./infoFormat";
|
import InfoFormat from "./infoFormat";
|
||||||
import {
|
import { selectableCategoryNames } from "../../util/stateManager/controlsHelpers";
|
||||||
selectableCategoryNames,
|
|
||||||
createCategorySummaryFromDfCol,
|
|
||||||
} from "../../util/stateManager/controlsHelpers";
|
|
||||||
|
|
||||||
@connect((state) => {
|
@connect((state) => {
|
||||||
return {
|
return {
|
||||||
@@ -20,44 +16,6 @@ import {
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
class InfoDrawer extends PureComponent {
|
class InfoDrawer extends PureComponent {
|
||||||
static watchAsync(props, prevProps) {
|
|
||||||
return !shallowEqual(props.watchProps, prevProps.watchProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchAsyncProps = async (props) => {
|
|
||||||
const { schema } = props.watchProps;
|
|
||||||
const { annoMatrix } = this.props;
|
|
||||||
|
|
||||||
const allCategoryNames = selectableCategoryNames(schema).sort();
|
|
||||||
|
|
||||||
const nonUserAnnoCategories = allCategoryNames.map((catName) => {
|
|
||||||
const isUserAnno = schema?.annotations?.obsByName[catName]?.writable;
|
|
||||||
if (!isUserAnno) return annoMatrix.fetch("obs", catName);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
const singleValueCategories = (
|
|
||||||
await Promise.all(nonUserAnnoCategories)
|
|
||||||
).reduce((acc, categoryData, i) => {
|
|
||||||
// Actually check to see if it is null(user anno)
|
|
||||||
if (!categoryData) return acc;
|
|
||||||
const catName = allCategoryNames[i];
|
|
||||||
|
|
||||||
const column = categoryData.icol(0);
|
|
||||||
const colSchema = schema.annotations.obsByName[catName];
|
|
||||||
|
|
||||||
const categorySummary = createCategorySummaryFromDfCol(column, colSchema);
|
|
||||||
|
|
||||||
const { numCategoryValues } = categorySummary;
|
|
||||||
// Add to the array if the category has only one value
|
|
||||||
if (numCategoryValues === 1) {
|
|
||||||
acc.set(catName, categorySummary.allCategoryValues[0]);
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, new Map());
|
|
||||||
|
|
||||||
return { singleValueCategories };
|
|
||||||
};
|
|
||||||
|
|
||||||
handleClose = () => {
|
handleClose = () => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
|
|
||||||
@@ -74,45 +32,31 @@ class InfoDrawer extends PureComponent {
|
|||||||
dataPortalProps,
|
dataPortalProps,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const allCategoryNames = selectableCategoryNames(schema).sort();
|
||||||
|
const singleValueCategories = new Map();
|
||||||
|
|
||||||
|
allCategoryNames.forEach((catName) => {
|
||||||
|
const isUserAnno = schema?.annotations?.obsByName[catName]?.writable;
|
||||||
|
const colSchema = schema.annotations.obsByName[catName];
|
||||||
|
if (!isUserAnno && colSchema.categories?.length === 1) {
|
||||||
|
singleValueCategories.set(catName, colSchema.categories[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
title="Dataset Overview"
|
title="Dataset Overview"
|
||||||
onClose={this.handleClose}
|
onClose={this.handleClose}
|
||||||
{...{ isOpen, position }}
|
{...{ isOpen, position }}
|
||||||
>
|
>
|
||||||
<Async
|
<InfoFormat
|
||||||
watchFn={InfoDrawer.watchAsync}
|
{...{
|
||||||
promiseFn={this.fetchAsyncProps}
|
datasetTitle,
|
||||||
watchProps={{ schema }}
|
aboutURL,
|
||||||
>
|
singleValueCategories,
|
||||||
<Async.Pending>
|
dataPortalProps,
|
||||||
<InfoFormat
|
}}
|
||||||
skeleton
|
/>
|
||||||
{...{ datasetTitle, aboutURL, dataPortalProps }}
|
|
||||||
/>
|
|
||||||
</Async.Pending>
|
|
||||||
<Async.Rejected>
|
|
||||||
{(error) => {
|
|
||||||
console.error(error);
|
|
||||||
return <span>Failed to load info</span>;
|
|
||||||
}}
|
|
||||||
</Async.Rejected>
|
|
||||||
<Async.Fulfilled>
|
|
||||||
{(asyncProps) => {
|
|
||||||
const { singleValueCategories } = asyncProps;
|
|
||||||
return (
|
|
||||||
<InfoFormat
|
|
||||||
{...{
|
|
||||||
datasetTitle,
|
|
||||||
aboutURL,
|
|
||||||
singleValueCategories,
|
|
||||||
dataPortalProps,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Async.Fulfilled>
|
|
||||||
</Async>
|
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { H3, H1, UL, Classes } from "@blueprintjs/core";
|
import { H3, H1, UL } from "@blueprintjs/core";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import Truncate from "../util/truncate";
|
import Truncate from "../util/truncate";
|
||||||
|
|
||||||
const renderContributors = (contributors, affiliations, skeleton) => {
|
const renderContributors = (contributors, affiliations) => {
|
||||||
// eslint-disable-next-line no-constant-condition -- Temp removed contributor section to avoid publishing PII
|
// eslint-disable-next-line no-constant-condition -- Temp removed contributor section to avoid publishing PII
|
||||||
if (!contributors || contributors.length === 0 || true) return null;
|
if (!contributors || contributors.length === 0 || true) return null;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<H3 className={skeleton ? Classes.SKELETON : null}>Contributors</H3>
|
<H3>Contributors</H3>
|
||||||
<p className={skeleton ? Classes.SKELETON : null}>
|
<p>
|
||||||
{contributors.map((contributor) => {
|
{contributors.map((contributor) => {
|
||||||
const { email, name, institution } = contributor;
|
const { email, name, institution } = contributor;
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ const renderContributors = (contributors, affiliations, skeleton) => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
{renderAffiliations(affiliations, skeleton)}
|
{renderAffiliations(affiliations)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -39,14 +39,14 @@ const buildAffiliations = (contributors = []) => {
|
|||||||
return affiliations;
|
return affiliations;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderAffiliations = (affiliations, skeleton) => {
|
const renderAffiliations = (affiliations) => {
|
||||||
if (affiliations.length === 0) return null;
|
if (affiliations.length === 0) return null;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<H3 className={skeleton ? Classes.SKELETON : null}>Affiliations</H3>
|
<H3>Affiliations</H3>
|
||||||
<UL>
|
<UL>
|
||||||
{affiliations.map((item, index) => (
|
{affiliations.map((item, index) => (
|
||||||
<div key={item} className={skeleton ? Classes.SKELETON : null}>
|
<div key={item}>
|
||||||
<sup>{index + 1}</sup>
|
<sup>{index + 1}</sup>
|
||||||
{" "}
|
{" "}
|
||||||
{item}
|
{item}
|
||||||
@@ -57,12 +57,12 @@ const renderAffiliations = (affiliations, skeleton) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderDOILink = (type, doi, skeleton) => {
|
const renderDOILink = (type, doi) => {
|
||||||
if (!doi) return null;
|
if (!doi) return null;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<H3 className={skeleton ? Classes.SKELETON : null}>{type}</H3>
|
<H3>{type}</H3>
|
||||||
<p className={skeleton ? Classes.SKELETON : null}>
|
<p>
|
||||||
<a href={doi} target="_blank" rel="noopener">
|
<a href={doi} target="_blank" rel="noopener">
|
||||||
{doi}
|
{doi}
|
||||||
</a>
|
</a>
|
||||||
@@ -71,12 +71,12 @@ const renderDOILink = (type, doi, skeleton) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderOrganism = (organism, skeleton) => {
|
const renderOrganism = (organism) => {
|
||||||
if (!organism) return null;
|
if (!organism) return null;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<H3 className={skeleton ? Classes.SKELETON : null}>Organism</H3>
|
<H3>Organism</H3>
|
||||||
<p className={skeleton ? Classes.SKELETON : null}>{organism}</p>
|
<p>{organism}</p>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -85,11 +85,11 @@ const ONTOLOGY_KEY = "ontology_term_id";
|
|||||||
const CAT_WIDTH = "30%";
|
const CAT_WIDTH = "30%";
|
||||||
const VAL_WIDTH = "35%";
|
const VAL_WIDTH = "35%";
|
||||||
// Render list of metadata attributes found in categorical field
|
// Render list of metadata attributes found in categorical field
|
||||||
const renderSingleValueCategories = (singleValueCategories, skeleton) => {
|
const renderSingleValueCategories = (singleValueCategories) => {
|
||||||
if (singleValueCategories.size === 0) return null;
|
if (singleValueCategories.size === 0) return null;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<H3 className={skeleton ? Classes.SKELETON : null}>Dataset Metadata</H3>
|
<H3>Dataset Metadata</H3>
|
||||||
<UL>
|
<UL>
|
||||||
{Array.from(singleValueCategories).reduce((elems, pair) => {
|
{Array.from(singleValueCategories).reduce((elems, pair) => {
|
||||||
const [category, value] = pair;
|
const [category, value] = pair;
|
||||||
@@ -115,11 +115,7 @@ const renderSingleValueCategories = (singleValueCategories, skeleton) => {
|
|||||||
} else {
|
} else {
|
||||||
// Create the list item
|
// Create the list item
|
||||||
elems.push(
|
elems.push(
|
||||||
<li
|
<li key={category} style={{ width: "100%" }}>
|
||||||
className={skeleton ? Classes.SKELETON : null}
|
|
||||||
key={category}
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
>
|
|
||||||
<Truncate>
|
<Truncate>
|
||||||
<span style={{ width: CAT_WIDTH }}>{`${category}:`}</span>
|
<span style={{ width: CAT_WIDTH }}>{`${category}:`}</span>
|
||||||
</Truncate>
|
</Truncate>
|
||||||
@@ -138,20 +134,17 @@ const renderSingleValueCategories = (singleValueCategories, skeleton) => {
|
|||||||
|
|
||||||
// Renders any links found in the config where link_type is not "SUMMARY"
|
// Renders any links found in the config where link_type is not "SUMMARY"
|
||||||
// If there are no links in the config, render the aboutURL
|
// If there are no links in the config, render the aboutURL
|
||||||
const renderLinks = (projectLinks, aboutURL, skeleton) => {
|
const renderLinks = (projectLinks, aboutURL) => {
|
||||||
if (!projectLinks && !aboutURL) return null;
|
if (!projectLinks && !aboutURL) return null;
|
||||||
if (projectLinks)
|
if (projectLinks)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<H3 className={skeleton ? Classes.SKELETON : null}>Project Links</H3>
|
<H3>Project Links</H3>
|
||||||
<UL>
|
<UL>
|
||||||
{projectLinks.map((link) => {
|
{projectLinks.map((link) => {
|
||||||
if (link.link_type === "SUMMARY") return null;
|
if (link.link_type === "SUMMARY") return null;
|
||||||
return (
|
return (
|
||||||
<li
|
<li key={link.link_name}>
|
||||||
key={link.link_name}
|
|
||||||
className={skeleton ? Classes.SKELETON : null}
|
|
||||||
>
|
|
||||||
<a href={link.link_url} target="_blank" rel="noopener">
|
<a href={link.link_url} target="_blank" rel="noopener">
|
||||||
{link.link_name}
|
{link.link_name}
|
||||||
</a>
|
</a>
|
||||||
@@ -164,14 +157,9 @@ const renderLinks = (projectLinks, aboutURL, skeleton) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<H3 className={skeleton ? Classes.SKELETON : null}>More Info</H3>
|
<H3>More Info</H3>
|
||||||
<p>
|
<p>
|
||||||
<a
|
<a href={aboutURL} target="_blank" rel="noopener">
|
||||||
className={skeleton ? Classes.SKELETON : null}
|
|
||||||
href={aboutURL}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
|
||||||
{aboutURL}
|
{aboutURL}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -179,24 +167,14 @@ const renderLinks = (projectLinks, aboutURL, skeleton) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const NUM_CATEGORIES = 8;
|
|
||||||
|
|
||||||
// Generates arbitrary placeholder array for singleValueCategories skeleton shape
|
|
||||||
const singleValueCategoriesPlaceholder = Array.from(Array(NUM_CATEGORIES)).map(
|
|
||||||
(_, index) => {
|
|
||||||
return [index, index];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const InfoFormat = React.memo(
|
const InfoFormat = React.memo(
|
||||||
({
|
({
|
||||||
datasetTitle,
|
datasetTitle,
|
||||||
singleValueCategories = new Map(singleValueCategoriesPlaceholder),
|
singleValueCategories,
|
||||||
aboutURL = "thisisabouthtelengthofaurl",
|
aboutURL,
|
||||||
dataPortalProps = {},
|
dataPortalProps = {},
|
||||||
skeleton = false,
|
|
||||||
}) => {
|
}) => {
|
||||||
if (dataPortalProps.corpora_schema_version === "1.0.0") {
|
if (dataPortalProps.version?.["corpora_schema_version"] !== "1.0.0") {
|
||||||
dataPortalProps = {};
|
dataPortalProps = {};
|
||||||
}
|
}
|
||||||
const {
|
const {
|
||||||
@@ -212,15 +190,13 @@ const InfoFormat = React.memo(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ margin: 24, overflow: "auto" }}>
|
<div style={{ margin: 24, overflow: "auto" }}>
|
||||||
<H1 className={skeleton ? Classes.SKELETON : null}>
|
<H1>{title ?? datasetTitle}</H1>
|
||||||
{title ?? datasetTitle}
|
{renderContributors(contributors, affiliations)}
|
||||||
</H1>
|
{renderDOILink("DOI", doi)}
|
||||||
{renderContributors(contributors, affiliations, skeleton)}
|
{renderDOILink("Preprint DOI", preprintDOI)}
|
||||||
{renderDOILink("DOI", doi, skeleton)}
|
{renderOrganism(organism)}
|
||||||
{renderDOILink("Preprint DOI", preprintDOI, skeleton)}
|
{renderSingleValueCategories(singleValueCategories)}
|
||||||
{renderOrganism(organism, skeleton)}
|
{renderLinks(projectLinks, aboutURL)}
|
||||||
{renderSingleValueCategories(singleValueCategories, skeleton)}
|
|
||||||
{renderLinks(projectLinks, aboutURL, skeleton)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user