diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1bda044..c4a153e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -16,10 +16,10 @@ jobs:
     steps:
       - name: Checkout the repo
         uses: actions/checkout@v2
-      - name: Set up Python 3.7
+      - name: Set up Python 3.8
         uses: actions/setup-python@v2
         with:
-          python-version: '3.7'
+          python-version: '3.8'
       - name: Run pre-commit action
         uses: pre-commit/action@v2.0.0
   build:
@@ -34,16 +34,16 @@ jobs:
       - name: Install Python
         uses: actions/setup-python@v2
         with:
-          python-version: '3.7'
+          python-version: '3.8'
           architecture: 'x64'
 
       - name: Setup pip cache
         uses: actions/cache@v2
         with:
           path: ~/.cache/pip
-          key: pip-3.7-${{ hashFiles('package.json') }}
+          key: pip-3.8-${{ hashFiles('package.json') }}
           restore-keys: |
-            pip-3.7-
+            pip-3.8-
             pip-
 
       - name: Get yarn cache directory path
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index bc30176..6fa1269 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -15,10 +15,10 @@ jobs:
       - name: Checkout source
         uses: actions/checkout@v2
 
-      - name: Set up Python 3.7
+      - name: Set up Python 3.8
         uses: actions/setup-python@v2
         with:
-          python-version: '3.7'
+          python-version: '3.8'
 
       - name: Check that the current version isn't already on PyPi
         run: |
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..c6e0b1b
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,40 @@
+name: Run Tests
+
+on:
+  push:
+    branches: [master, develop]
+  pull_request:
+    branches: [master, develop]
+  schedule:
+    - cron: '0 7 * * 1'
+  workflow_dispatch:
+
+jobs:
+  tests:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: [3.8, '3.11']
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Install Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-version }}
+          architecture: 'x64'
+
+      - name: Setup pip cache
+        uses: actions/cache@v2
+        with:
+          path: ~/.cache/pip
+          key: pip-${{ matrix.python-version }}-${{ hashFiles('package.json') }}
+          restore-keys: |
+            pip-${{ matrix.python-version }}-
+            pip-
+
+      - name: Install the extension
+        run: python -m pip install .[dev]
+      - name: Test with pytest
+        run: pytest -v
diff --git a/CHANGES.md b/CHANGES.md
index 46fdf6d..dd24ccd 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,12 @@
 # Release history
 
