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