mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-08 03:48:12 +08:00
feat: frontend geneset validation (#2258)
* add geneset name validation * validate genes before addition * display error messages
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { postUserErrorToast } from "../components/framework/toasters";
|
||||||
/*
|
/*
|
||||||
Action creators for gene sets
|
Action creators for gene sets
|
||||||
|
|
||||||
@@ -39,9 +40,25 @@ export const genesetDelete = (genesetName) => (dispatch, getState) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const genesetAddGenes = (genesetName, genes) => (dispatch, getState) => {
|
export const genesetAddGenes = (genesetName, genes) => async (
|
||||||
|
dispatch,
|
||||||
|
getState
|
||||||
|
) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { obsCrossfilter: prevObsCrossfilter } = state;
|
const { obsCrossfilter: prevObsCrossfilter, annoMatrix } = state;
|
||||||
|
const { schema } = annoMatrix;
|
||||||
|
const varIndex = schema.annotations.var.index;
|
||||||
|
const df = await annoMatrix.fetch("var", varIndex);
|
||||||
|
const geneNames = df.col(varIndex).asArray();
|
||||||
|
genes = genes.reduce((acc, gene) => {
|
||||||
|
if (geneNames.indexOf(gene.geneSymbol) === -1) {
|
||||||
|
postUserErrorToast(
|
||||||
|
`${gene.geneSymbol} doesn't appear to be a valid gene name.`
|
||||||
|
);
|
||||||
|
} else acc.push(gene);
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
const obsCrossfilter = dropGenesetSummaryDimension(
|
const obsCrossfilter = dropGenesetSummaryDimension(
|
||||||
prevObsCrossfilter,
|
prevObsCrossfilter,
|
||||||
state,
|
state,
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class AddGeneToGenesetDialogue extends React.PureComponent {
|
|||||||
"data-testid": `${geneset}:submit-label`,
|
"data-testid": `${geneset}:submit-label`,
|
||||||
}}
|
}}
|
||||||
title="Add genes to gene set"
|
title="Add genes to gene set"
|
||||||
instruction={`Add gene to ${geneset}`}
|
instruction={`Add genes to ${geneset}`}
|
||||||
cancelTooltipContent="Close this dialog without adding genes to gene set."
|
cancelTooltipContent="Close this dialog without adding genes to gene set."
|
||||||
primaryButtonText="Add genes"
|
primaryButtonText="Add genes"
|
||||||
text={genesToAdd}
|
text={genesToAdd}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class CreateGenesetDialogue extends React.PureComponent {
|
|||||||
genesetName: "",
|
genesetName: "",
|
||||||
genesToPopulateGeneset: "",
|
genesToPopulateGeneset: "",
|
||||||
genesetDescription: "",
|
genesetDescription: "",
|
||||||
|
nameErrorMessage: "",
|
||||||
});
|
});
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "geneset: disable create geneset mode",
|
type: "geneset: disable create geneset mode",
|
||||||
@@ -48,7 +49,7 @@ class CreateGenesetDialogue extends React.PureComponent {
|
|||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "geneset: create",
|
type: "geneset: create",
|
||||||
genesetName,
|
genesetName: genesetName.trim(),
|
||||||
genesetDescription,
|
genesetDescription,
|
||||||
});
|
});
|
||||||
if (genesToPopulateGeneset) {
|
if (genesToPopulateGeneset) {
|
||||||
@@ -82,7 +83,9 @@ class CreateGenesetDialogue extends React.PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
handleChange = (e) => {
|
handleChange = (e) => {
|
||||||
|
const { genesets } = this.props;
|
||||||
this.setState({ genesetName: e });
|
this.setState({ genesetName: e });
|
||||||
|
this.validate(e, genesets);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleGenesetInputChange = (e) => {
|
handleGenesetInputChange = (e) => {
|
||||||
@@ -100,11 +103,32 @@ class CreateGenesetDialogue extends React.PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
validate = (genesetName, genesets) => {
|
validate = (genesetName, genesets) => {
|
||||||
return genesets.has(genesetName);
|
if (genesets.has(genesetName)) {
|
||||||
|
this.setState({
|
||||||
|
nameErrorMessage: "There is already a geneset with that name",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
genesetName.length > 1 &&
|
||||||
|
// eslint-disable-next-line no-control-regex -- unicode 0-31 127-65535
|
||||||
|
genesetName.match(/^[\u0000-\u001F\u007F-\uFFFF]|[ ]{2,}/g)?.length
|
||||||
|
) {
|
||||||
|
this.setState({
|
||||||
|
nameErrorMessage:
|
||||||
|
"Gene set names can only contain alphanumeric characters and the following special characters: ! ” # $ % ’ ( ) * + , - . / : ; < = > ? @ ] ^ _ ` | ~",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
nameErrorMessage: "",
|
||||||
|
});
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { genesetName } = this.state;
|
const { genesetName, nameErrorMessage } = this.state;
|
||||||
const { metadataField, genesetsUI, genesets } = this.props;
|
const { metadataField, genesetsUI, genesets } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -136,13 +160,11 @@ class CreateGenesetDialogue extends React.PureComponent {
|
|||||||
<p
|
<p
|
||||||
style={{
|
style={{
|
||||||
marginTop: 7,
|
marginTop: 7,
|
||||||
visibility: this.validate(genesetName, genesets)
|
visibility: nameErrorMessage !== "" ? "visible" : "hidden",
|
||||||
? "visible"
|
|
||||||
: "hidden",
|
|
||||||
color: Colors.ORANGE3,
|
color: Colors.ORANGE3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{this.genesetNameError()}
|
{nameErrorMessage}
|
||||||
</p>
|
</p>
|
||||||
<p style={{ marginTop: 20 }}>
|
<p style={{ marginTop: 20 }}>
|
||||||
Optionally add a{" "}
|
Optionally add a{" "}
|
||||||
@@ -185,9 +207,7 @@ class CreateGenesetDialogue extends React.PureComponent {
|
|||||||
<Button
|
<Button
|
||||||
data-testid={`${metadataField}:submit-geneset`}
|
data-testid={`${metadataField}:submit-geneset`}
|
||||||
onClick={this.createGeneset}
|
onClick={this.createGeneset}
|
||||||
disabled={
|
disabled={nameErrorMessage !== ""}
|
||||||
!genesetName || this.validate(genesetName, genesets)
|
|
||||||
}
|
|
||||||
intent="primary"
|
intent="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import LabelInput from "../../labelInput";
|
|||||||
ontology: state.ontology,
|
ontology: state.ontology,
|
||||||
obsCrossfilter: state.obsCrossfilter,
|
obsCrossfilter: state.obsCrossfilter,
|
||||||
genesetsUI: state.genesetsUI,
|
genesetsUI: state.genesetsUI,
|
||||||
|
genesets: state.genesets.genesets,
|
||||||
}))
|
}))
|
||||||
class RenameGeneset extends React.PureComponent {
|
class RenameGeneset extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -61,9 +62,23 @@ class RenameGeneset extends React.PureComponent {
|
|||||||
this.setState({ newGenesetDescription: e });
|
this.setState({ newGenesetDescription: e });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
validate = (genesetName, genesets) => {
|
||||||
|
return (
|
||||||
|
!genesets.has(genesetName) &&
|
||||||
|
// eslint-disable-next-line no-control-regex -- unicode 0-31 127-65535
|
||||||
|
genesetName.match(/^\s|[\u0000-\u001F\u007F-\uFFFF]|[ ]{2,}|^$|\s$/g)
|
||||||
|
?.length
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { newGenesetName, newGenesetDescription } = this.state;
|
const { newGenesetName, newGenesetDescription } = this.state;
|
||||||
const { genesetsUI, parentGeneset, parentGenesetDescription } = this.props;
|
const {
|
||||||
|
genesetsUI,
|
||||||
|
parentGeneset,
|
||||||
|
parentGenesetDescription,
|
||||||
|
genesets,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -82,7 +97,7 @@ class RenameGeneset extends React.PureComponent {
|
|||||||
text={newGenesetName}
|
text={newGenesetName}
|
||||||
secondaryText={newGenesetDescription}
|
secondaryText={newGenesetDescription}
|
||||||
validationError={
|
validationError={
|
||||||
genesetsUI.isEditingGenesetName === newGenesetName &&
|
this.validate(newGenesetName, genesets) &&
|
||||||
parentGenesetDescription === newGenesetDescription
|
parentGenesetDescription === newGenesetDescription
|
||||||
}
|
}
|
||||||
annoInput={
|
annoInput={
|
||||||
|
|||||||
Reference in New Issue
Block a user