Skip to content

Commit

Permalink
PB-877: add autoselect to swisssearch url param
Browse files Browse the repository at this point in the history
  • Loading branch information
sommerfe committed Nov 5, 2024
1 parent bb1b4a0 commit d667678
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 84 deletions.
40 changes: 40 additions & 0 deletions src/router/storeSync/SearchAutoSelectConfig.class.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import AbstractParamConfig, {
STORE_DISPATCHER_ROUTER_PLUGIN,
} from '@/router/storeSync/abstractParamConfig.class'
import { removeQueryParamFromHref } from '@/utils/searchParamUtils'
import { URL_PARAM_NAME_SWISSSEARCH } from './SearchParamConfig.class'

const URL_PARAM_NAME = 'swisssearch_autoselect'
/**
* The goal is to stop centering on the search when sharing a position. When we share a position,
* both the center and the crosshair are sets.
*
* @param {Object} to The route object containing the query
* @param {Object} store The store
* @param {String} urlParamValue The search param
*/
function dispatchSearchFromUrl(to, store, urlParamValue) {
// avoiding setting the swisssearch autoselect to the store when there is nothing to autoselect because there is no swisssearch query
if (urlParamValue && to.query[URL_PARAM_NAME_SWISSSEARCH]) {
store.dispatch('setSwisssearchAutoSelect', {
value: urlParamValue,
dispatcher: STORE_DISPATCHER_ROUTER_PLUGIN,
})
}
}

export default class SearchAutoSelectConfig extends AbstractParamConfig {
constructor() {
super({
urlParamName: URL_PARAM_NAME,
mutationsToWatch: ['setSwisssearchAutoSelect'],
setValuesInStore: dispatchSearchFromUrl,
afterSetValuesInStore: () => removeQueryParamFromHref(URL_PARAM_NAME),
extractValueFromStore: (store) => store.state.search.swisssearchAutoSelect,
keepInUrlWhenDefault: false,
valueType: Boolean,
defaultValue: false,
validateUrlInput: null,
})
}
}
31 changes: 4 additions & 27 deletions src/router/storeSync/SearchParamConfig.class.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import AbstractParamConfig, {
STORE_DISPATCHER_ROUTER_PLUGIN,
} from '@/router/storeSync/abstractParamConfig.class'
import { removeQueryParamFromHref } from '@/utils/searchParamUtils'

const URL_PARAM_NAME = 'swisssearch'
export const URL_PARAM_NAME_SWISSSEARCH = 'swisssearch'
/**
* The goal is to stop centering on the search when sharing a position. When we share a position,
* both the center and the crosshair are sets.
Expand All @@ -23,37 +24,13 @@ function dispatchSearchFromUrl(to, store, urlParamValue) {
}
}

/**
* This will remove the query param from the URL It is necessary to do this in vanilla JS because
* the router does not provide a way to remove a query without reloading the page which then removes
* the value from the store.
*
* @param {Object} key The key to remove from the URL
*/
function removeQueryParamFromHref(key) {
const [baseUrl, queryString] = window.location.href.split('?')
if (!queryString) {
return
}

const params = new URLSearchParams(queryString)
if (!params.has(key)) {
return
}
params.delete(key)

const newQueryString = params.toString()
const newUrl = newQueryString ? `${baseUrl}?${newQueryString}` : baseUrl
window.history.replaceState({}, document.title, newUrl)
}

