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

[Search page] : Make filters configurable #97

Merged
merged 11 commits into from
Jul 17, 2024
49 changes: 43 additions & 6 deletions apps/datahub-e2e/src/e2e/search.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ describe('search', () => {
.find('h1')
.should(
'have.text',
" Concentrations annuelles de polluants dans l'air ambiant issues du réseau permanent de mesures en région Hauts-de-France "
' Metadata for E2E testing purpose. (this title is too long and should be cut, this title is too long and should be cut, this title is too long and should be cut, this title is too long and should be cut, this title is too long and should be cut) '
)
})
it('should filter the results when selecting multiple filter values (producer)', () => {
Expand All @@ -193,7 +193,7 @@ describe('search', () => {
cy.get('@options').first().click()
cy.get('@options').eq(1).click()
cy.get('@options').eq(2).click()
cy.get('mel-datahub-results-card-search').should('have.length', 3)
cy.get('mel-datahub-results-card-search').should('have.length', 5)
})
it('should filter by quality score', () => {
cy.get('[data-cy="filterExpandBtn"]').click()
Expand Down Expand Up @@ -222,12 +222,15 @@ describe('search', () => {
cy.get('@result-cards').should('have.length', 3)
cy.get('@filters').eq(1).click()
getFilterOptions()
cy.get('@options').eq(12).click()
cy.get('@options').eq(5).click()
cy.get('@result-cards').should('have.length', 1)
cy.get('@result-cards')
.first()
.find('h1')
.should('have.text', ' Accroches vélos MEL ')
.should(
'have.text',
' SCoT (Schéma de cohérence territoriale) en région Hauts-de-France '
)
})
it('should combine search input and filters and display a message if no results found', () => {
cy.get('mel-datahub-autocomplete input').type('test')
Expand All @@ -237,7 +240,7 @@ describe('search', () => {
cy.get('@result-cards').should('have.length', 3)
cy.get('@filters').eq(1).click()
getFilterOptions()
cy.get('@options').eq(10).click()
cy.get('@options').eq(8).click()
cy.get('[data-cy=searchResults]').should(
'have.text',
' Aucune correspondance. '
Expand All @@ -257,7 +260,7 @@ describe('search', () => {
cy.get('@filters').eq(3).click()
getFilterOptions()
cy.get('@options').eq(1).click()
cy.get('@result-cards').should('have.length', 2)
cy.get('@result-cards').should('have.length', 9)
cy.get('body').click()
cy.get('[data-cy=filterResetBtn]').click()
cy.get('@result-cards').should('have.length', 14)
Expand All @@ -269,6 +272,40 @@ describe('search', () => {
cy.get('mel-datahub-filter-dropdown').should('have.length', 3)
})
})
describe.only('Filters from config', () => {
beforeEach(() => {
// this will enable all available filters
cy.intercept('GET', '/assets/configuration/default.toml', {
fixture: 'config-with-all-filters.toml',
})
cy.visit('/search')
})
it('should display all filters', () => {
cy.get('[data-cy="filterExpandBtn"]').click()
cy.get('@filters').filter(':visible').should('have.length', 12)
cy.get('@filters')
.children()
.then(($dropdowns) =>
$dropdowns
.toArray()
.map((dropdown) => dropdown.getAttribute('data-cy-field'))
)
.should('eql', [
'publisher',
'format',
'publicationYear',
'inspireKeyword',
'keyword',
'topic',
'isSpatial',
'license',
'resourceType',
'representationType',
'revisionYear',
'categoryKeyword',
])
})
})
})
describe('pagination', () => {
beforeEach(() => {
Expand Down
2 changes: 2 additions & 0 deletions apps/datahub-e2e/src/fixtures/config-with-all-filters.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[search]
advanced_filters = ['publisher', 'format', 'publicationYear', 'inspireKeyword', 'keyword', 'topic', 'isSpatial', 'license', 'resourceType', 'representationType', 'revisionYear', 'categoryKeyword']
5 changes: 5 additions & 0 deletions apps/datahub/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
"glob": "**/*",
"input": "resources/assets",
"output": "./assets"
},
{
"glob": "*",
"input": "conf",
"output": "assets/configuration/"
}
],
"styles": ["resources/styles.css"],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { marker } from '@biesbjerg/ngx-translate-extract-marker'
import { RouterFacade } from 'geonetwork-ui'
import { getOptionalSearchConfig } from '@mel-dataplatform/mel'

marker('mel.datahub.search.filters.topic')
marker('mel.datahub.search.filters.categoryKeyword')
Expand All @@ -11,6 +12,12 @@ marker('mel.datahub.search.filters.qualityScore')
marker('mel.datahub.search.filters.territories')
marker('mel.datahub.search.filters.producerOrg')
marker('mel.datahub.search.filters.publisherOrg')
marker('mel.datahub.search.filters.format')
marker('mel.datahub.search.filters.inspireKeyword')
marker('mel.datahub.search.filters.keyword')
marker('mel.datahub.search.filters.isSpatial')
marker('mel.datahub.search.filters.resourceType')
marker('mel.datahub.search.filters.representationType')

@Component({
selector: 'mel-datahub-search-filters',
Expand All @@ -21,14 +28,16 @@ marker('mel.datahub.search.filters.publisherOrg')
export class SearchFiltersComponent {
constructor(private routerFacade: RouterFacade) {}
displayCount = 3
searchConfig = [
'categoryKeyword',
'organization',
'publicationYear',
'license',
'qualityScore',
'territories',
].map((filter) => ({
searchConfig = (
getOptionalSearchConfig().ADVANCED_FILTERS || [
'categoryKeyword',
'organization',
'revisionYear',
'license',
'qualityScore',
'territories',
]
).map((filter) => ({
fieldName: filter,
title: `mel.datahub.search.filters.${filter}`,
}))
Expand Down
6 changes: 6 additions & 0 deletions apps/datahub/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="assets/css/materials-symbols-outline.css" />
<link rel="stylesheet" href="assets/css/default-fonts.css" />
<link
rel="preload"
href="assets/configuration/default.toml"
as="fetch"
crossorigin
/>
</head>
<body>
<mel-datahub-root></mel-datahub-root>
Expand Down
13 changes: 8 additions & 5 deletions apps/datahub/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
import { AppModule } from './app/app.module'
import { loadAppConfig } from '@mel-dataplatform/mel'

platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
loadAppConfig().then(() => {
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err))
})
12 changes: 12 additions & 0 deletions conf/default.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# MEL configuration
# Note: this file's syntax is TOML (https://toml.io/)

### SEARCH SETTINGS

# This section contains settings used for fine-tuning the search experience
[search]

# The advanced search filters available to the user can be customized with this setting.
# The following fields can be used for filtering: 'organization', 'format', 'publicationYear', 'inspireKeyword', 'keyword', 'topic', 'isSpatial', 'license', 'resourceType', 'representationType', 'revisionYear', 'categoryKeyword', 'qualityScore', 'territories', 'publisherOrg', 'producerOrg'
# any other field will be ignored
# advanced_filters = ['format', 'topic', 'keyword', 'organization', 'publisherOrg', 'producerOrg']
1 change: 1 addition & 0 deletions libs/mel/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './lib/mel.module'
export * from './lib/embedded.translate.loader'
export * from './lib/route.utils'
export * from './lib/util/app-config'
61 changes: 61 additions & 0 deletions libs/mel/src/lib/util/app-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import * as TOML from '@ltd/j-toml'
import { parseConfigSection } from './parse-utils'
import { SearchConfig } from './model'

let searchConfig: SearchConfig | null = null

export function getOptionalSearchConfig(): SearchConfig | null {
return searchConfig
}

let appConfigLoaded = false

export function loadAppConfig() {
return fetch('assets/configuration/default.toml')
.then((resp) => {
if (!resp.ok) throw new Error('Configuration file could not be loaded')
return resp.text()
})
.then((conf) => {
let parsed
try {
parsed = TOML.parse(conf, { joiner: '\n', bigint: false })
} catch (e: unknown) {
throw new Error(
`An error occurred when parsing the configuration file: ${
(e as Error).message
}`
)
}
const errors = []
const warnings = []

const parsedSearchSection = parseConfigSection(
parsed,
'search',
[],
['advanced_filters'],
warnings,
errors
)
searchConfig =
parsedSearchSection === null
? null
: ({
ADVANCED_FILTERS: parsedSearchSection['advanced_filters'],
} as SearchConfig)
if (errors.length) {
throw new Error(`One or more mandatory settings were missing from the configuration file.
${errors.join('\n')}`)
} else if (warnings.length) {
console.warn(`One or more unexpected settings were encountered in the configuration file.
${warnings.join('\n')}`)
}

appConfigLoaded = true
})
}

export function isConfigLoaded() {
return appConfigLoaded
}
8 changes: 8 additions & 0 deletions libs/mel/src/lib/util/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { SearchPreset } from 'geonetwork-ui'

export interface SearchConfig {
FILTER_GEOMETRY_URL?: string
FILTER_GEOMETRY_DATA?: string
SEARCH_PRESET?: SearchPreset[]
ADVANCED_FILTERS?: []
}
48 changes: 48 additions & 0 deletions libs/mel/src/lib/util/parse-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export function parseConfigSection(
fullConfigObj: Record<string, Record<string, unknown>>,
sectionName: string,
mandatoryKeys: string[],
optionalKeys: string[],
outWarnings: string[],
outErrors: string[]
): Record<string, unknown> | null {
if (typeof fullConfigObj[sectionName] !== 'object') {
if (mandatoryKeys.length === 0) return null
outErrors.push(`The [${sectionName}] mandatory section is missing.`)
return null
}

const sectionConf = fullConfigObj[sectionName] as Record<string, string>
const keysCheck = checkKeys(sectionConf, mandatoryKeys, optionalKeys)

if (keysCheck.missing.length) {
// note: this is not thrown to allow merging several Errors down the line
outErrors.push(
`In the [${sectionName}] section: ${keysCheck.missing.join(', ')}`
)
return null
} else if (keysCheck.unrecognized.length) {
outWarnings.push(
`In the [${sectionName}] section: ${keysCheck.unrecognized.join(', ')}`
)
keysCheck.unrecognized.forEach((key) => delete sectionConf[key])
}

return sectionConf
}

const checkKeys = (
input: Record<string, string>,
mandatory: string[],
optional: string[]
) => {
const keys = Object.keys(input)
const missing = mandatory.filter((key) => keys.indexOf(key) === -1)
const unrecognized = keys.filter(
(key) => mandatory.indexOf(key) === -1 && optional.indexOf(key) === -1
)
return {
missing,
unrecognized,
}
}
12 changes: 10 additions & 2 deletions resources/translations/en_MEL.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,30 @@
"mel.datahub.multiselect.filter.placeholder": "",
"mel.datahub.search.clear": "",
"mel.datahub.search.filters.categoryKeyword": "",
"mel.datahub.search.filters.format": "",
cmoinier marked this conversation as resolved.
Show resolved Hide resolved
"mel.datahub.search.filters.inspireKeyword": "",
"mel.datahub.search.filters.isSpatial": "",
"mel.datahub.search.filters.keyword": "",
"mel.datahub.search.filters.license": "",
"mel.datahub.search.filters.maxValue": "",
"mel.datahub.search.filters.minValue": "",
"mel.datahub.search.filters.more": "",
"mel.datahub.search.filters.organization": "",
"mel.datahub.search.filters.producerOrg": "",
"mel.datahub.search.filters.publicationYear": "",
"mel.datahub.search.filters.publisherOrg": "",
"mel.datahub.search.filters.qualityScore": "",
"mel.datahub.search.filters.range.from": "",
"mel.datahub.search.filters.range.to": "",
"mel.datahub.search.filters.reduce": "",
"mel.datahub.search.filters.reset": "",
"mel.datahub.search.filters.territories": "",
"mel.datahub.search.filters.topic": "",
"mel.datahub.search.filters.validate": "",
"mel.datahub.search.filters.publicationYear": "",
"mel.datahub.search.filters.publisher": "",
"mel.datahub.search.filters.representationType": "",
"mel.datahub.search.filters.resourceType": "",
"mel.datahub.search.filters.revisionYear": "",
"mel.datahub.search.filters.topic": "",
"mel.datahub.search.form.description": "",
"mel.datahub.search.form.title": "",
"mel.datahub.search.hits.found": "",
Expand Down
10 changes: 9 additions & 1 deletion resources/translations/fr_MEL.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@
"mel.datahub.multiselect.filter.placeholder": "Rechercher",
"mel.datahub.search.clear": "Effacer",
"mel.datahub.search.filters.categoryKeyword": "Thématique",
"mel.datahub.search.filters.format": "Format",
"mel.datahub.search.filters.inspireKeyword": "Mot-clé INSPIRE",
"mel.datahub.search.filters.isSpatial": "Données spatiales",
"mel.datahub.search.filters.keyword": "Mot-clé",
"mel.datahub.search.filters.license": "Licence",
"mel.datahub.search.filters.maxValue": "Valeur maximale",
"mel.datahub.search.filters.minValue": "Valeur minimale",
"mel.datahub.search.filters.more": "Plus de filtres",
"mel.datahub.search.filters.organization": "Organisation",
"mel.datahub.search.filters.producerOrg": "Producteur",
"mel.datahub.search.filters.publicationYear": "Date",
"mel.datahub.search.filters.publisherOrg": "Distributeur",
"mel.datahub.search.filters.qualityScore": "Score de qualité",
"mel.datahub.search.filters.range.from": "De :",
Expand All @@ -27,6 +30,11 @@
"mel.datahub.search.filters.territories": "Territoires",
"mel.datahub.search.filters.topic": "Catégories",
"mel.datahub.search.filters.validate": "Valider",
"mel.datahub.search.filters.publicationYear": "Année de publication",
"mel.datahub.search.filters.publisher": "Producteur",
"mel.datahub.search.filters.representationType": "Type de représentation",
"mel.datahub.search.filters.resourceType": "Type de ressource",
"mel.datahub.search.filters.revisionYear": "Date",
"mel.datahub.search.form.description": "Vous pouvez utiliser la barre de recherche ou les différents filtres situés ci-dessous pour trouver un jeu de données plus rapidement.",
"mel.datahub.search.form.title": "Trouver un jeu de données",
"mel.datahub.search.hits.found": "{hits, plural, =0{Aucune correspondance.} one{1 enregistrement trouvé.} other{Ensemble des données: {hits}}}",
Expand Down
Loading
Loading