improve category and label name validation (#1034)

This commit is contained in:
Bruce Martin
2019-11-13 09:40:37 -08:00
committed by GitHub
parent 04c83993d2
commit a52e86a69b
3 changed files with 136 additions and 55 deletions
@@ -14,6 +14,7 @@ import { Select } from "@blueprintjs/select";
import { connect } from "react-redux"; import { connect } from "react-redux";
import * as globals from "../../globals"; import * as globals from "../../globals";
import Category from "./category"; import Category from "./category";
import { AnnotationsHelpers } from "../../util/stateManager";
@connect(state => ({ @connect(state => ({
categoricalSelection: state.categoricalSelection, categoricalSelection: state.categoricalSelection,
@@ -50,13 +51,57 @@ class Categories extends React.Component {
}; };
handleDisableAnnoMode = () => { handleDisableAnnoMode = () => {
this.setState({ createAnnoModeActive: false }); this.setState({
createAnnoModeActive: false,
categoryToDuplicate: null,
newCategoryText: ""
});
}; };
handleModalDuplicateCategorySelection = d => { handleModalDuplicateCategorySelection = d => {
this.setState({ categoryToDuplicate: d }); this.setState({ categoryToDuplicate: d });
}; };
categoryNameError = name => {
/*
return false if this is a LEGAL/acceptable category name,
or return an error type.
*/
const { categoricalSelection } = this.props;
const allCategoryNames = Object.keys(categoricalSelection);
if (allCategoryNames.indexOf(name) !== -1) {
return "duplicate";
}
if (!AnnotationsHelpers.isLegalAnnotationName(name)) {
return "characters";
}
return false;
};
categoryNameErrorMessage = name => {
const err = this.categoryNameError(name);
if (err == "duplicate") {
return (
<span>
<span style={{ fontStyle: "italic" }}>{name}</span> already exists -
no duplicates allowed
</span>
);
}
if (err == "characters") {
return (
<span>
<span style={{ fontStyle: "italic" }}>{name}</span> contains illegal
characters. Hint: use alpha-numeric and underscore
</span>
);
}
return err;
};
render() { render() {
const { const {
createAnnoModeActive, createAnnoModeActive,
@@ -81,30 +126,26 @@ 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 */}
{_.map( {_.map(allCategoryNames, catName =>
allCategoryNames, !schema.annotations.obsByName[catName].writable ? (
catName => <Category
!schema.annotations.obsByName[catName].writable ? ( key={catName}
<Category metadataField={catName}
key={catName} createAnnoModeActive={createAnnoModeActive}
metadataField={catName} isUserAnno={false}
createAnnoModeActive={createAnnoModeActive} />
isUserAnno={false} ) : null
/>
) : null
)} )}
{/* WRITEABLE FIELDS */} {/* WRITEABLE FIELDS */}
{_.map( {_.map(allCategoryNames, catName =>
allCategoryNames, schema.annotations.obsByName[catName].writable ? (
catName => <Category
schema.annotations.obsByName[catName].writable ? ( key={catName}
<Category metadataField={catName}
key={catName} createAnnoModeActive={createAnnoModeActive}
metadataField={catName} isUserAnno
createAnnoModeActive={createAnnoModeActive} />
isUserAnno ) : null
/>
) : null
)} )}
{writableCategoriesEnabled ? ( {writableCategoriesEnabled ? (
<div> <div>
@@ -124,8 +165,10 @@ class Categories extends React.Component {
<p>New, unique category name:</p> <p>New, unique category name:</p>
<InputGroup <InputGroup
autoFocus autoFocus
value={newCategoryText}
intent={ intent={
allCategoryNames.indexOf(newCategoryText) !== -1 newCategoryText &&
this.categoryNameError(newCategoryText)
? "warning" ? "warning"
: "none" : "none"
} }
@@ -138,16 +181,14 @@ class Categories extends React.Component {
style={{ style={{
marginTop: 7, marginTop: 7,
visibility: visibility:
allCategoryNames.indexOf(newCategoryText) !== -1 newCategoryText &&
this.categoryNameError(newCategoryText)
? "visible" ? "visible"
: "hidden", : "hidden",
color: Colors.ORANGE3 color: Colors.ORANGE3
}} }}
> >
<span style={{ fontStyle: "italic" }}> {this.categoryNameErrorMessage(newCategoryText)}
{newCategoryText}
</span>{" "}
already exists
</p> </p>
</div> </div>
@@ -186,9 +227,7 @@ class Categories extends React.Component {
</Tooltip> </Tooltip>
<Button <Button
onClick={this.handleCreateUserAnno} onClick={this.handleCreateUserAnno}
disabled={ disabled={this.categoryNameError(newCategoryText)}
allCategoryNames.indexOf(newCategoryText) !== -1
}
intent="primary" intent="primary"
type="submit" type="submit"
> >
+60 -23
View File
@@ -21,6 +21,7 @@ import {
import * as globals from "../../globals"; import * as globals from "../../globals";
import Value from "./value"; import Value from "./value";
import sortedCategoryValues from "./util"; import sortedCategoryValues from "./util";
import { AnnotationsHelpers } from "../../util/stateManager";
@connect(state => ({ @connect(state => ({
colorAccessor: state.colors.colorAccessor, colorAccessor: state.colors.colorAccessor,
@@ -82,18 +83,14 @@ class Category extends React.Component {
dispatch({ dispatch({
type: "annotation: disable add new label mode" type: "annotation: disable add new label mode"
}); });
this.setState({
newLabelText: ""
});
}; };
handleAddNewLabelToCategory = () => { handleAddNewLabelToCategory = () => {
const { dispatch, metadataField } = this.props; const { dispatch, metadataField } = this.props;
const { newLabelText } = this.state; const { newLabelText } = this.state;
/*
XXX TODO - temporary code generates random label string. Remove
when the label creation UI is implemented.
const { newLabelText } = this.state;
*/
// const newLabelText = `label${Math.random()}`;
dispatch({ dispatch({
type: "annotation: add new label to category", type: "annotation: add new label to category",
metadataField, metadataField,
@@ -146,6 +143,48 @@ class Category extends React.Component {
}); });
}; };
labelNameError = name => {
/*
return false if this is a LEGAL/acceptable category name,
or return an error type.
*/
const { metadataField, universe } = this.props;
const obsByName = universe.schema.annotations.obsByName;
if (obsByName[metadataField].categories.indexOf(name) !== -1) {
return "duplicate";
}
if (!AnnotationsHelpers.isLegalAnnotationName(name)) {
return "characters";
}
return false;
};
labelNameErrorMessage = name => {
const { metadataField } = this.props;
const err = this.labelNameError(name);
if (err == "duplicate") {
return (
<span>
<span style={{ fontStyle: "italic" }}>{name}</span> already exists
already exists within{" "}
<span style={{ fontStyle: "italic" }}>{metadataField}</span>{" "}
</span>
);
}
if (err == "characters") {
return (
<span>
<span style={{ fontStyle: "italic" }}>{name}</span> contains illegal
characters. Hint: use alpha-numeric and underscore
</span>
);
}
return err;
};
toggleAll() { toggleAll() {
const { dispatch, metadataField } = this.props; const { dispatch, metadataField } = this.props;
dispatch({ dispatch({
@@ -346,10 +385,12 @@ class Category extends React.Component {
<p>New, unique label name:</p> <p>New, unique label name:</p>
<InputGroup <InputGroup
autoFocus autoFocus
value={newLabelText}
intent={ intent={
universe.schema.annotations.obsByName[ // universe.schema.annotations.obsByName[
metadataField // metadataField
].categories.indexOf(newLabelText) !== -1 // ].categories.indexOf(newLabelText) !== -1
newLabelText && this.labelNameError(newLabelText)
? "warning" ? "warning"
: "none" : "none"
} }
@@ -362,21 +403,16 @@ class Category extends React.Component {
style={{ style={{
marginTop: 7, marginTop: 7,
visibility: visibility:
universe.schema.annotations.obsByName[ // universe.schema.annotations.obsByName[
metadataField // metadataField
].categories.indexOf(newLabelText) !== -1 // ].categories.indexOf(newLabelText) !== -1
newLabelText && this.labelNameError(newLabelText)
? "visible" ? "visible"
: "hidden", : "hidden",
color: Colors.ORANGE3 color: Colors.ORANGE3
}} }}
> >
<span style={{ fontStyle: "italic" }}> {this.labelNameErrorMessage(newLabelText)}
{newLabelText}
</span>{" "}
already exists within{" "}
<span style={{ fontStyle: "italic" }}>
{metadataField}
</span>
</p> </p>
</div> </div>
</div> </div>
@@ -390,9 +426,10 @@ class Category extends React.Component {
<Button <Button
disabled={ disabled={
newLabelText.length === 0 || newLabelText.length === 0 ||
universe.schema.annotations.obsByName[ // universe.schema.annotations.obsByName[
metadataField // metadataField
].categories.indexOf(newLabelText) !== -1 // ].categories.indexOf(newLabelText) !== -1
this.labelNameError(newLabelText)
} }
onClick={this.handleAddNewLabelToCategory} onClick={this.handleAddNewLabelToCategory}
intent="primary" intent="primary"
@@ -182,3 +182,8 @@ export function createWritableAnnotationDimensions(world, crossfilter) {
}, crossfilter); }, crossfilter);
return crossfilter; return crossfilter;
} }
const legalNames = /^\w+$/;
export function isLegalAnnotationName(name) {
return legalNames.test(name);
}