export default class SearchParamConfig extends AbstractParamConfig {
constructor() {
super({
urlParamName: URL_PARAM_NAME,
urlParamName: URL_PARAM_NAME_SWISSSEARCH,
mutationsToWatch: [],
setValuesInStore: dispatchSearchFromUrl,
afterSetValuesInStore: () => removeQueryParamFromHref(URL_PARAM_NAME),
afterSetValuesInStore: () => removeQueryParamFromHref(URL_PARAM_NAME_SWISSSEARCH),
keepInUrlWhenDefault: false,
valueType: String,
defaultValue: '',
Expand Down
6 changes: 4 additions & 2 deletions src/router/storeSync/storeSync.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import SimpleUrlParamConfig from '@/router/storeSync/SimpleUrlParamConfig.class'
import ZoomParamConfig from '@/router/storeSync/ZoomParamConfig.class'
import { FeatureInfoPositions } from '@/store/modules/ui.store.js'
import allCoordinateSystems from '@/utils/coordinates/coordinateSystems'

import TimeSliderParamConfig from './TimeSliderParamConfig.class'
import TimeSliderParamConfig from '@/router/storeSync/TimeSliderParamConfig.class'
import SearchAutoSelectConfig from '@/router/storeSync/SearchAutoSelectConfig.class'

/**
* Configuration for all URL parameters of this app that need syncing with the store (and
Expand All @@ -23,6 +23,8 @@ import TimeSliderParamConfig from './TimeSliderParamConfig.class'
* @type Array<AbstractParamConfig>
*/
const storeSyncConfig = [
// SearchAutoSelectConfig should be processed before SearchParamConfig to avoid a bug where the autoselect would not be set
new SearchAutoSelectConfig(),
new SimpleUrlParamConfig({
urlParamName: 'lang',
mutationsToWatch: ['setLang'],
Expand Down
2 changes: 2 additions & 0 deletions src/router/storeSync/storeSync.routerPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ function urlQueryWatcher(store, to, from) {
}

if (
// to call afterSetValuesInStore in SearchAutoSelectConfig if it was set to false in the URL to remove the parameter from the URL
queryValue === false ||
// when the query value is an empty string, queryValue is false.
(queryValue || queryValue === '') &&
queryValue !== storeValue &&
Expand Down
45 changes: 30 additions & 15 deletions src/store/modules/search.store.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,33 @@ const state = {
* @type {SearchResult[]}
*/
results: [],

/**
* If true, the first search result will be automatically selected
*
* @type {Boolean}
*/
swisssearchAutoSelect: false,
}

const getters = {}

function extractLimitNumber(query) {
const regex = / limit: \d+/
const match = query.match(regex)

if (match) {
return {
limit: parseInt(match[0].split(':')[1].trim()),
extractedQuery: query.replace(match[0], ''),
}
function getResultForAutoselect(results) {
if (results.length === 1) {
return results[0];
}
return { limit: 0, extractedQuery: query }
// Try to find a result with resultType LOCATION
const locationResult = results.find((result) => result.resultType === SearchResultTypes.LOCATION);

// If a location result is found, return it; otherwise, return the first result
return locationResult || results[0];
}

const actions = {
setSwisssearchAutoSelect: ({ commit }, { value = false, dispatcher }) => {
commit('setSwisssearchAutoSelect', { value, dispatcher })
},

/**
* @param {vuex} vuex
* @param {Object} payload
Expand All @@ -59,8 +68,6 @@ const actions = {
) => {
let results = []
commit('setSearchQuery', { query, dispatcher })
const { limit, extractedQuery } = extractLimitNumber(query)
query = extractedQuery
// only firing search if query is longer than or equal to 2 chars
if (query.length >= 2) {
const currentProjection = rootState.position.projection
Expand Down Expand Up @@ -151,12 +158,12 @@ const actions = {
queryString: query,
lang: rootState.i18n.lang,
layersToSearch: getters.visibleLayers,
limit,
limit: state.swisssearchAutoSelect ? 1 : 0,
})
if (originUrlParam && results.length === 1) {
if (originUrlParam && results.length === 1 || originUrlParam && state.swisssearchAutoSelect && results.length >= 1) {
dispatch('selectResultEntry', {
dispatcher: `${dispatcher}/setSearchQuery`,
entry: results[0],
entry: getResultForAutoselect(results),
})
}
} catch (error) {
Expand Down Expand Up @@ -195,6 +202,7 @@ const actions = {
queryString: state.query,
lang: rootState.i18n.lang,
layersToSearch: getters.visibleLayers,
limit: state.swisssearchAutoSelect ? 1 : 0,
})
if (resultIncludingLayerFeatures.length > state.results.length) {
commit('setSearchResults', {
Expand Down Expand Up @@ -258,6 +266,12 @@ const actions = {

break
}
if(state.swisssearchAutoSelect) {
dispatch('setSwisssearchAutoSelect', {
value: false,
dispatcher: dispatcherSelectResultEntry,
})
}
},
}

Expand Down Expand Up @@ -287,6 +301,7 @@ function createLayerFeature(olFeature, layer) {
}

const mutations = {
setSwisssearchAutoSelect: (state, { value }) => (state.swisssearchAutoSelect = value),
setSearchQuery: (state, { query }) => (state.query = query),
setSearchResults: (state, { results }) => (state.results = results ?? []),
}
Expand Down
23 changes: 23 additions & 0 deletions src/utils/searchParamUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* This will remove the query param from the URL It is necessary to do this in vanilla JS because
* the router does not provide a way to remove a query without reloading the page which then removes
* the value from the store.
*
* @param {Object} key The key to remove from the URL
*/
export function removeQueryParamFromHref(key) {
const [baseUrl, queryString] = window.location.href.split('?')
if (!queryString) {
return
}

const params = new URLSearchParams(queryString)
if (!params.has(key)) {
return
}
params.delete(key)

const newQueryString = params.toString()
const newUrl = newQueryString ? `${baseUrl}?${newQueryString}` : baseUrl
window.history.replaceState({}, document.title, newUrl)
}
40 changes: 0 additions & 40 deletions tests/cypress/tests-e2e/legacyParamImport.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,46 +286,6 @@ describe('Test on legacy param import', () => {
})
cy.get('[data-cy="search-results-locations"]').should('not.be.visible')
})
it('limits the swisssearch with legacy parameter limit', () => {
cy.intercept('**/rest/services/ech/SearchServer*?type=layers*', {
body: { results: [] },
}).as('search-layers')
const coordinates = [2598633.75, 1200386.75]
cy.intercept('**/rest/services/ech/SearchServer*?type=locations*', {
body: {
results: [
{
attrs: {
detail: '1530 payerne 5822 payerne ch vd',
label: ' <b>1530 Payerne</b>',
lat: 46.954559326171875,
lon: 7.420684814453125,
y: coordinates[0],
x: coordinates[1],
},
},
{
attrs: {
detail: '1530 payerne 5822 payerne ch vd 2',
label: ' <b>1530 Payerne</b> 2',
lat: 46.954559326171875,
lon: 7.420684814453125,
y: coordinates[0],
x: coordinates[1],
},
},
],
},
}).as('search-locations')
cy.goToMapView(
{
swisssearch: '1530 Payerne limit: 2',
},
false
)
cy.readStoreValue('state.search.query').should('eq', '1530 Payerne limit: 2')
cy.url().should('not.contain', 'swisssearch')
})
it('External WMS layer', () => {
const layerName = 'OpenData-AV'
const layerId = 'ch.swisstopo-vd.official-survey'
Expand Down
50 changes: 50 additions & 0 deletions tests/cypress/tests-e2e/search/search-results.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -402,4 +402,54 @@ describe('Test the search bar result handling', () => {
cy.readStoreValue('state.search.query').should('equal', '')
cy.get('@locationSearchResults').should('not.exist')
})
it('autoselects the first swisssearch result when swisssearch_autoselect is true', () => {
cy.intercept('**/rest/services/ech/SearchServer*?type=layers*', {
body: { results: [] },
}).as('search-layers')
const coordinates = [2598633.75, 1200386.75]
cy.intercept('**/rest/services/ech/SearchServer*?type=locations*', {
body: {
results: [
{
attrs: {
detail: '1530 payerne 5822 payerne ch vd',
label: ' <b>1530 Payerne</b>',
lat: 46.954559326171875,
lon: 7.420684814453125,
y: coordinates[0],
x: coordinates[1],
},
},
{
attrs: {
detail: '1530 payerne 5822 payerne ch vd 2',
label: ' <b>1530 Payerne</b> 2',
lat: 46.954559326171875,
lon: 7.420684814453125,
y: coordinates[0],
x: coordinates[1],
},
},
],
},
}).as('search-locations')
cy.goToMapView(
{
swisssearch: '1530 Payerne',
swisssearch_autoselect: 'true',
},
false
)
cy.readStoreValue('state.search.query').should('eq', '1530 Payerne')
cy.url().should('not.contain', 'swisssearch')
cy.url().should('not.contain', 'swisssearch_autoselect')
const acceptableDelta = 0.25

cy.readStoreValue('state.map.pinnedLocation').should((feature) => {
expect(feature).to.not.be.null
expect(feature).to.be.a('array').that.is.not.empty
expect(feature[0]).to.be.approximately(coordinates[0], acceptableDelta)
expect(feature[1]).to.be.approximately(coordinates[1], acceptableDelta)
})
})
})

0 comments on commit d667678

Please sign in to comment.