Skip to content

Commit

Permalink
#9751: Perform search using specific service from search menu (#9770)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsuren1 authored Dec 6, 2023
1 parent eefad42 commit b903bce
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 21 deletions.
99 changes: 87 additions & 12 deletions web/client/components/mapcontrols/search/SearchBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import React, { useEffect, useState } from 'react';
import {FormGroup, Glyphicon, MenuItem} from 'react-bootstrap';
import {isEmpty, isUndefined} from 'lodash';
import { isEmpty, isEqual, isUndefined, get } from 'lodash';

import Message from '../../I18N/Message';
import SearchBarMenu from './SearchBarMenu';
Expand All @@ -20,6 +20,49 @@ import SearchBarToolbar from '../../search/SearchBarToolbar';
import { defaultSearchWrapper } from '../../search/SearchBarUtils';
import BookmarkSelect, {BookmarkOptions} from "../searchbookmarkconfig/BookmarkSelect";
import CoordinatesSearch, {CoordinateOptions} from "../searchcoordinates/CoordinatesSearch";
import tooltip from '../../misc/enhancers/tooltip';

const TMenuItem = tooltip(MenuItem);
const SearchServicesSelectorMenu = ({activeTool, searchIcon, services = [], selectedService = -1, onServiceSelect = () => {}}) => {
if (services.length === 0) {
return null;
}
if (services.length === 1) {
return (
<MenuItem active={activeTool === "addressSearch"} onClick={() => onServiceSelect(-1)}>
<Glyphicon glyph={searchIcon}/>
<Message msgId="search.addressSearch"/>
</MenuItem>
);
}
return (<>
<TMenuItem
tooltipId="search.searchOnAllServices"
tooltipPosition="left"
active={activeTool === "addressSearch" && selectedService === -1}
onClick={() => onServiceSelect(-1)}
>
<Glyphicon glyph={searchIcon}/>
<Message msgId="search.addressSearch"/>
</TMenuItem>
{services.map((service, index) => {
const name = service.name || service.type;
return (<TMenuItem
tooltip={get(service, 'options.tooltip', `Search on ${name}`)}
tooltipPosition="left"
onClick={() => onServiceSelect(index)}
key={index}
active={activeTool === "addressSearch" && selectedService === index}
>
<span style={{marginLeft: 20}}>
<Glyphicon glyph={searchIcon}/>
{name}
</span>
</TMenuItem>);
})}
<MenuItem divider/>
</>);
};

