mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-15 12:47:56 +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:
committed by
GitHub
parent
ef2ab07ca0
commit
f49c3d8fe7
@@ -494,12 +494,23 @@ const CategoryRender = React.memo(
|
||||
*/
|
||||
const { numCategoryValues } = categorySummary;
|
||||
const isSingularValue = !isUserAnno && numCategoryValues === 1;
|
||||
|
||||
if (isSingularValue) {
|
||||
/*
|
||||
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 */}
|
||||
{/* this is duplicative but flat, could be abstracted */}
|
||||
{allCategoryNames.map((catName) =>
|
||||
!schema.annotations.obsByName[catName].writable &&
|
||||
(schema.annotations.obsByName[catName].categories?.length > 1 ||
|
||||
!schema.annotations.obsByName[catName].categories) ? (
|
||||
<Category
|
||||
key={catName}
|
||||
metadataField={catName}
|
||||
onExpansionChange={this.onExpansionChange}
|
||||
isExpanded={expandedCats.has(catName)}
|
||||
createAnnoModeActive={createAnnoModeActive}
|
||||
/>
|
||||
) : null
|
||||
{allCategoryNames.map(
|
||||
(catName) =>
|
||||
!schema.annotations.obsByName[catName].writable && (
|
||||
<Category
|
||||
key={catName}
|
||||
metadataField={catName}
|
||||
onExpansionChange={this.onExpansionChange}
|
||||
isExpanded={expandedCats.has(catName)}
|
||||
createAnnoModeActive={createAnnoModeActive}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
{/* WRITEABLE FIELDS */}
|
||||
{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 { connect } from "react-redux";
|
||||
import { Button } from "@blueprintjs/core";
|
||||
|
||||
import * as globals from "../../globals";
|
||||
import Logo from "../framework/logo";
|
||||
import Truncate from "../util/truncate";
|
||||
import InfoDrawer from "../infoDrawer/infoDrawer";
|
||||
import InformationMenu from "./infoMenu";
|
||||
|
||||
const DATASET_TITLE_FONT_SIZE = 14;
|
||||
@@ -25,11 +23,6 @@ const DATASET_TITLE_FONT_SIZE = 14;
|
||||
};
|
||||
})
|
||||
class LeftSideBar extends React.Component {
|
||||
handleClick = () => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch({ type: "toggle dataset drawer" });
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
datasetTitle,
|
||||
@@ -82,22 +75,19 @@ class LeftSideBar extends React.Component {
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ marginRight: 5, height: "100%" }}>
|
||||
<Button
|
||||
<span
|
||||
minimal
|
||||
style={{
|
||||
fontSize: DATASET_TITLE_FONT_SIZE,
|
||||
position: "relative",
|
||||
top: -1,
|
||||
padding: "5px 10px",
|
||||
}}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<Truncate>
|
||||
<span style={{ maxWidth: 155 }} data-testid="header">
|
||||
{title ?? datasetTitle}
|
||||
</span>
|
||||
</Truncate>
|
||||
</Button>
|
||||
<InfoDrawer />
|
||||
</span>
|
||||
<InformationMenu
|
||||
{...{
|
||||
libraryVersions,
|
||||
|
||||
8
client/src/reducers/controls.js
vendored
8
client/src/reducers/controls.js
vendored
@@ -17,8 +17,6 @@ const Controls = (
|
||||
scatterplotXXaccessor: null, // just easier to read
|
||||
scatterplotYYaccessor: null,
|
||||
graphRenderCounter: 0 /* integer as <Component key={graphRenderCounter} - a change in key forces a remount */,
|
||||
|
||||
datasetDrawer: false,
|
||||
},
|
||||
action
|
||||
) => {
|
||||
@@ -138,12 +136,6 @@ const Controls = (
|
||||
scatterplotYYaccessor: null,
|
||||
};
|
||||
|
||||
/**************************
|
||||
Dataset Drawer
|
||||
**************************/
|
||||
case "toggle dataset drawer":
|
||||
return { ...state, datasetDrawer: !state.datasetDrawer };
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user