Merge branch 'feature/ui_tests' into dev
This commit is contained in:
@@ -22,5 +22,22 @@ dependencies = [
|
|||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
|
"pytest",
|
||||||
|
"pytest-env",
|
||||||
"pre-commit",
|
"pre-commit",
|
||||||
|
"selenium",
|
||||||
|
"webdriver-manager",
|
||||||
|
"dash[testing]",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
pythonpath = [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
testpaths = [
|
||||||
|
"tests"
|
||||||
|
]
|
||||||
|
env = [
|
||||||
|
"DATA_FILE_PARQUET_PATH=tests/test.parquet"
|
||||||
|
]
|
||||||
|
addopts = "-p no:warnings"
|
||||||
|
|||||||
+5
-1
@@ -9,6 +9,10 @@ from flask import Response
|
|||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
# if os.getenv("PYTEST_CURRENT_TEST"):
|
||||||
|
# os.environ["DATA_FILE_PARQUET_PATH"]
|
||||||
|
|
||||||
|
|
||||||
development = os.getenv("DEVELOPMENT").lower() == "true"
|
development = os.getenv("DEVELOPMENT").lower() == "true"
|
||||||
|
|
||||||
meta_tags = [
|
meta_tags = [
|
||||||
@@ -22,7 +26,7 @@ meta_tags = [
|
|||||||
if development:
|
if development:
|
||||||
meta_tags.append({"name": "robots", "content": "noindex"})
|
meta_tags.append({"name": "robots", "content": "noindex"})
|
||||||
|
|
||||||
app = Dash(
|
app: Dash = Dash(
|
||||||
title="decp.info",
|
title="decp.info",
|
||||||
use_pages=True,
|
use_pages=True,
|
||||||
compress=True,
|
compress=True,
|
||||||
|
|||||||
@@ -330,7 +330,6 @@ class DataTable(dash_table.DataTable):
|
|||||||
},
|
},
|
||||||
sort_action=sort_action,
|
sort_action=sort_action,
|
||||||
sort_mode="multi",
|
sort_mode="multi",
|
||||||
sort_by=[],
|
|
||||||
row_deletable=False,
|
row_deletable=False,
|
||||||
page_current=0,
|
page_current=0,
|
||||||
style_cell_conditional=style_cell_conditional,
|
style_cell_conditional=style_cell_conditional,
|
||||||
|
|||||||
+43
-56
@@ -1,5 +1,4 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import uuid
|
|
||||||
|
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
import polars as pl
|
import polars as pl
|
||||||
@@ -12,7 +11,6 @@ from dash import (
|
|||||||
clientside_callback,
|
clientside_callback,
|
||||||
dcc,
|
dcc,
|
||||||
html,
|
html,
|
||||||
no_update,
|
|
||||||
register_page,
|
register_page,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -58,6 +56,9 @@ datatable = html.Div(
|
|||||||
className="marches_table",
|
className="marches_table",
|
||||||
children=DataTable(
|
children=DataTable(
|
||||||
dtid="acheteur_datatable",
|
dtid="acheteur_datatable",
|
||||||
|
persistence=True,
|
||||||
|
persistence_type="local",
|
||||||
|
persisted_props=["filter_query", "sort_by"],
|
||||||
page_action="custom",
|
page_action="custom",
|
||||||
filter_action="custom",
|
filter_action="custom",
|
||||||
sort_action="custom",
|
sort_action="custom",
|
||||||
@@ -70,8 +71,6 @@ datatable = html.Div(
|
|||||||
layout = [
|
layout = [
|
||||||
dcc.Store(id="acheteur_data", storage_type="memory"),
|
dcc.Store(id="acheteur_data", storage_type="memory"),
|
||||||
dcc.Store(id="acheteur-hidden-columns", storage_type="local"),
|
dcc.Store(id="acheteur-hidden-columns", storage_type="local"),
|
||||||
dcc.Store(id="acheteur-filters", storage_type="local"),
|
|
||||||
dcc.Store(id="acheteur-sort", storage_type="local"),
|
|
||||||
dcc.Store(id="filter-cleanup-trigger-acheteur"),
|
dcc.Store(id="filter-cleanup-trigger-acheteur"),
|
||||||
dcc.Location(id="acheteur_url", refresh="callback-nav"),
|
dcc.Location(id="acheteur_url", refresh="callback-nav"),
|
||||||
html.Div(
|
html.Div(
|
||||||
@@ -218,26 +217,40 @@ layout = [
|
|||||||
)
|
)
|
||||||
def update_acheteur_infos(url):
|
def update_acheteur_infos(url):
|
||||||
acheteur_siret = url.split("/")[-1]
|
acheteur_siret = url.split("/")[-1]
|
||||||
if len(acheteur_siret) != 14:
|
# if len(acheteur_siret) != 14:
|
||||||
acheteur_siret = (
|
# acheteur_siret = (
|
||||||
f"Le SIRET renseigné doit faire 14 caractères ({acheteur_siret})"
|
# f"Le SIRET renseigné doit faire 14 caractères ({acheteur_siret})"
|
||||||
)
|
# )
|
||||||
data = get_annuaire_data(acheteur_siret)
|
data = get_annuaire_data(acheteur_siret)
|
||||||
data_etablissement = data["matching_etablissements"][0]
|
data_etablissement = data.get("matching_etablissements") if data else None
|
||||||
acheteur_map = point_on_map(
|
if data_etablissement:
|
||||||
data_etablissement["latitude"], data_etablissement["longitude"]
|
data_etablissement = data_etablissement[0]
|
||||||
)
|
|
||||||
code_departement, nom_departement, nom_region = get_departement_region(
|
acheteur_map = point_on_map(
|
||||||
data_etablissement["code_postal"]
|
data_etablissement["latitude"], data_etablissement["longitude"]
|
||||||
)
|
)
|
||||||
departement = f"{nom_departement} ({code_departement})"
|
code_departement, nom_departement, nom_region = get_departement_region(
|
||||||
lien_annuaire = (
|
data_etablissement["code_postal"]
|
||||||
f"https://annuaire-entreprises.data.gouv.fr/etablissement/{acheteur_siret}"
|
)
|
||||||
)
|
departement = f"{nom_departement} ({code_departement})"
|
||||||
|
lien_annuaire = (
|
||||||
|
f"https://annuaire-entreprises.data.gouv.fr/etablissement/{acheteur_siret}"
|
||||||
|
)
|
||||||
|
raison_sociale = data["nom_raison_sociale"]
|
||||||
|
libelle_commune = data_etablissement["libelle_commune"]
|
||||||
|
|
||||||
|
else:
|
||||||
|
acheteur_map = html.Div()
|
||||||
|
code_departement, nom_departement, nom_region = "", "", ""
|
||||||
|
departement = ""
|
||||||
|
lien_annuaire = ""
|
||||||
|
raison_sociale = ""
|
||||||
|
libelle_commune = ""
|
||||||
|
|
||||||
return (
|
return (
|
||||||
acheteur_siret,
|
acheteur_siret,
|
||||||
data["nom_raison_sociale"],
|
raison_sociale,
|
||||||
data_etablissement["libelle_commune"],
|
libelle_commune,
|
||||||
acheteur_map,
|
acheteur_map,
|
||||||
departement,
|
departement,
|
||||||
nom_region,
|
nom_region,
|
||||||
@@ -306,16 +319,17 @@ def get_acheteur_marches_data(url, acheteur_year: str) -> tuple:
|
|||||||
Output("btn-download-filtered-data-acheteur", "children"),
|
Output("btn-download-filtered-data-acheteur", "children"),
|
||||||
Output("btn-download-filtered-data-acheteur", "title"),
|
Output("btn-download-filtered-data-acheteur", "title"),
|
||||||
Output("filter-cleanup-trigger-acheteur", "data", allow_duplicate=True),
|
Output("filter-cleanup-trigger-acheteur", "data", allow_duplicate=True),
|
||||||
|
Input("acheteur_url", "href"),
|
||||||
Input("acheteur_data", "data"),
|
Input("acheteur_data", "data"),
|
||||||
Input("acheteur_datatable", "page_current"),
|
Input("acheteur_datatable", "page_current"),
|
||||||
Input("acheteur_datatable", "page_size"),
|
Input("acheteur_datatable", "page_size"),
|
||||||
Input("acheteur-filters", "data"),
|
Input("acheteur_datatable", "filter_query"),
|
||||||
Input("acheteur-sort", "data"),
|
Input("acheteur_datatable", "sort_by"),
|
||||||
State("acheteur_datatable", "data_timestamp"),
|
State("acheteur_datatable", "data_timestamp"),
|
||||||
prevent_initial_call=True,
|
prevent_initial_call=True,
|
||||||
)
|
)
|
||||||
def get_last_marches_data(
|
def get_last_marches_data(
|
||||||
data, page_current, page_size, filter_query, sort_by, data_timestamp
|
href, data, page_current, page_size, filter_query, sort_by, data_timestamp
|
||||||
) -> tuple:
|
) -> tuple:
|
||||||
return prepare_table_data(
|
return prepare_table_data(
|
||||||
data, data_timestamp, filter_query, page_current, page_size, sort_by, "acheteur"
|
data, data_timestamp, filter_query, page_current, page_size, sort_by, "acheteur"
|
||||||
@@ -360,8 +374,8 @@ def download_acheteur_data(
|
|||||||
State("acheteur_data", "data"),
|
State("acheteur_data", "data"),
|
||||||
Input("btn-download-filtered-data-acheteur", "n_clicks"),
|
Input("btn-download-filtered-data-acheteur", "n_clicks"),
|
||||||
State("acheteur_nom", "children"),
|
State("acheteur_nom", "children"),
|
||||||
State("acheteur-filters", "data"),
|
State("acheteur_datatable", "filter_query"),
|
||||||
State("acheteur-sort", "data"),
|
State("acheteur_datatable", "sort_by"),
|
||||||
State("acheteur_datatable", "hidden_columns"),
|
State("acheteur_datatable", "hidden_columns"),
|
||||||
prevent_initial_call=True,
|
prevent_initial_call=True,
|
||||||
)
|
)
|
||||||
@@ -406,19 +420,16 @@ clientside_callback(
|
|||||||
|
|
||||||
@callback(
|
@callback(
|
||||||
Output("acheteur-hidden-columns", "data", allow_duplicate=True),
|
Output("acheteur-hidden-columns", "data", allow_duplicate=True),
|
||||||
Output("filter-cleanup-trigger-acheteur", "data", allow_duplicate=True),
|
|
||||||
Input("acheteur_column_list", "selected_rows"),
|
Input("acheteur_column_list", "selected_rows"),
|
||||||
State("acheteur-filters", "data"),
|
|
||||||
prevent_initial_call=True,
|
prevent_initial_call=True,
|
||||||
)
|
)
|
||||||
def update_hidden_columns_from_checkboxes(selected_columns, filter_query):
|
def update_hidden_columns_from_checkboxes(selected_columns):
|
||||||
trigger_cleanup = str(uuid.uuid4()) if filter_query else no_update
|
|
||||||
if selected_columns:
|
if selected_columns:
|
||||||
selected_columns = [columns[i] for i in selected_columns]
|
selected_columns = [columns[i] for i in selected_columns]
|
||||||
hidden_columns = [col for col in columns if col not in selected_columns]
|
hidden_columns = [col for col in columns if col not in selected_columns]
|
||||||
return hidden_columns, trigger_cleanup
|
return hidden_columns
|
||||||
else:
|
else:
|
||||||
return [], trigger_cleanup
|
return []
|
||||||
|
|
||||||
|
|
||||||
@callback(
|
@callback(
|
||||||
@@ -458,30 +469,6 @@ def toggle_acheteur_columns(click_open, click_close, is_open):
|
|||||||
return is_open
|
return is_open
|
||||||
|
|
||||||
|
|
||||||
@callback(
|
|
||||||
Output("acheteur-filters", "data"), Input("acheteur_datatable", "filter_query")
|
|
||||||
)
|
|
||||||
def sync_filters_to_local_storage(filter_query):
|
|
||||||
return filter_query
|
|
||||||
|
|
||||||
|
|
||||||
@callback(Output("acheteur-sort", "data"), Input("acheteur_datatable", "sort_by"))
|
|
||||||
def sync_sort_to_local_storage(sort_by):
|
|
||||||
return sort_by
|
|
||||||
|
|
||||||
|
|
||||||
@callback(
|
|
||||||
Output("acheteur_datatable", "filter_query", allow_duplicate=True),
|
|
||||||
Output("acheteur_datatable", "sort_by", allow_duplicate=True),
|
|
||||||
Input("acheteur_url", "href"),
|
|
||||||
State("acheteur-filters", "data"),
|
|
||||||
State("acheteur-sort", "data"),
|
|
||||||
prevent_initial_call=True,
|
|
||||||
)
|
|
||||||
def sync_local_storage_to_datatable(href, filter_query, sort_by):
|
|
||||||
return filter_query, sort_by
|
|
||||||
|
|
||||||
|
|
||||||
@callback(
|
@callback(
|
||||||
Output("acheteur_datatable", "filter_query", allow_duplicate=True),
|
Output("acheteur_datatable", "filter_query", allow_duplicate=True),
|
||||||
Output("acheteur_datatable", "sort_by"),
|
Output("acheteur_datatable", "sort_by"),
|
||||||
|
|||||||
+9
-17
@@ -55,6 +55,9 @@ datatable = html.Div(
|
|||||||
className="marches_table",
|
className="marches_table",
|
||||||
children=DataTable(
|
children=DataTable(
|
||||||
dtid="tableau_datatable",
|
dtid="tableau_datatable",
|
||||||
|
persisted_props=["filter_query", "sort_by"],
|
||||||
|
persistence_type="local",
|
||||||
|
persistence=True,
|
||||||
page_size=20,
|
page_size=20,
|
||||||
page_action="custom",
|
page_action="custom",
|
||||||
filter_action="custom",
|
filter_action="custom",
|
||||||
@@ -68,8 +71,6 @@ layout = [
|
|||||||
dcc.Location(id="tableau_url", refresh=False),
|
dcc.Location(id="tableau_url", refresh=False),
|
||||||
dcc.Store(id="filter-cleanup-trigger-tableau"),
|
dcc.Store(id="filter-cleanup-trigger-tableau"),
|
||||||
dcc.Store(id="tableau-hidden-columns", storage_type="local"),
|
dcc.Store(id="tableau-hidden-columns", storage_type="local"),
|
||||||
dcc.Store(id="tableau-filters", storage_type="local"),
|
|
||||||
dcc.Store(id="tableau-sort", storage_type="local"),
|
|
||||||
dcc.Store(id="tableau-table"),
|
dcc.Store(id="tableau-table"),
|
||||||
html.Script(
|
html.Script(
|
||||||
type="application/ld+json",
|
type="application/ld+json",
|
||||||
@@ -285,14 +286,15 @@ layout = [
|
|||||||
Output("btn-download-data", "children"),
|
Output("btn-download-data", "children"),
|
||||||
Output("btn-download-data", "title"),
|
Output("btn-download-data", "title"),
|
||||||
Output("filter-cleanup-trigger-tableau", "data", allow_duplicate=True),
|
Output("filter-cleanup-trigger-tableau", "data", allow_duplicate=True),
|
||||||
|
Input("tableau_url", "href"),
|
||||||
Input("tableau_datatable", "page_current"),
|
Input("tableau_datatable", "page_current"),
|
||||||
Input("tableau_datatable", "page_size"),
|
Input("tableau_datatable", "page_size"),
|
||||||
Input("tableau-filters", "data"),
|
Input("tableau_datatable", "filter_query"),
|
||||||
Input("tableau-sort", "data"),
|
Input("tableau_datatable", "sort_by"),
|
||||||
State("tableau_datatable", "data_timestamp"),
|
State("tableau_datatable", "data_timestamp"),
|
||||||
prevent_initial_call=True,
|
prevent_initial_call=True,
|
||||||
)
|
)
|
||||||
def update_table(page_current, page_size, filter_query, sort_by, data_timestamp):
|
def update_table(href, page_current, page_size, filter_query, sort_by, data_timestamp):
|
||||||
# if ctx.triggered_id != "url":
|
# if ctx.triggered_id != "url":
|
||||||
# search_params = None
|
# search_params = None
|
||||||
# else:
|
# else:
|
||||||
@@ -337,8 +339,8 @@ def download_data(n_clicks, filter_query, sort_by, hidden_columns: list = None):
|
|||||||
Output("tableau_url", "search"),
|
Output("tableau_url", "search"),
|
||||||
Output("filter-cleanup-trigger-tableau", "data"),
|
Output("filter-cleanup-trigger-tableau", "data"),
|
||||||
Input("tableau_url", "search"),
|
Input("tableau_url", "search"),
|
||||||
State("tableau-filters", "data"),
|
State("tableau_datatable", "filter_query"),
|
||||||
State("tableau-sort", "data"),
|
State("tableau_datatable", "sort_by"),
|
||||||
)
|
)
|
||||||
def restore_view_from_url(search, stored_filters, stored_sort):
|
def restore_view_from_url(search, stored_filters, stored_sort):
|
||||||
if not search and not stored_filters:
|
if not search and not stored_filters:
|
||||||
@@ -512,16 +514,6 @@ def toggle_tableau_columns(click_open, click_close, is_open):
|
|||||||
return is_open
|
return is_open
|
||||||
|
|
||||||
|
|
||||||
@callback(Output("tableau-filters", "data"), Input("tableau_datatable", "filter_query"))
|
|
||||||
def sync_filters_to_local_storage(filter_query):
|
|
||||||
return filter_query
|
|
||||||
|
|
||||||
|
|
||||||
@callback(Output("tableau-sort", "data"), Input("tableau_datatable", "sort_by"))
|
|
||||||
def sync_sort_to_local_storage(sort_by):
|
|
||||||
return sort_by
|
|
||||||
|
|
||||||
|
|
||||||
@callback(
|
@callback(
|
||||||
Output("tableau_datatable", "filter_query", allow_duplicate=True),
|
Output("tableau_datatable", "filter_query", allow_duplicate=True),
|
||||||
Output("tableau_datatable", "sort_by", allow_duplicate=True),
|
Output("tableau_datatable", "sort_by", allow_duplicate=True),
|
||||||
|
|||||||
+38
-48
@@ -56,6 +56,9 @@ datatable = html.Div(
|
|||||||
className="marches_table",
|
className="marches_table",
|
||||||
children=DataTable(
|
children=DataTable(
|
||||||
dtid="titulaire_datatable",
|
dtid="titulaire_datatable",
|
||||||
|
persistence=True,
|
||||||
|
persistence_type="local",
|
||||||
|
persisted_props=["filter_query", "sort_by"],
|
||||||
page_action="custom",
|
page_action="custom",
|
||||||
filter_action="custom",
|
filter_action="custom",
|
||||||
sort_action="custom",
|
sort_action="custom",
|
||||||
@@ -68,8 +71,6 @@ datatable = html.Div(
|
|||||||
layout = [
|
layout = [
|
||||||
dcc.Store(id="titulaire_data", storage_type="memory"),
|
dcc.Store(id="titulaire_data", storage_type="memory"),
|
||||||
dcc.Store(id="titulaire-hidden-columns", storage_type="local"),
|
dcc.Store(id="titulaire-hidden-columns", storage_type="local"),
|
||||||
dcc.Store(id="titulaire-filters", storage_type="local"),
|
|
||||||
dcc.Store(id="titulaire-sort", storage_type="local"),
|
|
||||||
dcc.Store(id="filter-cleanup-trigger-titulaire"),
|
dcc.Store(id="filter-cleanup-trigger-titulaire"),
|
||||||
dcc.Location(id="titulaire_url", refresh="callback-nav"),
|
dcc.Location(id="titulaire_url", refresh="callback-nav"),
|
||||||
html.Div(
|
html.Div(
|
||||||
@@ -217,26 +218,38 @@ layout = [
|
|||||||
)
|
)
|
||||||
def update_titulaire_infos(url):
|
def update_titulaire_infos(url):
|
||||||
titulaire_siret = url.split("/")[-1]
|
titulaire_siret = url.split("/")[-1]
|
||||||
if len(titulaire_siret) != 14:
|
|
||||||
titulaire_siret = (
|
|
||||||
f"Le SIRET renseigné doit faire 14 caractères ({titulaire_siret})"
|
|
||||||
)
|
|
||||||
data = get_annuaire_data(titulaire_siret)
|
data = get_annuaire_data(titulaire_siret)
|
||||||
data_etablissement = data["matching_etablissements"][0]
|
data_etablissement = data.get("matching_etablissements") if data else None
|
||||||
titulaire_map = point_on_map(
|
if data_etablissement:
|
||||||
data_etablissement["latitude"], data_etablissement["longitude"]
|
data_etablissement = data_etablissement[0]
|
||||||
)
|
|
||||||
code_departement, nom_departement, nom_region = get_departement_region(
|
titulaire_map = point_on_map(
|
||||||
data_etablissement["code_postal"]
|
data_etablissement["latitude"], data_etablissement["longitude"]
|
||||||
)
|
)
|
||||||
departement = f"{nom_departement} ({code_departement})"
|
code_departement, nom_departement, nom_region = get_departement_region(
|
||||||
lien_annuaire = (
|
data_etablissement["code_postal"]
|
||||||
f"https://annuaire-entreprises.data.gouv.fr/etablissement/{titulaire_siret}"
|
)
|
||||||
)
|
departement = f"{nom_departement} ({code_departement})"
|
||||||
|
lien_annuaire = (
|
||||||
|
f"https://annuaire-entreprises.data.gouv.fr/etablissement/{titulaire_siret}"
|
||||||
|
)
|
||||||
|
raison_sociale = data["nom_raison_sociale"]
|
||||||
|
libelle_commune = data_etablissement["libelle_commune"]
|
||||||
|
|
||||||
|
else:
|
||||||
|
titulaire_map = html.Div()
|
||||||
|
code_departement, nom_departement, nom_region = "", "", ""
|
||||||
|
departement = ""
|
||||||
|
lien_annuaire = ""
|
||||||
|
raison_sociale = html.Span(
|
||||||
|
f"N° SIREN inconnu de l'INSEE ({titulaire_siret[:9]})"
|
||||||
|
)
|
||||||
|
libelle_commune = ""
|
||||||
|
|
||||||
return (
|
return (
|
||||||
titulaire_siret,
|
titulaire_siret,
|
||||||
data["nom_raison_sociale"],
|
raison_sociale,
|
||||||
data_etablissement["libelle_commune"],
|
libelle_commune,
|
||||||
titulaire_map,
|
titulaire_map,
|
||||||
departement,
|
departement,
|
||||||
nom_region,
|
nom_region,
|
||||||
@@ -314,16 +327,17 @@ def get_titulaire_marches_data(url, titulaire_year: str) -> tuple:
|
|||||||
Output("btn-download-filtered-data-titulaire", "children"),
|
Output("btn-download-filtered-data-titulaire", "children"),
|
||||||
Output("btn-download-filtered-data-titulaire", "title"),
|
Output("btn-download-filtered-data-titulaire", "title"),
|
||||||
Output("filter-cleanup-trigger-titulaire", "data", allow_duplicate=True),
|
Output("filter-cleanup-trigger-titulaire", "data", allow_duplicate=True),
|
||||||
|
Input(component_id="titulaire_url", component_property="href"),
|
||||||
Input("titulaire_data", "data"),
|
Input("titulaire_data", "data"),
|
||||||
Input("titulaire_datatable", "page_current"),
|
Input("titulaire_datatable", "page_current"),
|
||||||
Input("titulaire_datatable", "page_size"),
|
Input("titulaire_datatable", "page_size"),
|
||||||
Input("titulaire-filters", "data"),
|
Input("titulaire_datatable", "filter_query"),
|
||||||
Input("titulaire-sort", "data"),
|
Input("titulaire_datatable", "sort_by"),
|
||||||
State("titulaire_datatable", "data_timestamp"),
|
State("titulaire_datatable", "data_timestamp"),
|
||||||
config_prevent_initial_callbacks=True,
|
config_prevent_initial_callbacks=True,
|
||||||
)
|
)
|
||||||
def get_last_marches_data(
|
def get_last_marches_data(
|
||||||
data, page_current, page_size, filter_query, sort_by, data_timestamp
|
href, data, page_current, page_size, filter_query, sort_by, data_timestamp
|
||||||
) -> list[dict]:
|
) -> list[dict]:
|
||||||
return prepare_table_data(
|
return prepare_table_data(
|
||||||
data,
|
data,
|
||||||
@@ -374,8 +388,8 @@ def download_titulaire_data(
|
|||||||
State("titulaire_data", "data"),
|
State("titulaire_data", "data"),
|
||||||
Input("btn-download-filtered-data-titulaire", "n_clicks"),
|
Input("btn-download-filtered-data-titulaire", "n_clicks"),
|
||||||
State("titulaire_nom", "children"),
|
State("titulaire_nom", "children"),
|
||||||
State("titulaire-filters", "data"),
|
State("titulaire_datatable", "filter_query"),
|
||||||
State("titulaire-sort", "data"),
|
State("titulaire_datatable", "sort_by"),
|
||||||
State("titulaire_datatable", "hidden_columns"),
|
State("titulaire_datatable", "hidden_columns"),
|
||||||
prevent_initial_call=True,
|
prevent_initial_call=True,
|
||||||
)
|
)
|
||||||
@@ -469,30 +483,6 @@ def toggle_titulaire_columns(click_open, click_close, is_open):
|
|||||||
return is_open
|
return is_open
|
||||||
|
|
||||||
|
|
||||||
@callback(
|
|
||||||
Output("titulaire-filters", "data"), Input("titulaire_datatable", "filter_query")
|
|
||||||
)
|
|
||||||
def sync_filters_to_local_storage(filter_query):
|
|
||||||
return filter_query
|
|
||||||
|
|
||||||
|
|
||||||
@callback(Output("titulaire-sort", "data"), Input("titulaire_datatable", "sort_by"))
|
|
||||||
def sync_sort_to_local_storage(sort_by):
|
|
||||||
return sort_by
|
|
||||||
|
|
||||||
|
|
||||||
@callback(
|
|
||||||
Output("titulaire_datatable", "filter_query", allow_duplicate=True),
|
|
||||||
Output("titulaire_datatable", "sort_by", allow_duplicate=True),
|
|
||||||
Input("titulaire_url", "href"),
|
|
||||||
State("titulaire-filters", "data"),
|
|
||||||
State("titulaire-sort", "data"),
|
|
||||||
prevent_initial_call=True,
|
|
||||||
)
|
|
||||||
def sync_local_storage_to_datatable(href, filter_query, sort_by):
|
|
||||||
return filter_query, sort_by
|
|
||||||
|
|
||||||
|
|
||||||
@callback(
|
@callback(
|
||||||
Output("titulaire_datatable", "filter_query", allow_duplicate=True),
|
Output("titulaire_datatable", "filter_query", allow_duplicate=True),
|
||||||
Output("titulaire_datatable", "sort_by"),
|
Output("titulaire_datatable", "sort_by"),
|
||||||
|
|||||||
+9
-4
@@ -9,7 +9,7 @@ import dash
|
|||||||
import polars as pl
|
import polars as pl
|
||||||
import polars.selectors as cs
|
import polars.selectors as cs
|
||||||
from dash import no_update
|
from dash import no_update
|
||||||
from httpx import get, post
|
from httpx import HTTPError, get, post
|
||||||
from polars.exceptions import ComputeError
|
from polars.exceptions import ComputeError
|
||||||
from unidecode import unidecode
|
from unidecode import unidecode
|
||||||
|
|
||||||
@@ -208,8 +208,13 @@ def format_values(dff: pl.DataFrame) -> pl.DataFrame:
|
|||||||
|
|
||||||
def get_annuaire_data(siret: str) -> dict:
|
def get_annuaire_data(siret: str) -> dict:
|
||||||
url = f"https://recherche-entreprises.api.gouv.fr/search?q={siret}"
|
url = f"https://recherche-entreprises.api.gouv.fr/search?q={siret}"
|
||||||
response = get(url)
|
try:
|
||||||
return response.json()["results"][0]
|
response = get(url).raise_for_status()
|
||||||
|
response = response.json()["results"][0]
|
||||||
|
except (HTTPError, IndexError):
|
||||||
|
response = None
|
||||||
|
logger.warning("Could not fetch data from recherche-entreprises.api.")
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
def get_decp_data() -> pl.DataFrame:
|
def get_decp_data() -> pl.DataFrame:
|
||||||
@@ -601,7 +606,7 @@ def prepare_table_data(
|
|||||||
trigger_cleanup = no_update if source_table == "tableau" else str(uuid.uuid4())
|
trigger_cleanup = no_update if source_table == "tableau" else str(uuid.uuid4())
|
||||||
|
|
||||||
# Application des tris
|
# Application des tris
|
||||||
if len(sort_by) > 0:
|
if sort_by and len(sort_by) > 0:
|
||||||
lff = sort_table_data(lff, sort_by)
|
lff = sort_table_data(lff, sort_by)
|
||||||
|
|
||||||
# Matérialisation des filtres
|
# Matérialisation des filtres
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
|
||||||
|
import polars as pl
|
||||||
|
import pytest
|
||||||
|
from selenium.webdriver.chrome.options import Options
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def test_data():
|
||||||
|
data = [
|
||||||
|
{
|
||||||
|
"uid": "1",
|
||||||
|
"id": "1",
|
||||||
|
"acheteur_nom": "ACHETEUR 1",
|
||||||
|
"acheteur_id": "a1",
|
||||||
|
"titulaire_nom": "TITULAIRE 1",
|
||||||
|
"titulaire_id": "t1",
|
||||||
|
"montant": 10,
|
||||||
|
"dateNotification": datetime.date(2025, 1, 1),
|
||||||
|
"codeCPV": "71600000",
|
||||||
|
"donneesActuelles": True,
|
||||||
|
"acheteur_departement_code": "75",
|
||||||
|
"acheteur_departement_nom": "Paris",
|
||||||
|
"acheteur_commune_nom": "Paris",
|
||||||
|
"titulaire_departement_code": "35",
|
||||||
|
"titulaire_departement_nom": "Ille-et-Vilaine",
|
||||||
|
"titulaire_commune_nom": "Rennes",
|
||||||
|
"titulaire_distance": 10,
|
||||||
|
"titulaire_typeIdentifiant": "SIRET",
|
||||||
|
"objet": "Objet test",
|
||||||
|
"dureeRestanteMois": 12,
|
||||||
|
"lieuExecution_code": "75001",
|
||||||
|
"sourceFile": "test.xml",
|
||||||
|
"sourceDataset": "test_dataset",
|
||||||
|
"datePublicationDonnees": datetime.date(2025, 1, 1),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
path = "tests/test.parquet"
|
||||||
|
path = os.path.abspath(path)
|
||||||
|
print(f"Writing test data to: {path}") # <-- This will show you the real path
|
||||||
|
|
||||||
|
pl.DataFrame(data).write_parquet("tests/test.parquet")
|
||||||
|
yield path
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_setup_options():
|
||||||
|
options = Options()
|
||||||
|
options.add_argument("--window-size=1200,800")
|
||||||
|
return options
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from dash.testing.composite import DashComposite
|
||||||
|
from selenium.webdriver import Keys
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.remote.webelement import WebElement
|
||||||
|
|
||||||
|
|
||||||
|
def test_001_logo_and_search(dash_duo: DashComposite):
|
||||||
|
from src.app import app
|
||||||
|
|
||||||
|
dash_duo.start_server(app)
|
||||||
|
dash_duo.wait_for_text_to_equal(".logo > h1", "decp.info", timeout=4)
|
||||||
|
assert dash_duo.find_element(".logo > h1").text == "decp.info"
|
||||||
|
|
||||||
|
for org_type in ["acheteur", "titulaire"]:
|
||||||
|
name = f"{org_type.upper()} 1"
|
||||||
|
search_bar: WebElement = dash_duo.find_element("#search")
|
||||||
|
|
||||||
|
dash_duo.clear_input(search_bar)
|
||||||
|
|
||||||
|
search_bar.send_keys(name)
|
||||||
|
search_bar.send_keys(Keys.ENTER)
|
||||||
|
|
||||||
|
dash_duo.wait_for_element(f"#results_{org_type}_datatable", timeout=2)
|
||||||
|
result_table: WebElement = dash_duo.find_element(
|
||||||
|
f"#results_{org_type}_datatable tbody"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(result_table.find_elements(by=By.TAG_NAME, value="tr")) == 2, (
|
||||||
|
"The search should return only one result"
|
||||||
|
) # header row + 1 result
|
||||||
|
assert (
|
||||||
|
result_table.find_element(
|
||||||
|
by=By.CSS_SELECTOR, value=f'td[data-dash-column="{org_type}_nom"]'
|
||||||
|
).text
|
||||||
|
== name
|
||||||
|
), f"The search result should have the right {org_type} name"
|
||||||
|
|
||||||
|
|
||||||
|
def test_002_filter_persistence(dash_duo: DashComposite):
|
||||||
|
from src.app import app
|
||||||
|
|
||||||
|
dash_duo.start_server(app)
|
||||||
|
dash_duo.wait_for_text_to_equal(".logo > h1", "decp.info", timeout=4)
|
||||||
|
|
||||||
|
def open_page_and_check_filter_input():
|
||||||
|
dash_duo.wait_for_page(f"{dash_duo.server_url}/{page}")
|
||||||
|
filter_input_selector = (
|
||||||
|
'.marches_table th[data-dash-column="uid"] input[type="text"]'
|
||||||
|
)
|
||||||
|
dash_duo.wait_for_element(filter_input_selector, timeout=2)
|
||||||
|
_filter_input: WebElement = dash_duo.find_element(filter_input_selector)
|
||||||
|
return _filter_input
|
||||||
|
|
||||||
|
for page in ["tableau", "acheteurs/a1", "titulaires/t1"]:
|
||||||
|
print("page:", page)
|
||||||
|
filter_input = open_page_and_check_filter_input()
|
||||||
|
filter_input.send_keys("11") # a UID that doesn't exist
|
||||||
|
filter_input.send_keys(Keys.ENTER)
|
||||||
|
sleep(1)
|
||||||
|
filter_input = open_page_and_check_filter_input()
|
||||||
|
assert filter_input.get_attribute("value") == "11"
|
||||||
Reference in New Issue
Block a user