mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-05 02:38:11 +08:00
Ontologies (#1110)
* add sample ontologies file * add ontologies reducer * Move select category to own component * Dialog and Input factored out * refactoring categorical, partway * validationn * anno * suggest populates input * frontend for ontology working * initial implementation of back-end support for ontologies * edit is now dialog again * autosuggest working on edit * part way through create arbitrary label * handle choice in function * pass duplicate cat prop * editing works * update test to match new CLI params * fix occupancy alignment * edit category as dialogue * secondary button * remove stubbed out ontologies * add label setting upon new label creation * Update legal characters for labels (#1119) * Allow any term in the ontology (bypass legal name check) * Add hyphens and parens to legal characters in names * improve performance for large ontologies * correctly handle case where ontologies are disabled * fix logic error in CLI Co-authored-by: Bruce Martin <bruce@chanzuckerberg.com> * PR cleanup 1 * lint * validate user generated labels * finish hooking up connected suggest component * protect against undefined callbacks * Fix illegal characters error message * break out npm run commands * fix error detection on label edit Co-authored-by: Bruce Martin <bruce@chanzuckerberg.com> Co-authored-by: Sidney Bell <sidneymbell@users.noreply.github.com>
This commit is contained in:
co-authored by
Bruce Martin
Sidney Bell
parent
8d725b1ad9
commit
d48647a655
@@ -0,0 +1,159 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { InputGroup, MenuItem } from "@blueprintjs/core";
|
||||
import { Suggest } from "@blueprintjs/select";
|
||||
import fuzzysort from "fuzzysort";
|
||||
|
||||
const filterOntology = (query, ontology) =>
|
||||
/* fires on load, once, and then for each character typed into the input */
|
||||
fuzzysort.go(query, ontology, {
|
||||
limit: 100,
|
||||
threshold: -10000 // don't return bad results
|
||||
});
|
||||
|
||||
const renderListItem = (fuzzySortResult, { handleClick, modifiers }) => {
|
||||
if (!modifiers.matchesPredicate) {
|
||||
return null;
|
||||
}
|
||||
/* the fuzzysort wraps the object with other properties, like a score */
|
||||
const geneName = fuzzySortResult.target;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
data-testid={`suggest-menu-item-${geneName}`}
|
||||
// Use of annotations in this way is incorrect and dataset specific.
|
||||
// See https://github.com/chanzuckerberg/cellxgene/issues/483
|
||||
// label={gene.n_counts}
|
||||
key={geneName}
|
||||
onClick={g =>
|
||||
/* this fires when user clicks a menu item */
|
||||
handleClick(g)
|
||||
}
|
||||
text={geneName}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const AnnoSuggest = props => {
|
||||
const {
|
||||
ontologyLoading,
|
||||
handleItemChange,
|
||||
handleChoice,
|
||||
ontology,
|
||||
handleCreateArbitraryLabel,
|
||||
handleTextChange,
|
||||
isTextInvalid,
|
||||
isTextInvalidErrorMessage
|
||||
} = props;
|
||||
return (
|
||||
<Suggest
|
||||
fill
|
||||
resetOnSelect
|
||||
closeOnSelect
|
||||
resetOnClose
|
||||
createNewItemFromQuery={() => {}}
|
||||
createNewItemRenderer={userInputStr => {
|
||||
return isTextInvalid?.(userInputStr) ? (
|
||||
<MenuItem disabled text={isTextInvalidErrorMessage(userInputStr)} />
|
||||
) : (
|
||||
<MenuItem
|
||||
icon="add"
|
||||
text="Create a label not in the ontology"
|
||||
active
|
||||
onClick={() => {
|
||||
handleCreateArbitraryLabel(userInputStr);
|
||||
}}
|
||||
shouldDismissPopover={false}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
itemDisabled={ontologyLoading ? () => true : () => false}
|
||||
noResults={<MenuItem disabled text="No matching ontology identifier" />}
|
||||
onItemSelect={handleChoice}
|
||||
initialContent={
|
||||
<MenuItem disabled text="Enter an ontology identifier…" />
|
||||
}
|
||||
inputProps={{ "data-testid": "gene-search" }}
|
||||
inputValueRenderer={t => t.target}
|
||||
itemListPredicate={filterOntology}
|
||||
onActiveItemChange={handleItemChange}
|
||||
itemRenderer={renderListItem.bind(this)}
|
||||
items={!ontologyLoading && ontology ? ontology : ["No ontology loaded"]}
|
||||
popoverProps={{ minimal: true }}
|
||||
onQueryChange={(s, e) => {
|
||||
// undefined event means resetOnSelect
|
||||
if (e !== undefined) handleTextChange?.(s);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const VanillaInput = props => {
|
||||
const { text, handleTextChange } = props;
|
||||
return (
|
||||
<InputGroup
|
||||
autoFocus
|
||||
value={text}
|
||||
intent="none"
|
||||
onChange={e => handleTextChange?.(e.target.value)}
|
||||
leftIcon="tag"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@connect(state => ({
|
||||
colorAccessor: state.colors.colorAccessor,
|
||||
categoricalSelection: state.categoricalSelection,
|
||||
annotations: state.annotations,
|
||||
universe: state.universe,
|
||||
world: state.world,
|
||||
ontology: state.ontology?.terms,
|
||||
ontologyLoading: state.ontology?.loading
|
||||
}))
|
||||
class AnnoInputs extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
useSuggest,
|
||||
handleTextChange,
|
||||
text,
|
||||
ontologyLoading,
|
||||
world,
|
||||
handleChoice,
|
||||
handleItemChange,
|
||||
handleCreateArbitraryLabel,
|
||||
ontology,
|
||||
isTextInvalid,
|
||||
isTextInvalidErrorMessage
|
||||
} = this.props;
|
||||
return (
|
||||
<div>
|
||||
{/* Le sigh https://github.com/palantir/blueprint/issues/2864 */}
|
||||
{useSuggest ? (
|
||||
<AnnoSuggest
|
||||
world={world}
|
||||
text={text}
|
||||
handleChoice={handleChoice}
|
||||
ontologyLoading={ontologyLoading}
|
||||
handleCreateArbitraryLabel={handleCreateArbitraryLabel}
|
||||
handleItemChange={handleItemChange}
|
||||
ontology={ontology}
|
||||
handleTextChange={handleTextChange}
|
||||
isTextInvalid={isTextInvalid}
|
||||
isTextInvalidErrorMessage={isTextInvalidErrorMessage}
|
||||
/>
|
||||
) : (
|
||||
<VanillaInput text={text} handleTextChange={handleTextChange} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AnnoInputs;
|
||||
Reference in New Issue
Block a user