From 4a25f97b69806ddd4e0e3e8f7280ac9b51e06f8d Mon Sep 17 00:00:00 2001 From: David Glymph Date: Tue, 23 Jul 2024 16:18:58 -0400 Subject: [PATCH] allow broader selections in the node picker to still select associations --- .../textEditorRow/QualifiersSelector.jsx | 19 ++++++++------ .../textEditorRow/TextEditorRow.jsx | 26 ++++++++++++++++--- src/pages/queryBuilder/useQueryBuilder.js | 12 +++++---- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/pages/queryBuilder/textEditor/textEditorRow/QualifiersSelector.jsx b/src/pages/queryBuilder/textEditor/textEditorRow/QualifiersSelector.jsx index 4a3af6fd..13330041 100644 --- a/src/pages/queryBuilder/textEditor/textEditorRow/QualifiersSelector.jsx +++ b/src/pages/queryBuilder/textEditor/textEditorRow/QualifiersSelector.jsx @@ -48,14 +48,16 @@ const getQualifierOptions = ({ range, subpropertyOf }) => { export default function QualifiersSelector({ id, associations }) { const queryBuilder = useContext(QueryBuilderContext); - const associationOptions = associations.map(({ association, qualifiers }) => ({ - name: association.name, - uuid: association.uuid, - qualifiers: qualifiers.map((q) => ({ - name: q.qualifier.name, - options: getQualifierOptions(q), - })), - })); + const associationOptions = associations + .filter((a) => a.qualifiers.length > 0) + .map(({ association, qualifiers }) => ({ + name: association.name, + uuid: association.uuid, + qualifiers: qualifiers.map((q) => ({ + name: q.qualifier.name, + options: getQualifierOptions(q), + })), + })); const [value, setValue] = React.useState(associationOptions[0] || null); const [qualifiers, setQualifiers] = React.useState({}); @@ -75,6 +77,7 @@ export default function QualifiersSelector({ id, associations }) { onChange={(_, newValue) => { setValue(newValue); }} + disableClearable size="small" options={associationOptions} getOptionLabel={(option) => option.name} diff --git a/src/pages/queryBuilder/textEditor/textEditorRow/TextEditorRow.jsx b/src/pages/queryBuilder/textEditor/textEditorRow/TextEditorRow.jsx index 5243c04d..b65a4a38 100644 --- a/src/pages/queryBuilder/textEditor/textEditorRow/TextEditorRow.jsx +++ b/src/pages/queryBuilder/textEditor/textEditorRow/TextEditorRow.jsx @@ -37,6 +37,26 @@ function getValidAssociations(s, p, o, model) { return traverse([n], range); }; + // Returns true if `n` is an ancestor of `domain` + const isInDomain = ( + n, + domain + ) => { + const traverse = (nodes, search) => { + for (const n of nodes) { + if (n === search) return true; + if (n.parent) { + if (traverse([n.parent], search)) return true; + } + if (n.mixinParents) { + if (traverse(n.mixinParents, search)) return true; + } + } + return false; + }; + return traverse([domain], n); + }; + /** * Get the inherited subject/predicate/object ranges for an association */ @@ -78,9 +98,9 @@ function getValidAssociations(s, p, o, model) { if (association.slotUsage && !association.abstract) { const inherited = getInheritedSPORanges(association); - const validSubject = isInRange(subject, inherited.subject); - const validObject = isInRange(object, inherited.object); - const validPredicate = isInRange(predicate, inherited.predicate); + const validSubject = isInRange(subject, inherited.subject) || isInDomain(subject, inherited.subject); + const validObject = isInRange(object, inherited.object) || isInDomain(object, inherited.object); + const validPredicate = isInRange(predicate, inherited.predicate) || isInDomain(predicate, inherited.predicate); const qualifiers = Object.entries(association.slotUsage) .map(([qualifierName, properties]) => { diff --git a/src/pages/queryBuilder/useQueryBuilder.js b/src/pages/queryBuilder/useQueryBuilder.js index 193be0b0..40da4efd 100644 --- a/src/pages/queryBuilder/useQueryBuilder.js +++ b/src/pages/queryBuilder/useQueryBuilder.js @@ -70,11 +70,13 @@ function reducer(state, action) { } case 'editQualifiers': { const { id, qualifiers } = action.payload; - const qualifier_set = Object.entries(qualifiers).map(([name, value]) => ({ - qualifier_type_id: `biolink:${_.snakeCase(name)}`, - qualifier_value: name === 'qualified predicate' ? `biolink:${_.snakeCase(value)}` : _.snakeCase(value), - })); - state.message.message.query_graph.edges[id].qualifier_constraints = [{ qualifier_set }]; + if (qualifiers.length !== 0) { + const qualifier_set = Object.entries(qualifiers).map(([name, value]) => ({ + qualifier_type_id: `biolink:${_.snakeCase(name)}`, + qualifier_value: name === 'qualified predicate' ? `biolink:${_.snakeCase(value)}` : _.snakeCase(value), + })); + state.message.message.query_graph.edges[id].qualifier_constraints = [{ qualifier_set }]; + } break; } case 'deleteEdge': {