Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#9751: Perform search using specific service from search menu #9770

Merged
merged 1 commit into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading