Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…into main
  • Loading branch information
jusa3 committed Mar 7, 2024
2 parents 8347fd1 + 9f53739 commit a329465
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 153 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# SemLookP Widgets
[Demo and Documentation](https://nfdi4health.github.io/semlookp-widgets/)
[Latest demo and documentation](https://nfdi4health.github.io/semlookp-widgets/latest/)

[All versions](https://nfdi4health.github.io/semlookp-widgets/)

## About The Project

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nfdi4health/semlookp-widgets",
"version": "1.19.11",
"version": "1.19.12",
"description": "This project includes a widget component library derived from the semantic lookup service SemLookP. The Terminology Service is a repository for biomedical resources that aims to provide a single point of access to the latest ontology and terminology versions. User interface (UI) functionalities were extracted and implemented as separate widgets to allow integration into other 3rd party services, thus simplifying the development of user interfaces and the visualization of semantic information. The widgets are built with React and TypeScript and can be used in React applications. SemLookP and the widgets are based on the Ontology Lookup Service (OLS), software developed by EBI.",
"main": "dist/esm/index.js",
"module": "dist/esm/index.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default {
placeholder: {
defaultValue: "Search for Term"
},
selectOption: {},
preselected: {},
parameter: {},
hasShortSelectedLabel: {
description: "If true, only the selected label of the entity is displayed. If false, the ontology and the entity short form is displayed behind the label. Default is true.",
Expand Down Expand Up @@ -58,18 +58,18 @@ export const withDefaults = Template.bind({});

export const withValue = Template.bind({});
withValue.args = {
selectOption: { iri: "http://purl.bioontology.org/ontology/MESH/D000086382" }
preselected: [{ iri: "http://purl.bioontology.org/ontology/MESH/D000086382" }],
};
export const withCustomValue = Template.bind({});
withCustomValue.args = {
allowCustomTerms: true,
selectOption: { label: "freetext" }
preselected: [{ label: "freetext" }],
};
export const withInvalidValue = Template.bind({});
withInvalidValue.args = {
selectOption: {
iri: "ht3stp://purl.bioontology.org/ontology/MESH/D000086382"
}
preselected: [{
iri: "ht3stp://purl.bioontology.org/ontology/MESH/D000086382",
}],
};

export const withGermanInput = Template.bind({});
Expand All @@ -94,6 +94,17 @@ allowAddingCustomTerms.args = {
allowCustomTerms: true
};

export const allowMultipleTerms = Template.bind({});
allowMultipleTerms.args = {
singleSelection: false,
};

export const withMultipleValues = Template.bind({});
withMultipleValues.args = {
preselected: [{ iri: "http://purl.bioontology.org/ontology/MESH/D000086382" }, { iri: "http://purl.bioontology.org/ontology/MESH/D003920" }],
singleSelection: false,
};




Expand Down
270 changes: 125 additions & 145 deletions src/components/widgets/AutocompleteWidget/AutocompleteWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,69 +14,68 @@ import { useQuery } from "react-query";
import { BreadcrumbWidget } from "../MetadataWidget";

export interface AutocompleteWidgetProps extends EuiComboBoxProps<string> {
/**
* Instance of the OLS API to call.
*/
api: string;
/**
* Additional parameters to pass to the API.
*
* This parameters can be used to filter the search results. Each parameter can be combined via
* the special character <i><b>&</b></i>. The values of a parameter key can be combined with a comma sign
* <i><b>,</b></i>. The following keys could be used:<br/> <br/>
* <table>
* <thead><tr><th>Parameter</th><th>Description</th></tr></thead>
* <tr><td>ontology</td><td>Restrict a search to a set of ontologies e.g. ontology=uberon,mesh</td></tr>
* <tr><td>type</td><td>Restrict a search to an entity type, one of {class,property,individual,ontology}</td></tr>
* <tr><td>slim</td><td>Restrict a search to a particular set of slims by name</td></tr>
* <tr><td>fieldList</td><td>Specify the fields to return. Pass {fieldList=description,label,iri,ontology_name,type,short_form} to display descriptions in search results.</td></tr>
* <tr><td>obsoletes</td><td>Set to true to include obsolete terms in the results</td></tr>
* <tr><td>local</td><td>Set to true to only return terms that are in a defining ontology, e.g. only return matches to gene ontology terms in the gene ontology, and exclude ontologies where those terms are also referenced</td></tr>
* <tr><td>childrenOf</td><td>You can restrict a search to all children of a given term. Supply a list of IRI for the terms that you want to search under (subclassOf/is-a relation only)</td></tr>
* <tr><td>allChildrenOf</td><td>You can restrict a search to all children of a given term. Supply a list of IRI for the terms that you want to search under (subclassOf/is-a plus any hierarchical/transitive properties like 'part of' or 'develops from')</td></tr>
* <tr><td>rows</td><td>Set results per page</td></tr>
* <tr><td>start</td><td>Set the results page number</td></tr>
* <tr><td>collection</td><td>Restrict a search to a terminology subset e.g. collection=nfdi4health</td></tr>
* </table>
*/
parameter?: string;
/**
* A method that is called once the set of selection changes
* @param selectedOptions The selected items
*/
selectionChangedEvent: (selectedOptions: {
label: string;
iri?: string;
ontology_name?: string;
type?: string;
}[]) => void;
/**
* Pass a pre select value.
*/
selectOption?: { label?: string; iri?: string };
/**
* Placeholder to show if no user input nor selection is performed.
*/
placeholder?: string;
/**
* If true, only the selected label of the entity is displayed. If false, the ontology and the entity short form is displayed behind the label. Default is true.
*/
hasShortSelectedLabel?: boolean;
/**
* If true, custom terms can be added that are not found via API.
*/
allowCustomTerms: boolean;
/**
* If true, only one concept can be selected at once.
*/
singleSelection: boolean;
/**
* Instance of the OLS API to call.
*/
api: string;
/**
* Additional parameter to pass to the API.
*
* This parameter could be used to filter the search results. Each parameter could be combined via
* the special character <i><b>&</b></i>. The values of a parameter key could be combined with a comma sign
* <i><b>,</b></i>. The following keys could be used:<br/> <br/>
* <table>
* <thead><tr><th>Parameter</th><th>Description</th></tr></thead>
* <tr><td>ontology</td><td>Restrict a search to a set of ontologies e.g. ontology=uberon,mesh</td></tr>
* <tr><td>type</td><td>Restrict a search to an entity type, one of {class,property,individual,ontology}</td></tr>
* <tr><td>slim</td><td>Restrict a search to a particular set of slims by name</td></tr>
* <tr><td>fieldList</td><td>Specify the fields to return, the defaults are {iri,label,short_form,obo_id,ontology_name,ontology_prefix,description,type}</td></tr>
* <tr><td>obsoletes</td><td>Set to true to include obsoleted terms in the results</td></tr>
* <tr><td>local</td><td>Set to true to only return terms that are in a defining ontology e.g. Only return matches to gene ontology terms in the gene ontology, and exclude ontologies where those terms are also referenced</td></tr>
* <tr><td>childrenOf</td><td>You can restrict a search to all children of a given term. Supply a list of IRI for the terms that you want to search under (subclassOf/is-a relation only)</td></tr>
* <tr><td>allChildrenOf</td><td>You can restrict a search to all children of a given term. Supply a list of IRI for the terms that you want to search under (subclassOf/is-a plus any hierarchical/transitive properties like 'part of' or 'develops from')</td></tr>
* <tr><td>rows</td><td>How many results per page</td></tr>
* <tr><td>start</td><td>The results page number</td></tr>
* </table>
*/
parameter?: string;
/**
* A method that is called once the set of selection changes
* @param selectedOptions The selected items
*/
selectionChangedEvent: (selectedOptions: {
label: string;
iri?: string;
ontology_name?: string;
type?: string;
}[]) => void;
/**
* Pass preselected values. If `singleSelection == true`, only the first one is displayed.
*/
preselected?: { label?: string; iri?: string }[];
/**
* Placeholder to show if no user input nor selection is performed.
*/
placeholder?: string;
/**
* If true, only the selected label of the entity is displayed. If false, the ontology and the entity short form is displayed behind the label. Default is true.
*/
hasShortSelectedLabel?: boolean;
/**
* If true, custom terms can be added that are not found via API.
*/
allowCustomTerms: boolean;
/**
* If true, only one concept can be selected at once.
*/
singleSelection: boolean;
}

/**
* A React component to provide Autosuggestion based on SemLookP.
*/
function AutocompleteWidget(props: AutocompleteWidgetProps) {
const { api, parameter, hasShortSelectedLabel, ...rest } = props;
const { api, parameter, hasShortSelectedLabel, allowCustomTerms, selectionChangedEvent, ...rest } = props;

const olsApi = new OlsApi(api);

Expand Down Expand Up @@ -175,95 +174,76 @@ function AutocompleteWidget(props: AutocompleteWidgetProps) {
}
};

/**
* on mount: fetches term for selectOption and sets it's label or sets a given label if no iri is provided or the given iri cannot be resolved only if allowCustomTerms is true
*/
const {
isLoading: isLoadingOnMount
} = useQuery(
[
"onMount", // no dependencies - does only need to be executed once when mounting the component
props.selectOption
],
async () => {
if (props.selectOption?.iri && props.selectOption?.iri.startsWith("http")) {
olsApi.select(
{ query: props.selectOption?.iri },
undefined,
undefined,
parameter
).then((response) => {
if (response.response && response.response.docs) {
response.response.docs.map((selection: any) => {
if (props.selectOption?.iri === selection.iri) {
setOptions([
{
// label to display within the combobox either raw value or generated one
// #renderOption() is used to display during selection.
label: hasShortSelectedLabel ? selection.label : generateDisplayLabel(selection),
// key to distinguish the options (especially those with same label)
key: selection.iri,
value: {
iri: selection.iri,
label: selection.label,
ontology_name: selection.ontology_name,
type: selection.type,
short_form: selection.short_form,
description: selection.description?.join()
}
}
]);
setSelectedOptions([
{
// label to display within the combobox either raw value or generated one
// #renderOption() is used to display during selection.
label: hasShortSelectedLabel ? selection.label : generateDisplayLabel(selection),
// key to distinguish the options (especially those with same label)
key: selection.iri,
value: {
iri: selection.iri,
label: selection.label,
ontology_name: selection.ontology_name,
type: selection.type,
short_form: selection.short_form,
description: selection.description?.join()
}
}
]);
}
});
}
});
} else if (props.selectOption?.label && props.allowCustomTerms) { // when a custom term is passed
setOptions([
{
label: props.selectOption?.label,
value: {
iri: "",
label: "",
ontology_name: "",
type: "",
short_form: "",
description: ""
}
}
]);
setSelectedOptions([
{
label: props.selectOption?.label,
value: {
iri: "",
label: "",
ontology_name: "",
type: "",
short_form: "",
description: ""
/**
* on mount: fetches term for selectOption and sets it's label or sets a given label if no iri is provided or the given iri cannot be resolved only if allowCustomTerms is true
*/
const {
isLoading: isLoadingOnMount
} = useQuery(
[
"onMount", // no dependencies - does only need to be executed once when mounting the component
props.preselected
],
async () => {
let preselectedValues : EuiComboBoxOptionOption<any>[] = [];

let uniqueValues = [...new Set(props.preselected)]
.filter((option) => {
return (props.allowCustomTerms && option.label) || option.iri;
});

if(props.singleSelection) uniqueValues = [uniqueValues[0]];

for(let option of uniqueValues) {
if (option.iri && option.iri.startsWith("http")) {
await olsApi.select(
{query: option.iri},
undefined,
undefined,
parameter,
).then((response) => {
if (response.response && response.response.docs) {
response.response.docs.map((selection: any) => {
if (option.iri === selection.iri) {
preselectedValues.push({
// label to display within the combobox either raw value or generated one
// #renderOption() is used to display during selection.
label: hasShortSelectedLabel ? selection.label : generateDisplayLabel(selection),
// key to distinguish the options (especially those with same label)
key: selection.iri,
value: {
iri: selection.iri,
label: selection.label,
ontology_name: selection.ontology_name,
type: selection.type,
short_form: selection.short_form,
description: selection.description?.join()
},
});
}
})
}
});
} else if (option.label && props.allowCustomTerms) {
preselectedValues.push({
label: option.label,
key: option.label,
value: {
iri: "",
label: "",
ontology_name: "",
type: "",
short_form: "",
description: ""
}
});
}
}
}
]);
}
}
);

setOptions(preselectedValues);
setSelectedOptions(preselectedValues);
}
)

/**
* fetches new options when searchValue changes
Expand Down

0 comments on commit a329465

Please sign in to comment.