mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-06 20:18:11 +08:00
feat: remove info drawer and reintroduce singleton categories (#2421)
* refactor: drop non-session auth from frontend * remove dataset drawer and reintroduce singleton values
This commit is contained in:
@@ -494,12 +494,23 @@ const CategoryRender = React.memo(
|
|||||||
*/
|
*/
|
||||||
const { numCategoryValues } = categorySummary;
|
const { numCategoryValues } = categorySummary;
|
||||||
const isSingularValue = !isUserAnno && numCategoryValues === 1;
|
const isSingularValue = !isUserAnno && numCategoryValues === 1;
|
||||||
|
|
||||||
if (isSingularValue) {
|
if (isSingularValue) {
|
||||||
/*
|
/*
|
||||||
Entire category has a single value, special case.
|
Entire category has a single value, special case.
|
||||||
*/
|
*/
|
||||||
return null;
|
const theOneValue = categorySummary.categoryValues[0];
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: 10, marginTop: 4 }}>
|
||||||
|
<Truncate>
|
||||||
|
<span style={{ maxWidth: 150, fontWeight: 700 }}>
|
||||||
|
{metadataField}
|
||||||
|
</span>
|
||||||
|
</Truncate>
|
||||||
|
<Truncate>
|
||||||
|
<span style={{ maxWidth: 150 }}>{`: ${theOneValue}`}</span>
|
||||||
|
</Truncate>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -193,18 +193,17 @@ class Categories extends React.Component {
|
|||||||
|
|
||||||
{/* READ ONLY CATEGORICAL FIELDS */}
|
{/* READ ONLY CATEGORICAL FIELDS */}
|
||||||
{/* this is duplicative but flat, could be abstracted */}
|
{/* this is duplicative but flat, could be abstracted */}
|
||||||
{allCategoryNames.map((catName) =>
|
{allCategoryNames.map(
|
||||||
!schema.annotations.obsByName[catName].writable &&
|
(catName) =>
|
||||||
(schema.annotations.obsByName[catName].categories?.length > 1 ||
|
!schema.annotations.obsByName[catName].writable && (
|
||||||
!schema.annotations.obsByName[catName].categories) ? (
|
<Category
|
||||||
<Category
|
key={catName}
|
||||||
key={catName}
|
metadataField={catName}
|
||||||
metadataField={catName}
|
onExpansionChange={this.onExpansionChange}
|
||||||
onExpansionChange={this.onExpansionChange}
|
isExpanded={expandedCats.has(catName)}
|
||||||
isExpanded={expandedCats.has(catName)}
|
createAnnoModeActive={createAnnoModeActive}
|
||||||
createAnnoModeActive={createAnnoModeActive}
|
/>
|
||||||
/>
|
)
|
||||||
) : null
|
|
||||||
)}
|
)}
|
||||||
{/* WRITEABLE FIELDS */}
|
{/* WRITEABLE FIELDS */}
|
||||||
{allCategoryNames.map((catName) =>
|
{allCategoryNames.map((catName) =>
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
import React, { PureComponent } from "react";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { Drawer } from "@blueprintjs/core";
|
|
||||||
|
|
||||||
import InfoFormat from "./infoFormat";
|
|
||||||
import { selectableCategoryNames } from "../../util/stateManager/controlsHelpers";
|
|
||||||
|
|
||||||
@connect((state) => ({
|
|
||||||
schema: state.annoMatrix.schema,
|
|
||||||
datasetTitle: state.config?.displayNames?.dataset ?? "",
|
|
||||||
aboutURL: state.config?.links?.["about-dataset"],
|
|
||||||
isOpen: state.controls.datasetDrawer,
|
|
||||||
dataPortalProps: state.config?.corpora_props,
|
|
||||||
}))
|
|
||||||
class InfoDrawer extends PureComponent {
|
|
||||||
handleClose = () => {
|
|
||||||
const { dispatch } = this.props;
|
|
||||||
|
|
||||||
dispatch({ type: "toggle dataset drawer" });
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
position,
|
|
||||||
aboutURL,
|
|
||||||
datasetTitle,
|
|
||||||
schema,
|
|
||||||
isOpen,
|
|
||||||
dataPortalProps,
|
|
||||||
} = 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 (
|
|
||||||
<Drawer
|
|
||||||
title="Dataset Overview"
|
|
||||||
onClose={this.handleClose}
|
|
||||||
{...{ isOpen, position }}
|
|
||||||
>
|
|
||||||
<InfoFormat
|
|
||||||
{...{
|
|
||||||
datasetTitle,
|
|
||||||
aboutURL,
|
|
||||||
singleValueCategories,
|
|
||||||
dataPortalProps: dataPortalProps ?? {},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Drawer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default InfoDrawer;
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
import { H3, H1, UL, HTMLTable, Classes } from "@blueprintjs/core";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
const renderContributors = (contributors, affiliations) => {
|
|
||||||
// eslint-disable-next-line no-constant-condition -- Temp removed contributor section to avoid publishing PII
|
|
||||||
if (!contributors || contributors.length === 0 || true) return null;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<H3>Contributors</H3>
|
|
||||||
<p>
|
|
||||||
{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)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// generates a list of unique institutions by order of appearance in contributors
|
|
||||||
const buildAffiliations = (contributors = []) => {
|
|
||||||
const affiliations = [];
|
|
||||||
contributors.forEach((contributor) => {
|
|
||||||
const { institution } = contributor;
|
|
||||||
if (affiliations.indexOf(institution) === -1) {
|
|
||||||
affiliations.push(institution);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return affiliations;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderAffiliations = (affiliations) => {
|
|
||||||
if (affiliations.length === 0) return null;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<H3>Affiliations</H3>
|
|
||||||
<UL>
|
|
||||||
{affiliations.map((item, index) => (
|
|
||||||
<div key={item}>
|
|
||||||
<sup>{index + 1}</sup>
|
|
||||||
{" "}
|
|
||||||
{item}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</UL>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderDOILink = (type, doi) => {
|
|
||||||
if (!doi) return null;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<H3>{type}</H3>
|
|
||||||
<p>
|
|
||||||
<a href={doi} target="_blank" rel="noopener">
|
|
||||||
{doi}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ONTOLOGY_KEY = "ontology_term_id";
|
|
||||||
// Render list of metadata attributes found in categorical field
|
|
||||||
const renderDatasetMetadata = (singleValueCategories, corporaMetadata) => {
|
|
||||||
if (singleValueCategories.size === 0) return null;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<H3>Dataset Metadata</H3>
|
|
||||||
<HTMLTable
|
|
||||||
striped
|
|
||||||
condensed
|
|
||||||
style={{ display: "block", width: "100%", overflowX: "auto" }}
|
|
||||||
>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Field</th>
|
|
||||||
<th>Label</th>
|
|
||||||
<th>Ontology ID</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{Object.entries(corporaMetadata).map(([key, value]) => (
|
|
||||||
<tr {...{ key }}>
|
|
||||||
<td>{`${key}:`}</td>
|
|
||||||
<td>{value}</td>
|
|
||||||
<td />
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
{Array.from(singleValueCategories).reduce((elems, pair) => {
|
|
||||||
const [category, value] = pair;
|
|
||||||
// If the value is empty skip it
|
|
||||||
if (!value) return elems;
|
|
||||||
|
|
||||||
// If this category is a ontology term, let's add its value to the previous node
|
|
||||||
if (String(category).includes(ONTOLOGY_KEY)) {
|
|
||||||
const prevElem = elems.pop();
|
|
||||||
const newChildren = [...prevElem.props.children];
|
|
||||||
newChildren.splice(2, 1, [<td key="ontology">{value}</td>]);
|
|
||||||
// Props aren't extensible so we must clone and alter the component to append the new child
|
|
||||||
elems.push(
|
|
||||||
React.cloneElement(prevElem, prevElem.props, newChildren)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Create the list item
|
|
||||||
elems.push(
|
|
||||||
<tr key={category}>
|
|
||||||
<td>{`${category}:`}</td>
|
|
||||||
<td>{value}</td>
|
|
||||||
<td />
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return elems;
|
|
||||||
}, [])}
|
|
||||||
</tbody>
|
|
||||||
</HTMLTable>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Renders any links found in the config where link_type is not "SUMMARY"
|
|
||||||
// If there are no links in the config, render the aboutURL
|
|
||||||
const renderLinks = (projectLinks, aboutURL) => {
|
|
||||||
if (!projectLinks && !aboutURL) return null;
|
|
||||||
if (projectLinks)
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<H3>Project Links</H3>
|
|
||||||
<UL>
|
|
||||||
{projectLinks.map((link) => {
|
|
||||||
if (link.link_type === "SUMMARY") return null;
|
|
||||||
return (
|
|
||||||
<li key={link.link_name}>
|
|
||||||
<a href={link.link_url} target="_blank" rel="noopener">
|
|
||||||
{link.link_name}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</UL>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<H3>More Info</H3>
|
|
||||||
<p>
|
|
||||||
<a href={aboutURL} target="_blank" rel="noopener">
|
|
||||||
{aboutURL}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const InfoFormat = React.memo(
|
|
||||||
({ datasetTitle, singleValueCategories, aboutURL, dataPortalProps = {} }) => {
|
|
||||||
if (
|
|
||||||
["1.0.0", "1.1.0"].indexOf(
|
|
||||||
dataPortalProps.version?.corpora_schema_version
|
|
||||||
) === -1
|
|
||||||
) {
|
|
||||||
dataPortalProps = {};
|
|
||||||
}
|
|
||||||
const {
|
|
||||||
title,
|
|
||||||
publication_doi: doi,
|
|
||||||
preprint_doi: preprintDOI,
|
|
||||||
organism,
|
|
||||||
contributors,
|
|
||||||
project_links: projectLinks,
|
|
||||||
} = dataPortalProps;
|
|
||||||
|
|
||||||
const affiliations = buildAffiliations(contributors);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={Classes.DIALOG_BODY}>
|
|
||||||
<div className={Classes.DIALOG_BODY}>
|
|
||||||
<H1>{title ?? datasetTitle}</H1>
|
|
||||||
{renderContributors(contributors, affiliations)}
|
|
||||||
{renderDatasetMetadata(singleValueCategories, { organism })}
|
|
||||||
{renderLinks(projectLinks, aboutURL)}
|
|
||||||
{renderDOILink("DOI", doi)}
|
|
||||||
{renderDOILink("Preprint DOI", preprintDOI)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default InfoFormat;
|
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Button } from "@blueprintjs/core";
|
|
||||||
|
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import Logo from "../framework/logo";
|
import Logo from "../framework/logo";
|
||||||
import Truncate from "../util/truncate";
|
import Truncate from "../util/truncate";
|
||||||
import InfoDrawer from "../infoDrawer/infoDrawer";
|
|
||||||
import InformationMenu from "./infoMenu";
|
import InformationMenu from "./infoMenu";
|
||||||
|
|
||||||
const DATASET_TITLE_FONT_SIZE = 14;
|
const DATASET_TITLE_FONT_SIZE = 14;
|
||||||
@@ -25,11 +23,6 @@ const DATASET_TITLE_FONT_SIZE = 14;
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
class LeftSideBar extends React.Component {
|
class LeftSideBar extends React.Component {
|
||||||
handleClick = () => {
|
|
||||||
const { dispatch } = this.props;
|
|
||||||
dispatch({ type: "toggle dataset drawer" });
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
datasetTitle,
|
datasetTitle,
|
||||||
@@ -82,22 +75,19 @@ class LeftSideBar extends React.Component {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginRight: 5, height: "100%" }}>
|
<div style={{ marginRight: 5, height: "100%" }}>
|
||||||
<Button
|
<span
|
||||||
minimal
|
minimal
|
||||||
style={{
|
style={{
|
||||||
fontSize: DATASET_TITLE_FONT_SIZE,
|
fontSize: DATASET_TITLE_FONT_SIZE,
|
||||||
position: "relative",
|
padding: "5px 10px",
|
||||||
top: -1,
|
|
||||||
}}
|
}}
|
||||||
onClick={this.handleClick}
|
|
||||||
>
|
>
|
||||||
<Truncate>
|
<Truncate>
|
||||||
<span style={{ maxWidth: 155 }} data-testid="header">
|
<span style={{ maxWidth: 155 }} data-testid="header">
|
||||||
{title ?? datasetTitle}
|
{title ?? datasetTitle}
|
||||||
</span>
|
</span>
|
||||||
</Truncate>
|
</Truncate>
|
||||||
</Button>
|
</span>
|
||||||
<InfoDrawer />
|
|
||||||
<InformationMenu
|
<InformationMenu
|
||||||
{...{
|
{...{
|
||||||
libraryVersions,
|
libraryVersions,
|
||||||
|
|||||||
Vendored
-8
@@ -17,8 +17,6 @@ const Controls = (
|
|||||||
scatterplotXXaccessor: null, // just easier to read
|
scatterplotXXaccessor: null, // just easier to read
|
||||||
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 */,
|
||||||
|
|
||||||
datasetDrawer: false,
|
|
||||||
},
|
},
|
||||||
action
|
action
|
||||||
) => {
|
) => {
|
||||||
@@ -138,12 +136,6 @@ const Controls = (
|
|||||||
scatterplotYYaccessor: null,
|
scatterplotYYaccessor: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**************************
|
|
||||||
Dataset Drawer
|
|
||||||
**************************/
|
|
||||||
case "toggle dataset drawer":
|
|
||||||
return { ...state, datasetDrawer: !state.datasetDrawer };
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user