mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-26 10:58:12 +08:00
Create / edit geneset description (#2139)
* geneset description add * edit geneset description * default state for desc * remove log * naming, todo * check for both dup name and desc * fixes * do not store gene set modal state in history stack * Update createGenesetDialogue.js * Update editGenesetNameDialogue.js Co-authored-by: bkmartinjr <bruce@chanzuckerberg.com>
This commit is contained in:
co-authored by
bkmartinjr
parent
99a795a688
commit
876ceb4d8b
@@ -276,7 +276,7 @@ describe("geneset: update", () => {
|
||||
},
|
||||
}
|
||||
);
|
||||
}).toThrow("update specified existing name");
|
||||
}).toThrow("update specified existing name and description");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ class AnnoDialog extends React.PureComponent {
|
||||
>
|
||||
{errorMessage}
|
||||
</p>
|
||||
{/* we might rename, secondary button and secondary input are not related */}
|
||||
{secondaryInstructions && (
|
||||
<p style={{ marginTop: secondaryInstructions ? 20 : 0 }}>
|
||||
{secondaryInstructions}
|
||||
@@ -62,6 +63,7 @@ class AnnoDialog extends React.PureComponent {
|
||||
<Tooltip content={cancelTooltipContent}>
|
||||
<Button onClick={handleCancel}>Cancel</Button>
|
||||
</Tooltip>
|
||||
{/* we might rename, secondary button and secondary input are not related */}
|
||||
{handleSecondaryButtonSubmit && secondaryButtonText ? (
|
||||
<Button
|
||||
onClick={handleSecondaryButtonSubmit}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Tooltip, Position, Switch } from "@blueprintjs/core";
|
||||
import { Position, Switch } from "@blueprintjs/core";
|
||||
import { Tooltip2 } from "@blueprintjs/popover2";
|
||||
import { FaChevronRight, FaChevronDown } from "react-icons/fa";
|
||||
import actions from "../../actions";
|
||||
import Gene from "./gene";
|
||||
@@ -128,7 +129,7 @@ class GeneSet extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { setName, setGenes } = this.props;
|
||||
const { setName, setGenes, genesetDescription } = this.props;
|
||||
const { isOpen, toggleSummaryHisto } = this.state;
|
||||
const genesetNameLengthVisible = 120; /* this magic number determines how much of a long geneset name we see */
|
||||
const genesetIsEmpty = setGenes.length === 0;
|
||||
@@ -156,7 +157,11 @@ class GeneSet extends React.Component {
|
||||
}}
|
||||
onClick={this.onGenesetMenuClick}
|
||||
>
|
||||
<Truncate>
|
||||
<Truncate
|
||||
tooltipAddendum={
|
||||
genesetDescription ? `: ${genesetDescription}` : ""
|
||||
}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
maxWidth: globals.leftSidebarWidth - genesetNameLengthVisible,
|
||||
@@ -185,7 +190,7 @@ class GeneSet extends React.Component {
|
||||
|
||||
<div style={{ marginLeft: 15, marginTop: 5, marginRight: 0 }}>
|
||||
{isOpen && !genesetIsEmpty && (
|
||||
<Tooltip
|
||||
<Tooltip2
|
||||
content="Aggregate all genes in this geneset, and allow coloring by and selecting on the entire set."
|
||||
position={Position.BOTTOM_RIGHT}
|
||||
usePortal
|
||||
@@ -204,7 +209,7 @@ class GeneSet extends React.Component {
|
||||
innerLabelChecked="on"
|
||||
onChange={this.toggleSummaryHisto}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Tooltip2>
|
||||
)}
|
||||
|
||||
{isOpen && genesetIsEmpty && (
|
||||
@@ -223,7 +228,10 @@ class GeneSet extends React.Component {
|
||||
setGenes={setGenes}
|
||||
/>
|
||||
))}
|
||||
<EditGenesetNameDialogue parentGeneset={setName} />
|
||||
<EditGenesetNameDialogue
|
||||
parentGeneset={setName}
|
||||
parentGenesetDescription={genesetDescription}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ class GeneExpression extends React.Component {
|
||||
setGenes={Array.from(geneset.genes.keys())}
|
||||
setGenesWithDescriptions={geneset.genes}
|
||||
setName={name}
|
||||
genesetDescription={geneset.genesetDescription}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import pull from "lodash.pull";
|
||||
import uniq from "lodash.uniq";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import AnnoDialog from "../../annoDialog";
|
||||
import { Button, Dialog, Classes, Colors } from "@blueprintjs/core";
|
||||
import { Tooltip2 } from "@blueprintjs/popover2";
|
||||
import LabelInput from "../../labelInput";
|
||||
import actions from "../../../actions";
|
||||
|
||||
@@ -20,6 +21,7 @@ class CreateGenesetDialogue extends React.PureComponent {
|
||||
this.state = {
|
||||
genesetName: "",
|
||||
genesToPopulateGeneset: "",
|
||||
genesetDescription: "",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,6 +30,7 @@ class CreateGenesetDialogue extends React.PureComponent {
|
||||
this.setState({
|
||||
genesetName: "",
|
||||
genesToPopulateGeneset: "",
|
||||
genesetDescription: "",
|
||||
});
|
||||
dispatch({
|
||||
type: "geneset: disable create geneset mode",
|
||||
@@ -37,12 +40,16 @@ class CreateGenesetDialogue extends React.PureComponent {
|
||||
|
||||
createGeneset = (e) => {
|
||||
const { dispatch } = this.props;
|
||||
const { genesetName, genesToPopulateGeneset } = this.state;
|
||||
const {
|
||||
genesetName,
|
||||
genesToPopulateGeneset,
|
||||
genesetDescription,
|
||||
} = this.state;
|
||||
|
||||
dispatch({
|
||||
type: "geneset: create",
|
||||
genesetName,
|
||||
genesetDescription: "",
|
||||
genesetDescription,
|
||||
});
|
||||
if (genesToPopulateGeneset) {
|
||||
const genesTmpHardcodedFormat = [];
|
||||
@@ -82,10 +89,14 @@ class CreateGenesetDialogue extends React.PureComponent {
|
||||
this.setState({ genesToPopulateGeneset: e });
|
||||
};
|
||||
|
||||
handleDescriptionInputChange = (e) => {
|
||||
this.setState({ genesetDescription: e });
|
||||
};
|
||||
|
||||
instruction = (genesetName, genesets) => {
|
||||
return genesets.has(genesetName)
|
||||
? "Geneset name must be unique."
|
||||
: "New, unique geneset name";
|
||||
? "Gene set name must be unique."
|
||||
: "New, unique gene set name";
|
||||
};
|
||||
|
||||
validate = (genesetName, genesets) => {
|
||||
@@ -98,47 +109,94 @@ class CreateGenesetDialogue extends React.PureComponent {
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnnoDialog
|
||||
isActive={genesetsUI.createGenesetModeActive}
|
||||
inputProps={{
|
||||
"data-testid": `${metadataField}:create-geneset-dialog`,
|
||||
}}
|
||||
primaryButtonProps={{
|
||||
"data-testid": `${metadataField}:submit-geneset`,
|
||||
}}
|
||||
<Dialog
|
||||
icon="tag"
|
||||
title="Create gene set"
|
||||
instruction={this.instruction(genesetName, genesets)}
|
||||
cancelTooltipContent="Close this dialog without creating a new gene set."
|
||||
primaryButtonText="Create gene set"
|
||||
text={genesetName}
|
||||
validationError={this.validate(genesetName, genesets)}
|
||||
annoInput={
|
||||
<LabelInput
|
||||
onChange={this.handleChange}
|
||||
inputProps={{
|
||||
"data-testid": "create-geneset-modal",
|
||||
leftIcon: "manually-entered-data",
|
||||
intent: "none",
|
||||
autoFocus: true,
|
||||
}}
|
||||
newLabelMessage="Create gene set"
|
||||
/>
|
||||
}
|
||||
secondaryInstructions="Optionally add a list of comma separated genes to populate the gene set"
|
||||
secondaryInput={
|
||||
<LabelInput
|
||||
onChange={this.handleGenesetInputChange}
|
||||
inputProps={{
|
||||
"data-testid": "add-genes",
|
||||
intent: "none",
|
||||
autoFocus: false,
|
||||
}}
|
||||
newLabelMessage="populate geneset with genes"
|
||||
/>
|
||||
}
|
||||
handleSubmit={this.createGeneset}
|
||||
handleCancel={this.disableCreateGenesetMode}
|
||||
/>
|
||||
isOpen={genesetsUI.createGenesetModeActive}
|
||||
onClose={this.disableCreateGenesetMode}
|
||||
>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<p>{this.instruction(genesetName, genesets)}</p>
|
||||
<LabelInput
|
||||
onChange={this.handleChange}
|
||||
inputProps={{
|
||||
"data-testid": "create-geneset-modal",
|
||||
leftIcon: "manually-entered-data",
|
||||
intent: "none",
|
||||
autoFocus: true,
|
||||
}}
|
||||
newLabelMessage="Create gene set"
|
||||
/>
|
||||
<p
|
||||
style={{
|
||||
marginTop: 7,
|
||||
visibility: this.validate(genesetName, genesets)
|
||||
? "visible"
|
||||
: "hidden",
|
||||
color: Colors.ORANGE3,
|
||||
}}
|
||||
>
|
||||
{this.genesetNameError()}
|
||||
</p>
|
||||
<p style={{ marginTop: 20 }}>
|
||||
Optionally add a{" "}
|
||||
<span style={{ fontWeight: 700 }}>description</span> for this
|
||||
gene set
|
||||
</p>
|
||||
<LabelInput
|
||||
onChange={this.handleDescriptionInputChange}
|
||||
inputProps={{
|
||||
"data-testid": "add-geneset-description",
|
||||
intent: "none",
|
||||
autoFocus: false,
|
||||
}}
|
||||
newLabelMessage="Add geneset description"
|
||||
/>
|
||||
|
||||
<p style={{ marginTop: 20 }}>
|
||||
Optionally add a list of comma separated{" "}
|
||||
<span style={{ fontWeight: 700 }}>genes</span> to populate the
|
||||
gene set
|
||||
</p>
|
||||
<LabelInput
|
||||
onChange={this.handleGenesetInputChange}
|
||||
inputProps={{
|
||||
"data-testid": "add-genes",
|
||||
intent: "none",
|
||||
autoFocus: false,
|
||||
}}
|
||||
newLabelMessage="populate geneset with genes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
<Tooltip2 content="Close this dialog without creating a new gene set.">
|
||||
<Button onClick={this.disableCreateGenesetMode}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Tooltip2>
|
||||
<Button
|
||||
data-testid={`${metadataField}:submit-geneset`}
|
||||
onClick={this.createGeneset}
|
||||
disabled={
|
||||
!genesetName || this.validate(genesetName, genesets)
|
||||
}
|
||||
intent="primary"
|
||||
type="submit"
|
||||
>
|
||||
Create gene set
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,14 +14,16 @@ class RenameGeneset extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
genesetName: props.parentGeneset,
|
||||
newGenesetName: props.parentGeneset,
|
||||
newGenesetDescription: props.parentGenesetDescription,
|
||||
};
|
||||
}
|
||||
|
||||
disableEditGenesetNameMode = (e) => {
|
||||
const { dispatch } = this.props;
|
||||
this.setState({
|
||||
genesetName: "",
|
||||
newGenesetName: "",
|
||||
newGenesetDescription: "",
|
||||
});
|
||||
dispatch({
|
||||
type: "geneset: disable rename geneset mode",
|
||||
@@ -31,12 +33,15 @@ class RenameGeneset extends React.PureComponent {
|
||||
|
||||
renameGeneset = (e) => {
|
||||
const { dispatch, genesetsUI } = this.props;
|
||||
const { genesetName } = this.state;
|
||||
const { newGenesetName, newGenesetDescription } = this.state;
|
||||
|
||||
dispatch({
|
||||
type: "geneset: update",
|
||||
genesetName: genesetsUI.isEditingGenesetName,
|
||||
update: { genesetName },
|
||||
update: {
|
||||
genesetName: newGenesetName,
|
||||
genesetDescription: newGenesetDescription,
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: "geneset: disable rename geneset mode",
|
||||
@@ -49,12 +54,16 @@ class RenameGeneset extends React.PureComponent {
|
||||
};
|
||||
|
||||
handleChange = (e) => {
|
||||
this.setState({ genesetName: e });
|
||||
this.setState({ newGenesetName: e });
|
||||
};
|
||||
|
||||
handleChangeDescription = (e) => {
|
||||
this.setState({ newGenesetDescription: e });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { genesetName } = this.state;
|
||||
const { genesetsUI, parentGeneset } = this.props;
|
||||
const { newGenesetName, newGenesetDescription } = this.state;
|
||||
const { genesetsUI, parentGeneset, parentGenesetDescription } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -66,15 +75,19 @@ class RenameGeneset extends React.PureComponent {
|
||||
primaryButtonProps={{
|
||||
"data-testid": `${genesetsUI.isEditingGenesetName}:submit-geneset`,
|
||||
}}
|
||||
title="Rename gene set"
|
||||
title="Edit gene set name and description"
|
||||
instruction={`Rename ${genesetsUI.isEditingGenesetName}`}
|
||||
cancelTooltipContent="Close this dialog without renaming the gene set."
|
||||
primaryButtonText="Rename gene set"
|
||||
text={genesetName}
|
||||
validationError={genesetsUI.isEditingGenesetName === genesetName}
|
||||
primaryButtonText="Edit gene set name and description"
|
||||
text={newGenesetName}
|
||||
secondaryText={newGenesetDescription}
|
||||
validationError={
|
||||
genesetsUI.isEditingGenesetName === newGenesetName &&
|
||||
parentGenesetDescription === newGenesetDescription
|
||||
}
|
||||
annoInput={
|
||||
<LabelInput
|
||||
label={genesetName}
|
||||
label={newGenesetName}
|
||||
onChange={this.handleChange}
|
||||
inputProps={{
|
||||
"data-testid": "rename-geneset-modal",
|
||||
@@ -82,7 +95,16 @@ class RenameGeneset extends React.PureComponent {
|
||||
intent: "none",
|
||||
autoFocus: true,
|
||||
}}
|
||||
newLabelMessage="Rename gene set"
|
||||
/>
|
||||
}
|
||||
secondaryInstructions="Edit description"
|
||||
secondaryInput={
|
||||
<LabelInput
|
||||
label={newGenesetDescription}
|
||||
onChange={this.handleChangeDescription}
|
||||
inputProps={{ "data-testid": "change geneset description" }}
|
||||
intent="none"
|
||||
autoFocus={false}
|
||||
/>
|
||||
}
|
||||
handleSubmit={this.renameGeneset}
|
||||
|
||||
@@ -82,7 +82,7 @@ class GenesetMenus extends React.PureComponent {
|
||||
data-testclass="activateEditGenesetNameMode"
|
||||
data-testid={`${geneset}:edit-genesetName-mode`}
|
||||
onClick={this.activateEditGenesetNameMode}
|
||||
text="Edit gene set name"
|
||||
text="Edit gene set name and description"
|
||||
/>
|
||||
<MenuItem
|
||||
icon="trash"
|
||||
|
||||
@@ -149,6 +149,7 @@ const GeneSets = (
|
||||
*/
|
||||
case "geneset: update": {
|
||||
const { genesetName, update } = action;
|
||||
|
||||
if (
|
||||
typeof genesetName !== "string" ||
|
||||
!genesetName ||
|
||||
@@ -157,8 +158,18 @@ const GeneSets = (
|
||||
throw new Error(
|
||||
"geneset: update -- geneset name unspecified or does not exist."
|
||||
);
|
||||
if (state.genesets.has(update.genesetName))
|
||||
throw new Error("geneset: update -- update specified existing name.");
|
||||
|
||||
/* now that we've confirmed the gene set exists, check for duplicates */
|
||||
const genesetNameIsDuplicate = state.genesets.has(update.genesetName);
|
||||
const descriptionIsDuplicate =
|
||||
state.genesets.get(update.genesetName) &&
|
||||
state.genesets.get(update.genesetName).genesetDescription ===
|
||||
update.genesetDescription;
|
||||
|
||||
if (genesetNameIsDuplicate && descriptionIsDuplicate)
|
||||
throw new Error(
|
||||
"geneset: update -- update specified existing name and description."
|
||||
);
|
||||
|
||||
const prevGs = state.genesets.get(genesetName);
|
||||
const newGs = {
|
||||
|
||||
@@ -60,7 +60,6 @@ const Reducer = undoable(
|
||||
"layoutChoice",
|
||||
"centroidLabels",
|
||||
"genesets",
|
||||
"genesetsUI",
|
||||
"annotations",
|
||||
],
|
||||
undoableConfig
|
||||
|
||||
Reference in New Issue
Block a user