+## 3.5.0 (2024-02-08)
+
+- New `provider` filtering [(#127)](https://github.com/CS-SI/eodag-labextension/pull/127)
+- New python tests [(#127)](https://github.com/CS-SI/eodag-labextension/pull/127)[(#130)](https://github.com/CS-SI/eodag-labextension/pull/130)
+- Product type `title` instead of longer `abstract` displayed in dropdown list tooltip [(#131)](https://github.com/CS-SI/eodag-labextension/pull/131)
+- Supported python versions starting from `3.8` to `3.11` [(#132)](https://github.com/CS-SI/eodag-labextension/pull/132)
+
 ## 3.4.0 (2023-10-12)
 
 - Updates internal geometry format and removes exposed REST API [(#120)](https://github.com/CS-SI/eodag-labextension/pull/120)
diff --git a/README.md b/README.md
index ca73f56..56c9cfc 100644
--- a/README.md
+++ b/README.md
@@ -72,8 +72,11 @@ Click on this icon in the left of JupyterLab interface to open EODAG-Labextensio
 
 With displayed search form, you can enter search extent and following search criteria:
 
+- **Provider**: the provider on which to perform the search. If no provider is selected, search will loop on providers
+  by [priority](https://eodag.readthedocs.io/en/stable/getting_started_guide/configure.html#priority-setting), and
+  return the first non empty results.
 - **Product type**: the searched product type. List filtering is performed using product types description keywords.
-  For each entry of the drop-down list, a tooltip is displayed at hovering time with corresponding description.
+  For each entry of the drop-down list, a tooltip is displayed at hovering time with corresponding title.
   ![product types](https://raw.githubusercontent.com/CS-SI/eodag-labextension/develop/notebooks/images/eodag_labext_product_types.png)
 
 - **Date range**: minimal and maximal dates of the search temporal window.
diff --git a/eodag_labextension/handlers.py b/eodag_labextension/handlers.py
index e0fa6b6..7f9b454 100644
--- a/eodag_labextension/handlers.py
+++ b/eodag_labextension/handlers.py
@@ -4,29 +4,100 @@
 
 """Tornado web requests handlers"""
 
-import json
 import re
 
+import orjson
 import tornado
-from eodag.rest.utils import eodag_api, get_product_types, search_products
+from eodag.rest.utils import eodag_api, search_products
 from eodag.utils import parse_qs
-from eodag.utils.exceptions import AuthenticationError, NoMatchingProductType, UnsupportedProductType, ValidationError
+from eodag.utils.exceptions import (
+    AuthenticationError,
+    NoMatchingProductType,
+    UnsupportedProductType,
+    UnsupportedProvider,
+    ValidationError,
+)
 from jupyter_server.base.handlers import APIHandler
 from jupyter_server.utils import url_path_join
 
 
 class ProductTypeHandler(APIHandler):
-    """Product type listing handler
+    """Product type listing handlerd"""
+
+    @tornado.web.authenticated
+    def get(self):
+        """Get endpoint"""
+        query_dict = parse_qs(self.request.query)
+
+        provider = None
+        if "provider" in query_dict and isinstance(query_dict["provider"], list) and len(query_dict["provider"]) > 0:
+            provider = query_dict.pop("provider")[0]
+        provider = None if not provider or provider == "null" else provider
+
+        try:
+            product_types = eodag_api.list_product_types(provider=provider)
+        except UnsupportedProvider as e:
+            self.set_status(400)
+            self.finish({"error": str(e)})
+            return
+
+        self.write(orjson.dumps(product_types))
 
-    .. note::
 
-        Product types endpoint filtered by provider not implemented"""
+class ProvidersHandler(APIHandler):
+    """Providers listing handler"""
 
     @tornado.web.authenticated
     def get(self):
         """Get endpoint"""
 
-        self.write(json.dumps(get_product_types()))
+        available_providers_kwargs = {}
+        query_dict = parse_qs(self.request.query)
+        if (
+            "product_type" in query_dict
+            and isinstance(query_dict["product_type"], list)
+            and len(query_dict["product_type"]) > 0
+        ):
+            available_providers_kwargs["product_type"] = query_dict["product_type"][0]
+        available_providers = eodag_api.available_providers(**available_providers_kwargs)
+
+        all_providers_list = [
+            dict(
+                provider=provider,
+                priority=conf.priority,
+                description=getattr(conf, "description", None),
+                url=getattr(conf, "url", None),
+            )
+            for provider, conf in eodag_api.providers_config.items()
+            if provider in available_providers
+        ]
+        all_providers_list.sort(key=lambda x: (x["priority"] * -1, x["provider"]))
+
+        returned_providers = []
+        if "keywords" in query_dict and isinstance(query_dict["keywords"], list) and len(query_dict["keywords"]) > 0:
+            # 1. List providers starting with given keyword
+            first_keyword = query_dict["keywords"][0].lower()
+            returned_providers = [p for p in all_providers_list if p["provider"].lower().startswith(first_keyword)]
+            providers_ids = [p["provider"] for p in returned_providers]
+
+            # 2. List providers containing given keyword
+            returned_providers += [
+                p
+                for p in all_providers_list
+                if first_keyword in p["provider"].lower() and p["provider"] not in providers_ids
+            ]
+            providers_ids = [p["provider"] for p in returned_providers]
+
+            # 3. List providers containing given keyword in decription
+            returned_providers += [
+                p
+                for p in all_providers_list
+                if first_keyword in p["description"].lower() and p["provider"] not in providers_ids
+            ]
+        else:
+            returned_providers = all_providers_list
+
+        self.write(orjson.dumps(returned_providers))
 
 
 class GuessProductTypeHandler(APIHandler):
@@ -37,23 +108,59 @@ def get(self):
         """Get endpoint"""
 
         query_dict = parse_qs(self.request.query)
-        guess_kwargs = {}
 
-        # ["aa bb", "cc-dd_ee"] to "*aa* *bb* *cc* **dd* *ee*"
-        for k, v in query_dict.items():
-            guess_kwargs[k] = re.sub(r"(\S+)", r"*\1*", " ".join(v).replace("-", " ").replace("_", " "))
+        provider = None
+        if "provider" in query_dict and isinstance(query_dict["provider"], list) and len(query_dict["provider"]) > 0:
+            provider = query_dict.pop("provider")[0]
+        provider = None if not provider or provider == "null" else provider
 
         try:
-            # guessed product types ids
-            guessed_ids_list = eodag_api.guess_product_type(**guess_kwargs)
-            # product types with full associated metadata
-            guessed_list = [
-                dict({"ID": k}, **v) for k, v in eodag_api.product_types_config.source.items() if k in guessed_ids_list
-            ]
-
-            self.write(json.dumps(guessed_list))
+            returned_product_types = []
+            # fetch all product types
+            all_product_types = eodag_api.list_product_types(provider=provider)
+
+            if (
+                "keywords" in query_dict
+                and isinstance(query_dict["keywords"], list)
+                and len(query_dict["keywords"]) > 0
+            ):
+                # 1. List product types starting with given keywords
+                first_keyword = query_dict["keywords"][0].lower()
+                returned_product_types = [pt for pt in all_product_types if pt["ID"].lower().startswith(first_keyword)]
+                returned_product_types_ids = [pt["ID"] for pt in returned_product_types]
+
+                # 2. List product types containing keyword
+                returned_product_types += [
+                    pt
+                    for pt in all_product_types
+                    if first_keyword in pt["ID"].lower() and pt["ID"] not in returned_product_types_ids
+                ]
+                returned_product_types_ids += [pt["ID"] for pt in returned_product_types]
+
+                # 3. Append guessed product types
+                guess_kwargs = {}
+                # ["aa bb", "cc-dd_ee"] to "*aa* *bb* *cc* **dd* *ee*"
+                for k, v in query_dict.items():
+                    guess_kwargs[k] = re.sub(r"(\S+)", r"*\1*", " ".join(v).replace("-", " ").replace("_", " "))
+
+                # guessed product types ids
+                guessed_ids_list = eodag_api.guess_product_type(**guess_kwargs)
+                # product types with full associated metadata
+                returned_product_types += [
+                    pt
+                    for pt in all_product_types
+                    if pt["ID"] in guessed_ids_list and pt["ID"] not in returned_product_types_ids
+                ]
+            else:
+                returned_product_types = all_product_types
+
+            self.write(orjson.dumps(returned_product_types))
         except NoMatchingProductType:
-            self.write(json.dumps([]))
+            self.write(orjson.dumps([]))
+        except UnsupportedProvider as e:
+            self.set_status(400)
+            self.finish({"error": str(e)})
+            return
 
 
 class SearchHandler(APIHandler):
@@ -63,13 +170,17 @@ class SearchHandler(APIHandler):
     def post(self, product_type):
         """Post endpoint"""
 
-        arguments = json.loads(self.request.body)
+        arguments = orjson.loads(self.request.body)
 
         # move geom to intersects parameter
         geom = arguments.pop("geom", None)
         if geom:
             arguments["intersects"] = geom
 
+        provider = arguments.pop("provider", None)
+        if provider and provider != "null":
+            arguments["provider"] = provider
+
         try:
             response = search_products(product_type, arguments, stac_formatted=False)
         except ValidationError as e:
@@ -121,12 +232,14 @@ def setup_handlers(web_app, url_path):
     # matching patterns
     host_pattern = ".*$"
     product_types_pattern = url_path_join(base_url, url_path, "product-types")
+    providers_pattern = url_path_join(base_url, url_path, "providers")
     guess_product_types_pattern = url_path_join(base_url, url_path, "guess-product-type")
     search_pattern = url_path_join(base_url, url_path, r"(?P<product_type>[\w-]+)")
 
     # handlers added for each pattern
     handlers = [
         (product_types_pattern, ProductTypeHandler),
+        (providers_pattern, ProvidersHandler),
         (guess_product_types_pattern, GuessProductTypeHandler),
         (MethodAndPathMatch("POST", search_pattern), SearchHandler),
     ]
diff --git a/notebooks/images/eodag_labext_form.png b/notebooks/images/eodag_labext_form.png
index 9fbdcd9..e53781c 100644
Binary files a/notebooks/images/eodag_labext_form.png and b/notebooks/images/eodag_labext_form.png differ
diff --git a/notebooks/images/eodag_labext_product_types.png b/notebooks/images/eodag_labext_product_types.png
index 5e5176a..362467f 100644
Binary files a/notebooks/images/eodag_labext_product_types.png and b/notebooks/images/eodag_labext_product_types.png differ
diff --git a/notebooks/user_manual.ipynb b/notebooks/user_manual.ipynb
index a1396bb..d0365f9 100644
--- a/notebooks/user_manual.ipynb
+++ b/notebooks/user_manual.ipynb
@@ -71,7 +71,10 @@
     "\n",
     "With displayed search form, you can enter search extent and following search criteria:\n",
     "\n",
-    "* **Product type**: the searched product type. List filtering is performed using product types description keywords. For each entry of the drop-down list, a tooltip is displayed at hovering time with corresponding description.\n",
+    "* **Provider**: the provider on which to perform the search. If no provider is selected, search will loop on providers\n",
+    "  by [priority](https://eodag.readthedocs.io/en/stable/getting_started_guide/configure.html#priority-setting), and\n",
+    "  return the first non empty results.\n",
+    "* **Product type**: the searched product type. List filtering is performed using product types description keywords. For each entry of the drop-down list, a tooltip is displayed at hovering time with corresponding title.\n",
     "<img style=\"display: block;\" src=\"https://raw.githubusercontent.com/CS-SI/eodag-labextension/develop/notebooks/images/eodag_labext_product_types.png\" alt=\"product types\">\n",
     "\n",
     "* **Date range**: minimal and maximal dates of the search temporal window.\n",
@@ -265,7 +268,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.10.4"
+   "version": "3.10.12"
   },
   "widgets": {
    "application/vnd.jupyter.widget-state+json": {
diff --git a/package.json b/package.json
index cfdd13c..c4a2dc9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eodag-labextension",
-  "version": "3.4.0",
+  "version": "3.5.0",
   "description": "Searching remote sensed imagery from various image providers",
   "keywords": [
     "jupyter",
diff --git a/setup.py b/setup.py
index dbcddad..f49951c 100644
--- a/setup.py
+++ b/setup.py
@@ -62,21 +62,22 @@
         "tornado>=6.0.3,<7.0.0",
         "notebook>=6.0.3,<7.0.0",
         "eodag[notebook]>=2.8.0",
+        "orjson",
     ],
-    extras_require={"dev": ["black", "pre-commit"]},
+    extras_require={"dev": ["black", "pre-commit", "pytest"]},
     zip_safe=False,
     include_package_data=True,
-    python_requires=">=3.7",
+    python_requires=">=3.8",
     platforms="Linux, Mac OS X, Windows",
     keywords=["Jupyter", "JupyterLab", "JupyterLab3"],
     classifiers=[
         "License :: OSI Approved :: Apache Software License",
         "Programming Language :: Python",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
         "Programming Language :: Python :: 3.10",
+        "Programming Language :: Python :: 3.11",
         "Framework :: Jupyter",
     ],
 )
diff --git a/src/Autocomplete.tsx b/src/Autocomplete.tsx
index 9b62b7d..53f8a8a 100644
--- a/src/Autocomplete.tsx
+++ b/src/Autocomplete.tsx
@@ -14,11 +14,6 @@ import ReactTooltip from 'react-tooltip';
 import { OptionTypeBase } from 'react-select/src/types';
 import AsyncSelect from 'react-select/async';
 
-import { showErrorMessage } from '@jupyterlab/apputils';
-import { URLExt } from '@jupyterlab/coreutils';
-import { ServerConnection } from '@jupyterlab/services';
-import { EODAG_SERVER_ADRESS } from './config';
-import { map } from 'lodash';
 import { IOptionTypeBase } from './FormComponent';
 
 function NoOptionsMessage(props: any) {
@@ -92,57 +87,30 @@ interface IProps {
   suggestions: OptionTypeBase[];
   value: string;
   handleChange: any;
+  label: string;
+  placeholder?: string;
+  loadSuggestions?: (inputValue: string) => Promise<IOptionTypeBase[]>;
 }
 
 class IntegrationReactSelect extends React.Component<IProps> {
   render() {
-    const { suggestions, value, handleChange } = this.props;
+    const {
+      label,
+      suggestions,
+      value,
+      handleChange,
+      placeholder,
+      loadSuggestions
+    } = this.props;
+
     const currentValue: OptionTypeBase = value
       ? suggestions.find(e => e.value === value)
       : undefined;
 
-    const guessProductTypes = async (inputValue: string) => {
-      const _serverSettings = ServerConnection.makeSettings();
-      const _eodag_server = URLExt.join(
-        _serverSettings.baseUrl,
-        `${EODAG_SERVER_ADRESS}`
-      );
-
-      return fetch(
-        URLExt.join(_eodag_server, `guess-product-type?keywords=${inputValue}`),
-        {
-          credentials: 'same-origin'
-        }
-      )
-        .then(response => {
-          if (response.status >= 400) {
-            showErrorMessage(
-              `Unable to contact the EODAG server. Are you sure the adress is ${_eodag_server}/ ?`,
-              {}
-            );
-            throw new Error('Bad response from server');
-          }
-          return response.json();
-        })
-        .then(products => {
-          const guessProductTypes = map(products, product => ({
-            value: product.ID,
-            label: product.ID,
-            description: product.abstract
-          }));
-          return guessProductTypes;
-        });
-    };
-
-    const loadSuggestions = (inputValue: string) =>
-      new Promise<IOptionTypeBase[]>(resolve => {
-        resolve(guessProductTypes(inputValue));
-      });
-
     return (
       <div className="jp-EodagWidget-field">
         <label className="jp-EodagWidget-input-name">
-          Product type
+          {label}
           <div
             style={{
               marginTop: 10
@@ -151,13 +119,12 @@ class IntegrationReactSelect extends React.Component<IProps> {
             <AsyncSelect
               className="jp-EodagWidget-select"
               classNamePrefix="jp-EodagWidget-select"
-              cacheOptions
               defaultOptions={suggestions}
               loadOptions={loadSuggestions}
               components={listcomponents}
               value={currentValue}
               onChange={handleChange}
-              placeholder="S2_..."
+              placeholder={placeholder}
               isClearable
             />
           </div>
diff --git a/src/CodeGenerator.ts b/src/CodeGenerator.ts
index 1d7cad9..1c43f24 100644
--- a/src/CodeGenerator.ts
+++ b/src/CodeGenerator.ts
@@ -17,7 +17,8 @@ const formatCode = (
     productType,
     geometry,
     cloud,
-    additionnalParameters
+    additionnalParameters,
+    provider
   }: IFormInput,
   replaceCode: boolean
 ) => {
@@ -44,7 +45,12 @@ ${standardMessage}`
 geometry = "${geojsonToWKT(geometry)}"`;
   }
   code += `
-search_results, total_count = dag.search(
+search_results, total_count = dag.search(`;
+  if (provider) {
+    code += `
+    provider="${provider}",`;
+  }
+  code += `
     productType="${productType}",`;
   if (geometryIsOk) {
     code += `
diff --git a/src/FormComponent.tsx b/src/FormComponent.tsx
index 0f3bd26..573a727 100644
--- a/src/FormComponent.tsx
+++ b/src/FormComponent.tsx
@@ -12,14 +12,10 @@ import {
   UseFormReturn
 } from 'react-hook-form';
 import { showErrorMessage } from '@jupyterlab/apputils';
-import { URLExt } from '@jupyterlab/coreutils';
-import { ServerConnection } from '@jupyterlab/services';
-import { map } from 'lodash';
 import DatePicker from 'react-datepicker';
 import 'react-datepicker/dist/react-datepicker.css';
 import 'isomorphic-fetch';
 import Autocomplete from './Autocomplete';
-import { EODAG_SERVER_ADRESS } from './config';
 import SearchService from './SearchService';
 import { ChangeEvent } from 'react';
 import MapExtentComponent from './MapExtentComponent';
@@ -36,6 +32,7 @@ import {
 } from './icones.js';
 import ReactTooltip from 'react-tooltip';
 import { ThreeDots } from 'react-loader-spinner';
+import { useFetchProduct, useFetchProvider } from './hooks/useFetchData';
 
 export interface IProps {
   handleShowFeature: any;
@@ -44,10 +41,21 @@ export interface IProps {
   isNotebookCreated: any;
   commands: any;
 }
+
 export interface IOptionTypeBase {
   [key: string]: any;
 }
 
+export interface IProduct {
+  ID: string;
+  title: string;
+}
+
+export interface IProvider {
+  provider: string;
+  description: string;
+}
+
 export const FormComponent: FC<IProps> = ({
   handleShowFeature,
   saveFormValues,
@@ -56,6 +64,7 @@ export const FormComponent: FC<IProps> = ({
   commands
 }) => {
   const [productTypes, setProductTypes] = useState<IOptionTypeBase[]>();
+  const [providers, setProviders] = useState<IOptionTypeBase[]>();
   const defaultStartDate: Date = undefined;
   const defaultEndDate: Date = undefined;
   const [startDate, setStartDate] = useState(undefined);
@@ -63,7 +72,8 @@ export const FormComponent: FC<IProps> = ({
   const [cloud, setCloud] = useState(100);
   const [isLoadingSearch, setIsLoadingSearch] = useState(false);
   const [openModal, setOpenModal] = useState(true);
-  const [selectValue, setSelectValue] = useState(null);
+  const [providerValue, setProviderValue] = useState(null);
+  const [productTypeValue, setProductTypeValue] = useState(null);
 
   const {
     control,
@@ -81,53 +91,24 @@ export const FormComponent: FC<IProps> = ({
   });
 
   useEffect(() => {
-    // Fetch product types
-    const _serverSettings = ServerConnection.makeSettings();
-    const _eodag_server = URLExt.join(
-      _serverSettings.baseUrl,
-      `${EODAG_SERVER_ADRESS}`
-    );
-    fetch(URLExt.join(_eodag_server, 'product-types/'), {
-      credentials: 'same-origin'
-    })
-      .then(response => {
-        if (response.status >= 400) {
-          showErrorMessage(
-            `Unable to contact the EODAG server. Are you sure the adress is ${_eodag_server}/ ?`,
-            {}
-          );
-          throw new Error('Bad response from server');
-        }
-        return response.json();
-      })
-      .then(products => {
-        const productTypes = map(products, product => ({
-          value: product.ID,
-          label: product.ID,
-          description: product.abstract
-        }));
-        setProductTypes(productTypes);
-      })
-      .catch(() => {
-        showErrorMessage(
-          `Unable to contact the EODAG server. Are you sure the adress is ${_eodag_server}/ ?`,
-          {}
-        );
-      });
-  }, []);
+    const fetchData = async () => {
+      const fetchProduct = useFetchProduct();
+      const productList = await fetchProduct(providerValue);
+      setProductTypes(productList);
+    };
 
-  // useEffect(
-  //   () => {
-  //     if (!_.isEmpty(errors)) {
-  //       showErrorMessage(
-  //         'The following fields are required',
-  //         _.keys(errors).join(', ')
-  //       ).then(() => clearErrors());
-  //     }
-  //   },
-  //   // useEffect is not triggered with only errors as dependency thus we need to list all its elements
-  //   [errors]
-  // );
+    fetchData();
+  }, [providerValue]);
+
+  useEffect(() => {
+    const fetchData = async () => {
+      const fetchProvider = useFetchProvider();
+      const providerList = await fetchProvider(productTypeValue);
+      setProviders(providerList);
+    };
+
+    fetchData();
+  }, [productTypeValue]);
 
   const onSubmit: SubmitHandler<IFormInput> = data => {
     if (!isNotebookCreated()) {
@@ -168,6 +149,9 @@ export const FormComponent: FC<IProps> = ({
     commands.execute('settingeditor:open', { query: 'EODAG' });
   };
 
+  const loadProductTypesSuggestions = useFetchProduct();
+  const loadProviderSuggestions = useFetchProvider();
+
   return (
     <div className="jp-EodagWidget-wrapper">
       <form onSubmit={handleSubmit(onSubmit)} className="jp-EodagWidget-form">
@@ -182,17 +166,41 @@ export const FormComponent: FC<IProps> = ({
           />
         </div>
         <div className="jp-EodagWidget-field">
+          <Controller
+            name="provider"
+            control={control}
+            render={({ field: { onChange, value } }) => (
+              <Autocomplete
+                label="Provider"
+                placeholder="Any"
+                suggestions={providers}
+                value={value}
+                loadSuggestions={(inputValue: string) =>
+                  loadProviderSuggestions(null, inputValue)
+                }
+                handleChange={(e: IOptionTypeBase | null) => {
+                  onChange(e?.value);
+                  setProviderValue(e?.value);
+                }}
+              />
+            )}
+          />
           <Controller
             name="productType"
             control={control}
             rules={{ required: true }}
             render={({ field: { onChange, value } }) => (
               <Autocomplete
+                label="Product Type"
                 suggestions={productTypes}
+                placeholder="S2_..."
                 value={value}
+                loadSuggestions={(inputValue: string) =>
+                  loadProductTypesSuggestions(providerValue, inputValue)
+                }
                 handleChange={(e: IOptionTypeBase | null) => {
                   onChange(e?.value);
-                  setSelectValue(e?.value);
+                  setProductTypeValue(e?.value);
                 }}
               />
             )}
@@ -312,7 +320,7 @@ export const FormComponent: FC<IProps> = ({
                     type="submit"
                     color="primary"
                     className={
-                      !selectValue
+                      !productTypeValue
                         ? 'jp-EodagWidget-buttons-button jp-EodagWidget-buttons-button__disabled'
                         : 'jp-EodagWidget-buttons-button'
                     }
@@ -327,7 +335,7 @@ export const FormComponent: FC<IProps> = ({
                       <br />
                       Results
                     </p>
-                    {!selectValue && (
+                    {!productTypeValue && (
                       <ReactTooltip
                         id="btn-preview-results"
                         className="jp-Eodag-tooltip"
@@ -343,7 +351,7 @@ export const FormComponent: FC<IProps> = ({
                     type="submit"
                     color="primary"
                     className={
-                      !selectValue
+                      !productTypeValue
                         ? 'jp-EodagWidget-buttons-button jp-EodagWidget-buttons-button__disabled'
                         : 'jp-EodagWidget-buttons-button'
                     }
@@ -358,7 +366,7 @@ export const FormComponent: FC<IProps> = ({
                       <br />
                       Code
                     </p>
-                    {!selectValue && (
+                    {!productTypeValue && (
                       <ReactTooltip
                         id="btn-generate-value"
                         className="jp-Eodag-tooltip"
diff --git a/src/SearchService.ts b/src/SearchService.ts
index 91b3cae..59c4efa 100644
--- a/src/SearchService.ts
+++ b/src/SearchService.ts
@@ -41,7 +41,8 @@ class SearchService {
       dtend: formValues.endDate ? formatDate(formValues.endDate) : undefined,
       cloudCover: formValues.cloud < 100 ? formValues.cloud : undefined,
       page: page,
-      geom: formValues.geometry
+      geom: formValues.geometry,
+      provider: formValues.provider
     };
 
     if (formValues.additionnalParameters) {
diff --git a/src/hooks/useFetchData.ts b/src/hooks/useFetchData.ts
new file mode 100644
index 0000000..45ac789
--- /dev/null
+++ b/src/hooks/useFetchData.ts
@@ -0,0 +1,100 @@
+import { showErrorMessage } from '@jupyterlab/apputils';
+import { URLExt } from '@jupyterlab/coreutils';
+import { ServerConnection } from '@jupyterlab/services';
+import { EODAG_SERVER_ADRESS } from './../config';
+import { IOptionTypeBase, IProduct, IProvider } from './../FormComponent';
+
+interface IFetchDataProps<T> {
+  queryParams: string;
+  onSuccess: (data: T[]) => Promise<IOptionTypeBase[]>;
+}
+
+const fetchData = async <T>({
+  queryParams,
+  onSuccess
+}: IFetchDataProps<T>): Promise<IOptionTypeBase[]> => {
+  const serverSettings = ServerConnection.makeSettings();
+  const eodagServer = URLExt.join(
+    serverSettings.baseUrl,
+    `${EODAG_SERVER_ADRESS}`
+  );
+
+  try {
+    const response = await fetch(URLExt.join(eodagServer, queryParams), {
+      credentials: 'same-origin'
+    });
+    if (response.status >= 400) {
+      throw new Error('Bad response from server');
+    }
+    const data = await response.json();
+    return onSuccess(data);
+  } catch (error) {
+    showErrorMessage(
+      `Unable to contact the EODAG server. Are you sure the address is ${eodagServer}/ ?`,
+      {}
+    );
+    return Promise.reject(error);
+  }
+};
+
+const useFetchProduct = () => {
+  const fetchProduct = async (
+    providerValue: string,
+    inputValue?: string
+  ): Promise<IOptionTypeBase[]> => {
+    let queryParams = 'guess-product-type?';
+
+    if (inputValue) {
+      queryParams += `keywords=${inputValue}`;
+    }
+
+    if (providerValue) {
+      queryParams += queryParams.includes('keywords') ? '&' : '';
+      queryParams += `provider=${providerValue}`;
+    }
+    return fetchData<IProduct>({
+      queryParams,
+      onSuccess: data =>
+        Promise.resolve(
+          data.map((d: IProduct) => ({
+            value: d.ID,
+            label: d.ID,
+            description: d.title
+          }))
+        )
+    });
+  };
+
+  return fetchProduct;
+};
+
+const useFetchProvider = () => {
+  const fetchProvider = async (
+    productTypeValue: string,
+    inputValue?: string
+  ): Promise<IOptionTypeBase[]> => {
+    let queryParams = 'providers?';
+
+    if (inputValue) {
+      queryParams += `keywords=${inputValue}`;
+    }
+    if (productTypeValue) {
+      queryParams += `product_type=${productTypeValue}`;
+    }
+    return fetchData<IProvider>({
+      queryParams,
+      onSuccess: data =>
+        Promise.resolve(
+          data.map((d: IProvider) => ({
+            value: d.provider,
+            label: d.provider,
+            description: d.description
+          }))
+        )
+    });
+  };
+
+  return fetchProvider;
+};
+
+export { fetchData, useFetchProduct, useFetchProvider };
diff --git a/src/types.ts b/src/types.ts
index b9a715e..cadc6d4 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -8,6 +8,7 @@ export interface IFormInput {
   startDate: Date;
   endDate: Date;
   productType: string;
+  provider: string;
   cloud: number;
   additionnalParameters?: { name: string; value: string }[];
 }
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..d5c232e
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Copyright 2024 CS GROUP - France, http://www.c-s.fr
+# All rights reserved
+"""EODAG-labextension tests"""
diff --git a/tests/test_handlers.py b/tests/test_handlers.py
new file mode 100644
index 0000000..3b25bd7
--- /dev/null
+++ b/tests/test_handlers.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+# Copyright 2024 CS GROUP - France, http://www.c-s.fr
+# All rights reserved
+import json
+import os
+import re
+from unittest import mock
+
+from notebook.notebookapp import NotebookApp
+from tornado.testing import AsyncHTTPTestCase
+from tornado.web import authenticated
+
+from eodag_labextension import load_jupyter_server_extension
+from eodag_labextension.handlers import APIHandler
+
+
+class MockUser:
+    name = "test"
+
+
+class TestEodagLabExtensionHandler(AsyncHTTPTestCase):
+    @classmethod
+    def setUpClass(cls):
+        # backup os.environ as it will be modified by tests
+        cls.eodag_env_pattern = re.compile(r"EODAG_\w+")
+        cls.eodag_env_backup = {k: v for k, v in os.environ.items() if cls.eodag_env_pattern.match(k)}
+        # disable product types fetch
+        os.environ["EODAG_EXT_PRODUCT_TYPES_CFG_FILE"] = ""
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestEodagLabExtensionHandler, cls).tearDownClass()
+        # restore os.environ
+        for k, v in os.environ.items():
+            if cls.eodag_env_pattern.match(k):
+                os.environ.pop(k)
+        os.environ.update(cls.eodag_env_backup)
+
+    def get_app(self):
+        # Create a new NotebookApp instance
+        app = NotebookApp()
+        app.initialize(argv=[])
+
+        # Load extension
+        load_jupyter_server_extension(app)
+
+        return app.web_app
+
+    @mock.patch.object(APIHandler, "get_current_user", return_value=MockUser())
+    @mock.patch.object(authenticated, "__call__", return_value=lambda x: x)
+    def fetch_results(self, url, mock_auth, mock_user):
+        """Check that request status is 200 and return the json result as dict"""
+        response = self.fetch(url)
+        self.assertEqual(response.code, 200)
+        return json.loads(response.body.decode("utf-8"))
+
+    @mock.patch.object(APIHandler, "get_current_user", return_value=MockUser())
+    @mock.patch.object(authenticated, "__call__", return_value=lambda x: x)
+    def fetch_results_err400(self, url, mock_auth, mock_user):
+        """Check that request returns a 400 error"""
+        response = self.fetch(url)
+        self.assertEqual(response.code, 400)
+        return json.loads(response.body.decode("utf-8"))
+
+    def test_product_types(self):
+        # all product types
+        results = self.fetch_results("/eodag/product-types")
+        self.assertIn("S2_MSI_L1C", [pt["ID"] for pt in results])
+
+        # single provider product types
+        less_results = self.fetch_results("/eodag/product-types?provider=peps")
+        self.assertGreater(len(less_results), 0)
+        self.assertLess(len(less_results), len(results))
+
+        # unknown provider
+        self.fetch_results_err400("/eodag/product-types?provider=foo")
+
+    def test_providers(self):
+        # all providers
+        results = self.fetch_results("/eodag/providers")
+        self.assertIn("peps", [res["provider"] for res in results])
+
+        less_results = self.fetch_results("/eodag/providers?product_type=S2_MSI_L1C")
+        self.assertGreater(len(less_results), 0)
+        self.assertLess(len(less_results), len(results))
+
+        result_with_name = self.fetch_results("/eodag/providers?keywords=peps")
+        self.assertEqual(len(result_with_name), 1)
+        self.assertEqual(result_with_name[0]["provider"], "peps")
+
+        result_with_description = self.fetch_results("/eodag/providers?keywords=cop")
+        self.assertGreater(len(result_with_description), 2)
+        self.assertEqual(result_with_description[0]["provider"], "cop_ads")
+
+        no_result = self.fetch_results("/eodag/providers?product_type=foo")
+        self.assertEqual(len(no_result), 0)
+
+    def test_guess_product_types(self):
+        all_results = self.fetch_results("/eodag/guess-product-type")
+        self.assertIn("S2_MSI_L1C", [pt["ID"] for pt in all_results])
+
+        one_provider_results = self.fetch_results("/eodag/guess-product-type?provider=creodias")
+        self.assertLess(len(one_provider_results), len(all_results))
+        self.assertIn("COP_DEM_GLO90_DGED", [pt["ID"] for pt in all_results])
+
+        one_result = self.fetch_results("/eodag/guess-product-type?keywords=S2_MSI_L1C")
+        self.assertEqual(len(one_result), 1)
+        self.assertEqual(one_result[0]["ID"], "S2_MSI_L1C")
+
+        another_result = self.fetch_results("/eodag/guess-product-type?keywords=Sentinel2%20L1C")
+        self.assertEqual(len(another_result), 1)
+        self.assertEqual(another_result[0]["ID"], "S2_MSI_L1C")
+
+        more_results = self.fetch_results("/eodag/guess-product-type?keywords=Sentinel")
+        self.assertGreater(len(more_results), 1)
+        self.assertLess(len(more_results), len(all_results))
+        self.assertIn("S2_MSI_L1C", [pt["ID"] for pt in more_results])
+
+        less_results = self.fetch_results("/eodag/guess-product-type?keywords=Sentinel&provider=peps")
+        self.assertGreater(len(more_results), 1)
+        self.assertLess(len(less_results), len(more_results))
+        self.assertEqual(less_results[0]["ID"], "S1_SAR_GRD")
+
+        other_results = self.fetch_results("/eodag/guess-product-type?keywords=cop")
+        self.assertGreater(len(other_results), 1)
+        self.assertLess(len(other_results), len(all_results))
+        self.assertTrue(other_results[0]["ID"].lower().startswith("cop"))
+
+        self.fetch_results_err400("/eodag/guess-product-type?provider=foo")
diff --git a/tsconfig.json b/tsconfig.json
index e46e569..3157439 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -18,7 +18,8 @@
     "strict": true,
     "strictNullChecks": false,
     "target": "es2017",
-    "types": []
+    "types": [],
+    "skipLibCheck": true
   },
-  "include": ["src/*", "style/*"]
+  "include": ["src/**/*", "style/*"]
 }
diff --git a/yarn.lock b/yarn.lock
index a0fbb6e..897e380 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,20 +2,29 @@
 # yarn lockfile v1
 
 
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.18.6":
+"@babel/code-frame@^7.0.0":
   version "7.18.6"
   resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a"
   integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==
   dependencies:
     "@babel/highlight" "^7.18.6"
 
-"@babel/generator@^7.20.5":
-  version "7.20.5"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.5.tgz#cb25abee3178adf58d6814b68517c62bdbfdda95"
-  integrity sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==
+"@babel/code-frame@^7.22.13":
+  version "7.22.13"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
+  integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
   dependencies:
-    "@babel/types" "^7.20.5"
+    "@babel/highlight" "^7.22.13"
+    chalk "^2.4.2"
+
+"@babel/generator@^7.23.0":
+  version "7.23.0"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
+  integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
+  dependencies:
+    "@babel/types" "^7.23.0"
     "@jridgewell/gen-mapping" "^0.3.2"
+    "@jridgewell/trace-mapping" "^0.3.17"
     jsesc "^2.5.1"
 
 "@babel/helper-annotate-as-pure@^7.16.0":
@@ -25,25 +34,25 @@
   dependencies:
     "@babel/types" "^7.18.6"
 
-"@babel/helper-environment-visitor@^7.18.9":
-  version "7.18.9"
-  resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
-  integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
+"@babel/helper-environment-visitor@^7.22.20":
+  version "7.22.20"
+  resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
+  integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
 
-"@babel/helper-function-name@^7.19.0":
-  version "7.19.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c"
-  integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==
+"@babel/helper-function-name@^7.23.0":
+  version "7.23.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
+  integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
   dependencies:
-    "@babel/template" "^7.18.10"
-    "@babel/types" "^7.19.0"
+    "@babel/template" "^7.22.15"
+    "@babel/types" "^7.23.0"
 
-"@babel/helper-hoist-variables@^7.18.6":
-  version "7.18.6"
-  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678"
-  integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==
+"@babel/helper-hoist-variables@^7.22.5":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
+  integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
   dependencies:
-    "@babel/types" "^7.18.6"
+    "@babel/types" "^7.22.5"
 
 "@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.0", "@babel/helper-module-imports@^7.16.7":
   version "7.18.6"
@@ -57,28 +66,33 @@
   resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf"
   integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==
 
-"@babel/helper-split-export-declaration@^7.18.6":
-  version "7.18.6"
-  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075"
-  integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==
+"@babel/helper-split-export-declaration@^7.22.6":
+  version "7.22.6"
+  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
+  integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
   dependencies:
-    "@babel/types" "^7.18.6"
+    "@babel/types" "^7.22.5"
 
 "@babel/helper-string-parser@^7.18.10":
   version "7.18.10"
   resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56"
   integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==
 
-"@babel/helper-string-parser@^7.19.4":
-  version "7.19.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63"
-  integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==
+"@babel/helper-string-parser@^7.22.5":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
+  integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
 
-"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1":
+"@babel/helper-validator-identifier@^7.18.6":
   version "7.19.1"
   resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
   integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==
 
+"@babel/helper-validator-identifier@^7.22.20":
+  version "7.22.20"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
+  integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
+
 "@babel/highlight@^7.18.6":
   version "7.18.6"
   resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
@@ -88,10 +102,19 @@
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.18.10", "@babel/parser@^7.20.5":
-  version "7.20.5"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8"
-  integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==
+"@babel/highlight@^7.22.13":
+  version "7.22.20"
+  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54"
+  integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
+  dependencies:
+    "@babel/helper-validator-identifier" "^7.22.20"
+    chalk "^2.4.2"
+    js-tokens "^4.0.0"
+
+"@babel/parser@^7.22.15", "@babel/parser@^7.23.0":
+  version "7.23.0"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
+  integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
 
 "@babel/plugin-syntax-jsx@^7.17.12":
   version "7.18.6"
@@ -107,40 +130,31 @@
   dependencies:
     regenerator-runtime "^0.13.4"
 
-"@babel/template@^7.18.10":
-  version "7.18.10"
-  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71"
-  integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==
+"@babel/template@^7.22.15":
+  version "7.22.15"
+  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
+  integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
   dependencies:
-    "@babel/code-frame" "^7.18.6"
-    "@babel/parser" "^7.18.10"
-    "@babel/types" "^7.18.10"
+    "@babel/code-frame" "^7.22.13"
+    "@babel/parser" "^7.22.15"
+    "@babel/types" "^7.22.15"
 
 "@babel/traverse@^7.4.5":
-  version "7.20.5"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.5.tgz#78eb244bea8270fdda1ef9af22a5d5e5b7e57133"
-  integrity sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==
-  dependencies:
-    "@babel/code-frame" "^7.18.6"
-    "@babel/generator" "^7.20.5"
-    "@babel/helper-environment-visitor" "^7.18.9"
-    "@babel/helper-function-name" "^7.19.0"
-    "@babel/helper-hoist-variables" "^7.18.6"
-    "@babel/helper-split-export-declaration" "^7.18.6"
-    "@babel/parser" "^7.20.5"
-    "@babel/types" "^7.20.5"
+  version "7.23.2"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
+  integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
+  dependencies:
+    "@babel/code-frame" "^7.22.13"
+    "@babel/generator" "^7.23.0"
+    "@babel/helper-environment-visitor" "^7.22.20"
+    "@babel/helper-function-name" "^7.23.0"
+    "@babel/helper-hoist-variables" "^7.22.5"
+    "@babel/helper-split-export-declaration" "^7.22.6"
+    "@babel/parser" "^7.23.0"
+    "@babel/types" "^7.23.0"
     debug "^4.1.0"
     globals "^11.1.0"
 
-"@babel/types@^7.18.10", "@babel/types@^7.19.0", "@babel/types@^7.20.5":
-  version "7.20.5"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.5.tgz#e206ae370b5393d94dfd1d04cd687cace53efa84"
-  integrity sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==
-  dependencies:
-    "@babel/helper-string-parser" "^7.19.4"
-    "@babel/helper-validator-identifier" "^7.19.1"
-    to-fast-properties "^2.0.0"
-
 "@babel/types@^7.18.6":
   version "7.19.0"
   resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.0.tgz#75f21d73d73dc0351f3368d28db73465f4814600"
@@ -150,6 +164,15 @@
     "@babel/helper-validator-identifier" "^7.18.6"
     to-fast-properties "^2.0.0"
 
+"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0":
+  version "7.23.0"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
+  integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
+  dependencies:
+    "@babel/helper-string-parser" "^7.22.5"
+    "@babel/helper-validator-identifier" "^7.22.20"
+    to-fast-properties "^2.0.0"
+
 "@blueprintjs/colors@^4.0.0-alpha.3":
   version "4.1.6"
   resolved "https://registry.yarnpkg.com/@blueprintjs/colors/-/colors-4.1.6.tgz#a2e3d02b40867b3770187c69a8514f113ab4dd85"
@@ -398,6 +421,11 @@
   resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
   integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
 
+"@jridgewell/resolve-uri@^3.1.0":
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
+  integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
+
 "@jridgewell/set-array@^1.0.1":
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
@@ -416,6 +444,11 @@
   resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
   integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
 
+"@jridgewell/sourcemap-codec@^1.4.14":
+  version "1.4.15"
+  resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
+  integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
+
 "@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.9":
   version "0.3.15"
   resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774"
@@ -424,6 +457,14 @@
     "@jridgewell/resolve-uri" "^3.0.3"
     "@jridgewell/sourcemap-codec" "^1.4.10"
 
+"@jridgewell/trace-mapping@^0.3.17":
+  version "0.3.20"
+  resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f"
+  integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==
+  dependencies:
+    "@jridgewell/resolve-uri" "^3.1.0"
+    "@jridgewell/sourcemap-codec" "^1.4.14"
+
 "@juggle/resize-observer@^3.3.1":
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60"
@@ -2381,7 +2422,7 @@ caseless@~0.12.0:
   resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
   integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==
 
-chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1:
+chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
   integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==