export default ({
activeSearchTool: activeTool = 'addressSearch',
Expand Down Expand Up @@ -61,8 +104,24 @@ export default ({
items = [],
...props
}) => {
const [selectedSearchService, setSearchServiceSelected] = useState(-1);
useEffect(() => {
// Reset selected service, when service changes
if (!isEqual(searchOptions?.services, searchOptions?.services)) {
setSearchServiceSelected(-1);
}
}, [searchOptions?.services]);

const search = defaultSearchWrapper({searchText, selectedItems, searchOptions, maxResults, onSearch, onSearchReset});
const selectedServices = searchOptions?.services?.filter((_, index) => selectedSearchService >= 0 ? selectedSearchService === index : true) ?? [];
const search = defaultSearchWrapper({
searchText,
selectedItems,
searchOptions: {
...searchOptions,
services: selectedServices
},
maxResults, onSearch, onSearchReset
});

const clearSearch = () => {
onSearchReset();
Expand All @@ -78,14 +137,20 @@ export default ({
let searchMenuOptions = [];
if (showAddressSearchOption) {
searchMenuOptions.push(
<MenuItem active={activeTool === "addressSearch"} onClick={()=>{
onClearCoordinatesSearch({owner: "search"});
onClearBookmarkSearch("selected");
onChangeActiveSearchTool("addressSearch");
}}
>
<Glyphicon glyph={searchIcon}/> <Message msgId="search.addressSearch"/>
</MenuItem>);
<SearchServicesSelectorMenu
searchIcon={searchIcon}
activeTool={activeTool}
selectedService={selectedSearchService}
onServiceSelect={(index) => {
setSearchServiceSelected(index === -1 ? undefined : index);
onClearCoordinatesSearch({owner: "search"});
onClearBookmarkSearch("selected");
onChangeActiveSearchTool("addressSearch");
return;
}}
services={searchOptions?.services}
/>
);
}
if (showCoordinatesSearchOption) {
searchMenuOptions.push(
Expand Down Expand Up @@ -129,6 +194,16 @@ export default ({
return null;
};

const getPlaceholder = () => {
// when placeholder is present, nested service's placeholder is applied
if (!placeholder && selectedServices?.length === 1 && searchOptions?.services?.length > 1) {
const [service] = selectedServices;
const name = service.name || service.type;
return get(service, 'options.placeholder', `Search by ${name}`);
}
return placeholder;
};

return (<SearchBarBase>
<FormGroup>
<div className="input-group" style={{display: "flex"}}>
Expand All @@ -140,7 +215,7 @@ export default ({
delay={delay}
typeAhead={typeAhead}
blurResetDelay={blurResetDelay}
placeholder={placeholder}
placeholder={getPlaceholder()}
placeholderMsgId={placeholderMsgId}
searchText={searchText}
selectedItems={selectedItems}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,52 @@ describe("test the SearchBar", () => {
expect(rootDiv).toExist();
});

it('test search service menu', () => {
ReactDOM.render(<SearchBar searchOptions={{services: [{type: "nominatim"}]}}/>, document.getElementById("container"));
let search = document.getElementsByClassName("glyphicon-search");
expect(search).toBeTruthy();
expect(search.length).toBe(2);
});
it('test search with multiple services', () => {
ReactDOM.render(<SearchBar searchOptions={{services: [{type: "nominatim"}, {type: "wfs", name: "test"}]}}/>, document.getElementById("container"));
let search = document.getElementsByClassName("glyphicon-search");
let menuItems = document.querySelectorAll('[role="menuitem"]');
expect(search).toBeTruthy();
expect(search.length).toBe(4);
expect(menuItems.length).toBe(4);
expect(menuItems[1].innerText).toBe('nominatim');
expect(menuItems[2].innerText).toBe('test'); // Service name is menu name
});
it('test onSearch with multiple services', () => {
const actions = {
onSearch: () => {}
};
const services = [{type: "nominatim"}, {type: "wfs", name: "test"}];
const spyOnSearch = expect.spyOn(actions, 'onSearch');
ReactDOM.render(<SearchBar onSearch={actions.onSearch} searchText="test" searchOptions={{services}}/>, document.getElementById("container"));
let search = document.getElementsByClassName("glyphicon-search");
let input = document.querySelector(".searchInput");
let menuItems = document.querySelectorAll('[role="menuitem"]');
expect(search).toBeTruthy();
expect(input).toBeTruthy();
expect(search.length).toBe(4);

expect(menuItems.length).toBe(4);
TestUtils.Simulate.click(menuItems[1]); // Select single search

TestUtils.Simulate.keyDown(input, { key: 'Enter', keyCode: 13 });
expect(spyOnSearch).toHaveBeenCalled();
expect(spyOnSearch.calls[0].arguments[0]).toBe("test");
expect(spyOnSearch.calls[0].arguments[1]).toEqual({"services": [services[0]]});
expect(spyOnSearch.calls[0].arguments[2]).toBe(15);

TestUtils.Simulate.click(menuItems[0]); // Select all search
TestUtils.Simulate.keyDown(input, { key: 'Enter', keyCode: 13 });
expect(spyOnSearch).toHaveBeenCalled();
expect(spyOnSearch.calls[1].arguments[0]).toBe("test");
expect(spyOnSearch.calls[1].arguments[1]).toEqual({services});
expect(spyOnSearch.calls[1].arguments[2]).toBe(30);
});
it('test search and reset buttons', () => {
const renderSearchBar = (testHandlers, text) => {
return ReactDOM.render(
Expand Down Expand Up @@ -211,7 +257,7 @@ describe("test the SearchBar", () => {
let search = document.getElementsByClassName("glyphicon-search");
expect(reset).toExist();
expect(search).toExist();
expect(search.length).toBe(2);
expect(search.length).toBe(1);
});
it('test only search present, splitTools=false', () => {
ReactDOM.render(<SearchBar splitTools={false} searchText={""} delay={0} typeAhead={false} />, document.getElementById("container"));
Expand All @@ -237,7 +283,7 @@ describe("test the SearchBar", () => {
let reset = document.getElementsByClassName("glyphicon-1-close")[0];
let search = document.getElementsByClassName("glyphicon-search");
expect(reset).toExist();
expect(search.length).toBe(1);
expect(search.length).toBe(0);
});
it('test zoomToPoint, with search, with decimal, with reset', () => {
const store = {dispatch: () => {}, subscribe: () => {}, getState: () => ({search: {coordinate: {lat: 2, lon: 2}}})};
Expand All @@ -246,7 +292,7 @@ describe("test the SearchBar", () => {
let search = document.getElementsByClassName("glyphicon-search");
let cog = document.getElementsByClassName("glyphicon-cog");
expect(reset.length).toBe(1);
expect(search.length).toBe(2);
expect(search.length).toBe(1);
expect(cog.length).toBe(1);
});

Expand All @@ -258,7 +304,7 @@ describe("test the SearchBar", () => {
let cog = document.getElementsByClassName("glyphicon-cog");
let inputs = document.getElementsByTagName("input");
expect(reset.length).toBe(0);
expect(search.length).toBe(2);
expect(search.length).toBe(1);
expect(cog.length).toBe(1);
expect(inputs.length).toBe(6);
});
Expand Down Expand Up @@ -404,9 +450,9 @@ describe("test the SearchBar", () => {
TestUtils.Simulate.click(buttons[1]);
const links = container.querySelectorAll('a');
const bookmark = container.getElementsByClassName('glyphicon-bookmark');
expect(links.length).toBe(3);
expect(links.length).toBe(2);
expect(bookmark).toExist();
expect(links[2].innerText).toBe('Search by bookmark');
expect(links[1].innerText).toBe('Search by bookmark');
});
it('test searchByBookmark, search button disabled', () => {
const store = {dispatch: () => {}, subscribe: () => {}, getState: () => ({searchbookmarkconfig: {selected: {}}})};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,26 @@ class WFSOptionalProps extends React.Component {
/>
<Label key="maxZoomLevel-label" className="slider-label" >{options.maxZoomLevel || 21}</Label>
</FormGroup>
<FormGroup>
<ControlLabel>
<Message msgId="search.s_placeholder" />
</ControlLabel>
<FormControl
value={options.placeholder}
key="placeholder"
type="text"
onChange={this.updateProp.bind(null, "placeholder")}/>
</FormGroup>
<FormGroup>
<ControlLabel>
<Message msgId="search.s_tooltip" />
</ControlLabel>
<FormControl
value={options.tooltip}
key="tooltip"
type="text"
onChange={this.updateProp.bind(null, "tooltip")}/>
</FormGroup>
</form>);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe("test ResultProps component", () => {
const wfsOptionalProps = ReactDOM.render(<WFSOptionalProps.Element/>, document.getElementById("container"));
expect(wfsOptionalProps).toExist();
const labels = TestUtils.scryRenderedDOMComponentsWithClass(wfsOptionalProps, "control-label");
expect(labels.length).toBe(3);
expect(labels.length).toBe(5);
});

it('test WFSOptionalProps with preconfigured service', () => {
Expand All @@ -45,12 +45,14 @@ describe("test ResultProps component", () => {
let wfsOptionalProps = ReactDOM.render(<WFSOptionalProps.Element service={service}/>, document.getElementById("container"));
expect(wfsOptionalProps).toExist();
const labels = TestUtils.scryRenderedDOMComponentsWithClass(wfsOptionalProps, "control-label");
expect(labels.length).toBe(3);
expect(labels.length).toBe(5);
expect(labels[0].innerText).toBe('search.s_sort');
expect(labels[1].innerText).toBe('search.s_max_features');
expect(labels[2].innerText).toBe('search.s_max_zoom');
expect(labels[3].innerText).toBe('search.s_placeholder');
expect(labels[4].innerText).toBe('search.s_tooltip');
const inputs = TestUtils.scryRenderedDOMComponentsWithClass(wfsOptionalProps, 'form-control');
expect(inputs.length).toBe(1);
expect(inputs.length).toBe(3);
expect(inputs[0].value).toBe('NAME');
let sliders = TestUtils.scryRenderedDOMComponentsWithClass(wfsOptionalProps, 'slider-label');
expect(sliders.length).toBe(2);
Expand Down
3 changes: 3 additions & 0 deletions web/client/translations/data.de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,7 @@
"aeronautical": "Luftfahrt",
"changeSearchInputField": "Suchwerkzeug ändern",
"addressSearch": "Suche nach Standortname",
"searchOnAllServices": "Suchen Sie unten nach allen Diensten",
"coordinatesSearch": "Suche nach Koordinaten",
"searchservicesbutton": "Suchdienste konfigurieren",
"configpaneltitle": "Einen Suchdienst anlegen / bearbeiten",
Expand Down Expand Up @@ -923,6 +924,8 @@
"s_wfs_opt_props_label" : "Optionale Eigenschaften",
"s_result_props_label": "Ergebnisanzeigeeigenschaften",
"s_priority_info": "Wird verwendet, um Suchergebnisse zu sortieren, niedrigere Werte zuerst. Nominatim Ergebnisse haben Priorität = 5",
"s_placeholder": "Platzhalter für die Sucheingabe",
"s_tooltip": "Kurzinfo zum Servicemenü",
"serviceslistempty": "Keine benutzerdefinierten Services definiert",
"service_missing": "{serviceType} dienst ist nicht konfiguriert",
"generic_error": "Bei der Suche ist ein Fehler aufgetreten. Fehlerdetails: {message}",
Expand Down
3 changes: 3 additions & 0 deletions web/client/translations/data.en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,7 @@
"aeronautical": "Aeronautical",
"changeSearchInputField": "Change the search tool",
"addressSearch": "Search by location name",
"searchOnAllServices": "Search on all services below",
"coordinatesSearch": "Search by coordinates",
"searchservicesbutton": "Configure search services",
"configpaneltitle": "Create/edit a search service",
Expand Down Expand Up @@ -884,6 +885,8 @@
"s_wfs_opt_props_label" : "Optional properties",
"s_result_props_label": "Result display properties",
"s_priority_info": "Used to sort search results, lower values first. Nominatim results have priority = 5",
"s_placeholder": "Search input placeholder",
"s_tooltip": "Service menu tooltip",
"serviceslistempty": "No custom services defined",
"service_missing": "{serviceType} service is not configured",
"generic_error": "An error occurred during search. Error details: {message}",
Expand Down
3 changes: 3 additions & 0 deletions web/client/translations/data.es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,7 @@
"aeronautical": "Aeronáutica",
"changeSearchInputField": "Cambiar la herramienta de búsqueda",
"addressSearch": "Buscar por nombre de ubicación",
"searchOnAllServices": "Busque en todos los servicios a continuación",
"coordinatesSearch": "buscar por coordenadas",
"searchservicesbutton": "Configurar los servicios de búsqueda",
"configpaneltitle": "Crear / modificar un servicio de búsqueda",
Expand Down Expand Up @@ -884,6 +885,8 @@
"s_wfs_opt_props_label" : "Propiedades opcionales",
"s_result_props_label": "Resultados de la visualización de las propiedades",
"s_priority_info": "Usado para ordenar los resultados de búsqueda, de menor a mayor. Los resultados Nominatim tienen prioridad = 5",
"s_placeholder": "Marcador de posición de entrada de búsqueda",
"s_tooltip": "Información sobre herramientas del menú de servicio",
"serviceslistempty": "No se ha definido ningún servicio personalizado",
"service_missing": "el servicio {serviceType} no está configurado",
"generic_error": "Se ha producido un error durante la búsqueda. Error de detalles: {message}",
Expand Down
3 changes: 3 additions & 0 deletions web/client/translations/data.fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,7 @@
"aeronautical": "Aéronautique",
"changeSearchInputField": "Changer l'outil de recherche",
"addressSearch": "Recherche par nom de lieu",
"searchOnAllServices": "Recherchez tous les services ci-dessous",
"coordinatesSearch": "Rechercher par coordonnées",
"searchservicesbutton": "Configurer les services de recherche",
"configpaneltitle": "Créer / modifier un service de recherche",
Expand Down Expand Up @@ -884,6 +885,8 @@
"s_wfs_opt_props_label": "Propriétés optionnelles",
"s_result_props_label": "Propriétés d'affichage des résultats",
"s_priority_info": "Utilisé pour trier les résultats de recherche, les valeurs les plus élevées en premier. Les résultats Nominatim ont priorité = 5",
"s_placeholder": "Espace réservé de saisie de recherche",
"s_tooltip": "Info-bulle du menu Service",
"serviceslistempty": "Aucun service personnalisé n'a été défini",
"service_missing": "Le service {serviceType} n'est pas configuré",
"generic_error": "Une erreur s'est produite lors de la recherche. Détails de l'erreur : {message}",
Expand Down
3 changes: 3 additions & 0 deletions web/client/translations/data.it-IT.json
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,7 @@
"aeronautical": "Aeronautiche",
"changeSearchInputField": "Cambia lo strumento di ricerca",
"addressSearch": "Cerca un indirizzo",
"searchOnAllServices": "Cerca su tutti i servizi di seguito",
"coordinatesSearch": "Cerca per coordinate",
"searchservicesbutton": "Configura servizi di ricerca",
"configpaneltitle": "Aggiungi/modifica un servizio di ricerca",
Expand Down Expand Up @@ -884,6 +885,8 @@
"s_wfs_opt_props_label" : "Proprietà facoltative",
"s_result_props_label": "Proprietà di visualizzazione dei risultati",
"s_priority_info": "Usato per ordinare i risultati, i valori più alti vengono visualizzati per primi. Nominatim ha priorità = 5",
"s_placeholder": "Cerca segnaposto di input",
"s_tooltip": "Descrizione comando del menu di servizio",
"serviceslistempty": "Nessun servizio definito",
"service_missing": "Il servizio {serviceType} non è configurato",
"generic_error": "Un errore e' occorso durante la ricerca: Dettagli errore: {message}",
Expand Down

0 comments on commit b903bce

Please sign in to comment.