mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 06:26:26 +00:00
feat(places): send a search to Google, once or always
Since 4.3.0 the TREK index and OpenStreetMap answer every search first and Google is only asked when they find nothing, so a list with the wrong place on it never reached Google, whatever key the instance had, and the provider choice only decided who fills the empty case. Two ways to change that, both only where Google holds the key slot. Under a result list that did not come from Google, one quiet line sends the same query to Google Places alone (`provider: 'google'` on the search contract, the MCP search tool takes it too). And beside the key, an admin switch "Search with Google only" hands every search and every suggestion to Google and asks nothing of the index or OpenStreetMap; off by default, read by the search after the keyed provider so an install without a key, or one on Amap or OpenStreetMap, is untouched. The panel says so beside the switch. Both shells share the one rule for when the line is offered (placeSource), and the wiki names both switches.
This commit is contained in:
@@ -186,6 +186,53 @@ describe('PlaceFormModal', () => {
|
||||
expect(await screen.findByText('Eiffel Tower')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('FE-PLANNER-PLACEFORM-018b: a list the index answered offers Google instead, and the link sends the same query there alone', async () => {
|
||||
const user = userEvent.setup();
|
||||
seedStore(useAuthStore, { user: buildUser(), isAuthenticated: true, hasMapsKey: true });
|
||||
const bodies: Record<string, unknown>[] = [];
|
||||
server.use(
|
||||
http.post('/api/maps/search', async ({ request }) => {
|
||||
const body = (await request.json()) as Record<string, unknown>;
|
||||
bodies.push(body);
|
||||
return body.provider === 'google'
|
||||
? HttpResponse.json({ places: [{ name: 'Tokyo Station', address: 'Chiyoda', lat: '35.68', lng: '139.77' }], source: 'google' })
|
||||
: HttpResponse.json({ places: [{ name: 'Weigh station', address: 'Ritzville', lat: '47.1', lng: '-118.4' }], source: 'trek-places+openstreetmap' });
|
||||
}),
|
||||
);
|
||||
|
||||
render(<PlaceFormModal {...defaultProps} />);
|
||||
const searchInput = screen.getByPlaceholderText('Search places...');
|
||||
await user.type(searchInput, 'Tokyo Station');
|
||||
const searchRow = searchInput.closest('.flex') as HTMLElement;
|
||||
await user.click(within(searchRow).getByRole('button'));
|
||||
expect(await screen.findByText('Weigh station')).toBeInTheDocument();
|
||||
|
||||
// The quiet line under the list, only because the instance has a key and
|
||||
// this list did not come from Google.
|
||||
const retry = screen.getByRole('button', { name: /Search Google instead/ });
|
||||
await user.click(retry);
|
||||
expect(await screen.findByText('Tokyo Station')).toBeInTheDocument();
|
||||
expect(bodies[0]).not.toHaveProperty('provider');
|
||||
expect(bodies[1]).toMatchObject({ query: 'Tokyo Station', provider: 'google' });
|
||||
// A list Google produced has nowhere further to go.
|
||||
expect(screen.queryByRole('button', { name: /Search Google instead/ })).toBeNull();
|
||||
});
|
||||
|
||||
it('FE-PLANNER-PLACEFORM-018c: without a Google key the list offers nothing', async () => {
|
||||
const user = userEvent.setup();
|
||||
server.use(
|
||||
http.post('/api/maps/search', () =>
|
||||
HttpResponse.json({ places: [{ name: 'Weigh station', address: 'Ritzville', lat: '47.1', lng: '-118.4' }], source: 'trek-places+openstreetmap' }),
|
||||
),
|
||||
);
|
||||
render(<PlaceFormModal {...defaultProps} />);
|
||||
const searchInput = screen.getByPlaceholderText('Search places...');
|
||||
await user.type(searchInput, 'Tokyo Station');
|
||||
await user.click(within(searchInput.closest('.flex') as HTMLElement).getByRole('button'));
|
||||
expect(await screen.findByText('Weigh station')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: /Search Google instead/ })).toBeNull();
|
||||
});
|
||||
|
||||
it('FE-PLANNER-PLACEFORM-019: pressing Enter in search input triggers search', async () => {
|
||||
const user = userEvent.setup();
|
||||
server.use(
|
||||
|
||||
@@ -13,12 +13,12 @@ import { useSettingsStore } from '../../store/settingsStore'
|
||||
import CollectionPicker from '../Collections/CollectionPicker'
|
||||
import PlaceDetailsColumn, { type PlaceDetailsSelection } from './PlaceDetailsColumn'
|
||||
import { useToast } from '../shared/Toast'
|
||||
import { Search, Paperclip, X, AlertTriangle, Loader2, Plus } from 'lucide-react'
|
||||
import { Search, Paperclip, X, AlertTriangle, Loader2, Plus, RotateCcw } from 'lucide-react'
|
||||
import { useTranslation } from '../../i18n'
|
||||
import CustomTimePicker from '../shared/CustomTimePicker'
|
||||
import { DEFAULT_FORM, isMapUrl, mergeResult, type PlaceFormData, type ResultField } from './PlaceFormModal.helpers'
|
||||
import { getApiErrorMessage } from '../../utils/apiError'
|
||||
import { sourceLabelFor } from '../../utils/placeSource'
|
||||
import { offersGoogleRetry, sourceLabelFor } from '../../utils/placeSource'
|
||||
import { useLocationBias } from '../../hooks/useLocationBias'
|
||||
import { BookingCostsSection } from './BookingCostsSection'
|
||||
import type { BookingExpenseRequest } from './BookingCostsSection.types'
|
||||
@@ -396,7 +396,7 @@ function usePlaceFormModal(props: PlaceFormModalProps) {
|
||||
setForm(prev => ({ ...prev, [field]: value }))
|
||||
}
|
||||
|
||||
const handleMapsSearch = async () => {
|
||||
const handleMapsSearch = async (provider?: 'google') => {
|
||||
if (!mapsSearch.trim()) return
|
||||
setIsSearchingMaps(true)
|
||||
try {
|
||||
@@ -419,7 +419,7 @@ function usePlaceFormModal(props: PlaceFormModalProps) {
|
||||
return
|
||||
}
|
||||
}
|
||||
const result = await mapsApi.search(mapsSearch, language, locationBiasPoint)
|
||||
const result = await mapsApi.search(mapsSearch, language, locationBiasPoint, provider)
|
||||
searchMetaRef.current = { query: mapsSearch.trim(), source: result.source || 'unknown' }
|
||||
setMapsResults(result.places || [])
|
||||
setSearchSource(result.source || '')
|
||||
@@ -1002,6 +1002,21 @@ export default function PlaceFormModal(props: PlaceFormModalProps) {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{/* The index answers first and Google only when it finds nothing, so a
|
||||
list with the wrong place on it never reaches Google by itself. One
|
||||
quiet line under the list sends the same query there, on an instance
|
||||
that has a key and for a list Google did not already produce. */}
|
||||
{mapsResults.length > 0 && offersGoogleRetry(searchSource, hasMapsKey) && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleMapsSearch('google')}
|
||||
disabled={isSearchingMaps}
|
||||
className="mt-1.5 inline-flex items-center gap-1 text-caption text-content-faint hover:text-content transition-colors disabled:opacity-50"
|
||||
>
|
||||
<RotateCcw size={11} strokeWidth={2} aria-hidden="true" />
|
||||
{t('places.searchGoogleInstead')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Name */}
|
||||
|
||||
Reference in New Issue
Block a user