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:
Severiano Badajoz
2021-09-20 16:58:55 +00:00
committed by GitHub
parent ef2ab07ca0
commit f49c3d8fe7
6 changed files with 27 additions and 297 deletions
@@ -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>
);
} }
/* /*
+11 -12
View File
@@ -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,
-8
View File
@@ -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;
} }