From 6bdb3b0492d6a72d150150bf6d60bf2ef3d97248 Mon Sep 17 00:00:00 2001 From: rbisson <remi.bisson@inrae.fr> Date: Mon, 3 Feb 2025 10:03:10 +0100 Subject: [PATCH 1/3] [BasicSearch] added error handling on search request ; added a HowTo component [Layout] improved styling and code logic --- public/locales/en/search.json | 69 +++++++++- public/locales/fr/search.json | 67 +++++++++- src/Utils.js | 1 - src/components/Layout/Layout.js | 48 +++---- src/components/Layout/styles.js | 6 +- src/pages/search/BasicSearch/BasicSearch.js | 20 ++- .../search/BasicSearch/HowToBasicSearch.js | 120 ++++++++++++++++++ 7 files changed, 298 insertions(+), 33 deletions(-) create mode 100644 src/pages/search/BasicSearch/HowToBasicSearch.js diff --git a/public/locales/en/search.json b/public/locales/en/search.json index 67d35bc..b25f4c6 100644 --- a/public/locales/en/search.json +++ b/public/locales/en/search.json @@ -5,10 +5,75 @@ "map": "Map" }, "sendSearchButton": "Search", + "queryError": "Your query is not formed correctly.", "basicSearch": { "switchSearchMode": "Switch to advanced search", - "searchInputPlaceholder": "Search..." - }, + "searchInputPlaceholder": "Search...", + "howTo": { + "toggleAction": "How does basic search works ?", + "examplesTitle": "A few examples", + "examples": [ + { + "query": "Cedrus", + "desc": "searches for Cedrus in any field of the standard." + }, + { + "query": "biological_material.species:Cedrus", + "desc": "searches for Cedrus <strong>only</strong>in the species field of the standard." + }, + { + "query": "Cedr?s Atlanti*", + "desc": "searches for Cedrus Atlantica, Cedris Atlanticus, and all other possibilities." + }, + { + "query": "\"Cedrus Atlantica\" +(FCBA || INRAE)", + "desc": "searches \"Cedrus Atlantica\" and <strong>necessarily</strong> FCBA or INRAE." + }, + { + "query": "\"Pinus nigra\" +experimental_site.geo_point.altitude:>1200 +experimental_site.start_date:[1970-01-01 TO 1999-01-01]", + "desc": "searches for black pine on an experimental site above 1200m high that started between 1970 and 1999." + }, + { + "query": "\"Cedrus Atlantica\" -biological_material.genetic_level:clone", + "desc": "searches for Cedrus Atlantica while excluding all clones." + } + ], + "sections": [ + { + "title": "Term based functionnality", + "content": [ + "Each term searches for an <strong>exact</strong> match among all standard fields.", + "Colon <strong>: </strong> allows to search in a <strong>specific standard field</strong>.", + "Quotes <strong>\" \"</strong> link terms <strong>in one and only</strong> character string." + ] + }, { + "title": "Wildcards", + "content": [ + "<strong>?</strong> matches any unique character.", + "<strong>*</strong> matches zero or any multiple character including an empty one." + ] + }, { + "title": "Priorities operators", + "content": [ + "Ampersand <strong>&&</strong> allows a <strong>logical and</strong> between two terms.", + "Double pipe <strong>||</strong> allows a <strong>logical or</strong> between two terms.", + "Parenthesis <strong>( )</strong> specify priority while using multiple operators.", + "<strong>+</strong> forces <strong>presence</strong> of the following terms.", + "<strong>-</strong> or <strong>!</strong> forces <strong>absence</strong> of the following terms." + ] + }, { + "title": "Comparison operators", + "content": [ + "<, >, <=, => allows numeric and date comparison. Dates in YYYY-MM-DD format.", + "Brackets <strong>[ ]</strong> to search in a inclusive interval.", + "Curly brackets <strong>{ }</strong> to search in a exclusive interval.", + "Intervals two <strong>endpoints</strong> must be linked with <strong>TO</strong>.", + "You can combine two types of brackets in a same interval: <strong>[ } or { ]</strong>." + ] + } + ] + } +}, "advancedSearch": { "switchSearchMode": "Switch to basic search", "textQueryPlaceholder": "Add fields...", diff --git a/public/locales/fr/search.json b/public/locales/fr/search.json index 101410a..df6a52b 100644 --- a/public/locales/fr/search.json +++ b/public/locales/fr/search.json @@ -5,9 +5,74 @@ "map": "Carte" }, "sendSearchButton": "Lancer la recherche", + "queryError": "Votre requête n'est pas formée correctement.", "basicSearch": { "switchSearchMode": "Passer en recherche avancée", - "searchInputPlaceholder": "Chercher..." + "searchInputPlaceholder": "Chercher...", + "howTo": { + "toggleAction": "Comment fonctionne la recherche basique ?", + "examplesTitle": "Quelques exemples", + "examples": [ + { + "query": "Cedrus", + "desc": "cherche Cedrus parmi l'ensemble des champs du standard." + }, + { + "query": "biological_material.species:Cedrus", + "desc": "cherche Cedrus <strong>uniquement</strong> dans le champ espèce du standard." + }, + { + "query": "Cedr?s Atlanti*", + "desc": "cherche Cedrus Atlantica, Cedris Atlanticus, ainsi que toutes les autres possibilités." + }, + { + "query": "\"Cedrus Atlantica\" +(FCBA || INRAE)", + "desc": "cherche \"Cedrus Atlantica\" et <strong>obligatoirement</strong> un de FCBA ou INRAE." + }, + { + "query": "\"Pinus nigra\" +experimental_site.geo_point.altitude:>1200 +experimental_site.start_date:[1970-01-01 TO 1999-01-01]", + "desc": "cherche du pin noir sur un site exp. situé à plus de 1200m d'altitude, ayant débuté entre 1970 et 1999." + }, + { + "query": "\"Cedrus Atlantica\" -biological_material.genetic_level:clone", + "desc": "cherche du Cedrus Atlantica en excluant tous les clônes." + } + ], + "sections": [ + { + "title": "Fonctionnement par mots clés", + "content": [ + "Chaque terme cherche une correspondance <strong>exacte</strong> parmi tous les champs du standard.", + "Le <strong>deux-points : </strong>permet de chercher dans un <strong>champ spécifique du standard</strong>.", + "Les guillemets <strong>\" \"</strong> lient des termes en <strong>une seule et même</strong> chaîne de caractère." + ] + }, { + "title": "Caractères de remplacement", + "content": [ + "<strong>?</strong> qui correspond à n'importe quel caractère unique.", + "<strong>*</strong> qui peut correspondre à zéro ou plusieurs caractères, y compris un caractère vide." + ] + }, { + "title": "Opérateurs de priorités", + "content": [ + "Le et commercial <strong>&&</strong> permet un <strong>et logique</strong> entre deux termes.", + "La barre verticale <strong>||</strong> permet un <strong>ou logique</strong> entre deux termes.", + "Les parenthèses <strong>( )</strong> précisent la priorité dès que vous utilisez plusieurs opérateurs.", + "<strong>+</strong> oblige <strong>la présence</strong> des termes suivants.", + "<strong>-</strong> ou <strong>!</strong> obligent <strong>l'absence</strong> des termes suivants." + ] + }, { + "title": "Opérateurs de comparaison", + "content": [ + "<, >, <=, => permettent des comparaisons avec des valeurs numériques ou des dates (au format AAAA-MM-DD).", + "Les crochets <strong>[ ]</strong> pour chercher dans un intervalle inclusif.", + "Les brackets <strong>{ }</strong> pour chercher dans un intervalle exclusif.", + "Les deux <strong>bornes</strong> d'un intervalle doivent être liés par le terme <strong>TO</strong>.", + "Vous pouvez combiner un crochet et un bracket dans le même intervalle: <strong>[ } ou { ]</strong>." + ] + } + ] + } }, "advancedSearch": { "switchSearchMode": "Passer en recherche basique", diff --git a/src/Utils.js b/src/Utils.js index 65fc6c4..c51028a 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -433,7 +433,6 @@ export const createAdvancedQueriesBySource = ( }); const queryContent = buildDslQuery(searchRequest, fields); - console.log('dslQuery', queryContent); sourcesLists.forEach((sourcesArray, index) => { let sourceParam = `"_source": [`; diff --git a/src/components/Layout/Layout.js b/src/components/Layout/Layout.js index 087c5ce..b28f490 100644 --- a/src/components/Layout/Layout.js +++ b/src/components/Layout/Layout.js @@ -1,34 +1,38 @@ import React from 'react'; import { Outlet } from 'react-router-dom'; import Header from '../../components/Header'; -import { EuiPage, EuiPageBody, EuiPageSection } from '@elastic/eui'; +import { EuiFlexGroup, EuiPage, EuiPageBody, EuiPageSection } from '@elastic/eui'; import styles from './styles.js'; import { Slide, ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; const Layout = () => { return ( - <EuiPage style={styles.page} restrictWidth={false}> - <EuiPageBody> - <ToastContainer - position="bottom-right" - autoClose={5000} - hideProgressBar - newestOnTop={false} - closeOnClick - rtl={false} - pauseOnFocusLoss - draggable - pauseOnHover - theme="light" - transition={Slide} - /> - <Header /> - <EuiPageSection style={styles.pageContent} grow={true}> - <Outlet /> - </EuiPageSection> - </EuiPageBody> - </EuiPage> + <EuiFlexGroup style={styles.page}> + <EuiPage grow={true}> + <EuiPageBody> + <ToastContainer + position="bottom-right" + autoClose={5000} + hideProgressBar + newestOnTop={false} + closeOnClick + rtl={false} + pauseOnFocusLoss + draggable + pauseOnHover + theme="light" + transition={Slide} + /> + <Header /> + <EuiFlexGroup> + <EuiPageSection grow={true}> + <Outlet /> + </EuiPageSection> + </EuiFlexGroup> + </EuiPageBody> + </EuiPage> + </EuiFlexGroup> ); }; diff --git a/src/components/Layout/styles.js b/src/components/Layout/styles.js index fd435cb..5a2a1fe 100644 --- a/src/components/Layout/styles.js +++ b/src/components/Layout/styles.js @@ -1,9 +1,7 @@ const styles = { page: { - padding: '0px', - }, - pageContent: { - backgroundColor: '#ffffff', + minHeight: '100vh', + maxHeight: '100vh', }, }; diff --git a/src/pages/search/BasicSearch/BasicSearch.js b/src/pages/search/BasicSearch/BasicSearch.js index 77f208a..8f36200 100644 --- a/src/pages/search/BasicSearch/BasicSearch.js +++ b/src/pages/search/BasicSearch/BasicSearch.js @@ -9,6 +9,9 @@ import { import { useTranslation } from 'react-i18next'; import SearchModeSwitcher from '../SearchModeSwitcher'; import { useGatekeeper } from '../../../contexts/GatekeeperContext'; +import HowToBasicSearch from './HowToBasicSearch'; +import { toast } from 'react-toastify'; +import ToastMessage from '../../../components/ToastMessage/ToastMessage'; const BasicSearch = ({ standardFields, @@ -20,7 +23,7 @@ const BasicSearch = ({ setSearchResults, setSelectedTabNumber, }) => { - const { t } = useTranslation('search'); + const { t } = useTranslation(['search', 'validation']); const client = useGatekeeper(); const [isLoading, setIsLoading] = useState(false); @@ -33,8 +36,18 @@ const BasicSearch = ({ fieldsId: standardFields, }); setIsLoading(false); - setSearchResults(result); - setSelectedTabNumber(1); + if (!result.error) { + setSearchResults(result); + setSelectedTabNumber(1); + } else if (result.statusCode === 400) { + toast.error( + <ToastMessage title={t('validation:error')} message={result.message} /> + ); + } else { + toast.error( + <ToastMessage title={t('validation:error')} message={t('search:queryError')} /> + ); + } }; return ( @@ -65,6 +78,7 @@ const BasicSearch = ({ </EuiFlexItem> </EuiFlexGroup> </form> + <HowToBasicSearch /> </EuiFlexItem> </EuiFlexGroup> </> diff --git a/src/pages/search/BasicSearch/HowToBasicSearch.js b/src/pages/search/BasicSearch/HowToBasicSearch.js new file mode 100644 index 0000000..1b4dd8a --- /dev/null +++ b/src/pages/search/BasicSearch/HowToBasicSearch.js @@ -0,0 +1,120 @@ +import React from 'react'; +import { + EuiAccordion, + EuiButtonEmpty, + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, + useGeneratedHtmlId, +} from '@elastic/eui'; +import { Trans, useTranslation } from 'react-i18next'; + +const HowToBasicSearch = () => { + const { t, ready } = useTranslation('search'); + + const Example = ({ query, desc }) => { + return ( + <EuiFlexGroup> + <EuiText color={'success'}> + <code> + <EuiIcon type="search" /> <Trans i18nKey={query} /> + </code> + </EuiText> + <EuiText> + <Trans i18nKey={desc} /> + </EuiText> + </EuiFlexGroup> + ); + }; + + const Examples = () => { + const examples = t('search:basicSearch.howTo.examples', { returnObjects: true }); + + return ( + <EuiPanel hasShadow={false} hasBorder={true}> + <EuiTitle size={'xs'}> + <p>{t('search:basicSearch.howTo.examplesTitle')}</p> + </EuiTitle> + <EuiSpacer size={'s'} /> + <EuiFlexGrid columns={1} gutterSize={'s'} direction={'column'}> + {examples.map((example, index) => { + return ( + <EuiFlexItem key={index}> + <Example query={example.query} desc={example.desc} /> + </EuiFlexItem> + ); + })} + </EuiFlexGrid> + </EuiPanel> + ); + }; + + const Section = ({ title, text }) => { + return ( + <EuiPanel hasShadow={false} hasBorder={true}> + <EuiTitle size={'xs'}> + <p>{t(title)}</p> + </EuiTitle> + <EuiPanel hasShadow={false} paddingSize={'s'}> + <EuiText> + <ul> + {text.map((item, index) => { + return ( + <li key={index}> + <Trans i18nKey={item} /> + </li> + ); + })} + </ul> + </EuiText> + </EuiPanel> + </EuiPanel> + ); + }; + + if (!ready) { + return 'Loading translations...'; + } + + const sections = t('search:basicSearch.howTo.sections', { returnObjects: true }); + + return ( + <> + <EuiSpacer size="s" /> + <EuiFlexGroup> + <EuiFlexItem> + <EuiAccordion + id={useGeneratedHtmlId({ prefix: 'howToAccordion' })} + buttonContent={ + <EuiButtonEmpty> + {t('search:basicSearch.howTo.toggleAction')} + </EuiButtonEmpty> + } + buttonElement={'div'} + > + <EuiSpacer size="s" /> + <Examples /> + <EuiSpacer size="s" /> + <EuiFlexGrid columns={2} gutterSize={'s'}> + {sections.map((section, index) => { + return ( + <EuiFlexItem key={index}> + <Section title={section.title} text={section.content} /> + </EuiFlexItem> + ); + })} + </EuiFlexGrid> + <EuiSpacer size="s" /> + </EuiAccordion> + </EuiFlexItem> + </EuiFlexGroup> + </> + ); +}; + +export default HowToBasicSearch; -- GitLab From d59173e110b22fc19e8775eb56ed4c8f930fae98 Mon Sep 17 00:00:00 2001 From: rbisson <remi.bisson@inrae.fr> Date: Mon, 3 Feb 2025 10:12:25 +0100 Subject: [PATCH 2/3] [Profile] improved display --- .../profile/UserFieldsDisplaySettings.js | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/pages/profile/UserFieldsDisplaySettings.js b/src/pages/profile/UserFieldsDisplaySettings.js index e30e423..a043783 100644 --- a/src/pages/profile/UserFieldsDisplaySettings.js +++ b/src/pages/profile/UserFieldsDisplaySettings.js @@ -1,11 +1,11 @@ import React, { useEffect, useState } from 'react'; import { - EuiSpacer, - EuiSelectable, EuiButton, - EuiFlexItem, EuiFlexGroup, + EuiFlexItem, EuiPanel, + EuiSelectable, + EuiSpacer, EuiTitle, } from '@elastic/eui'; import { Trans, useTranslation } from 'react-i18next'; @@ -131,25 +131,23 @@ const UserFieldsDisplaySettings = ({ userSettings, setUserSettings, publicFields const SelectableSettingsPanel = () => { return ( <EuiFlexItem> - <EuiPanel paddingSize="l" hasShadow={false} hasBorder={true}> - <EuiSelectable - aria-label={t('profile:fieldsDisplaySettings.selectedOptionsLabel')} - options={settingsOptions} - onChange={(newOptions) => setSettingsOptions(newOptions)} - searchable - listProps={{ bordered: true }} - style={{ minHeight: '65vh' }} - height={'full'} - > - {(list, search) => ( - <> - {search} - <EuiSpacer size={'xs'} /> - {list} - </> - )} - </EuiSelectable> - </EuiPanel> + <EuiSelectable + aria-label={t('profile:fieldsDisplaySettings.selectedOptionsLabel')} + options={settingsOptions} + onChange={(newOptions) => setSettingsOptions(newOptions)} + searchable + listProps={{ bordered: true }} + style={{ minHeight: '65vh' }} + height={'full'} + > + {(list, search) => ( + <> + {search} + <EuiSpacer size={'xs'} /> + {list} + </> + )} + </EuiSelectable> </EuiFlexItem> ); }; -- GitLab From 14ed1d5897a79208a1c0972b576188016d8f08dd Mon Sep 17 00:00:00 2001 From: rbisson <remi.bisson@inrae.fr> Date: Mon, 3 Feb 2025 10:15:38 +0100 Subject: [PATCH 3/3] [Layout] improved pages display --- src/components/Layout/Layout.js | 2 +- src/components/Layout/styles.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/Layout/Layout.js b/src/components/Layout/Layout.js index b28f490..b7d705a 100644 --- a/src/components/Layout/Layout.js +++ b/src/components/Layout/Layout.js @@ -26,7 +26,7 @@ const Layout = () => { /> <Header /> <EuiFlexGroup> - <EuiPageSection grow={true}> + <EuiPageSection grow={true} style={styles.pageContent}> <Outlet /> </EuiPageSection> </EuiFlexGroup> diff --git a/src/components/Layout/styles.js b/src/components/Layout/styles.js index 5a2a1fe..fb86e31 100644 --- a/src/components/Layout/styles.js +++ b/src/components/Layout/styles.js @@ -3,6 +3,9 @@ const styles = { minHeight: '100vh', maxHeight: '100vh', }, + pageContent: { + backgroundColor: '#ffffff', + }, }; export default styles; -- GitLab