From b4cbfd52178b0781f84fe5788d8f2f809861db0b Mon Sep 17 00:00:00 2001 From: Leo Valais Date: Tue, 28 May 2024 22:02:50 +0200 Subject: [PATCH 1/2] editoast: impl modelsv2::List for Infra and refactor cache queries --- editoast/src/modelsv2/infra.rs | 33 ------- editoast/src/views/infra/mod.rs | 129 +++++++++++++------------ editoast/src/views/timetable/import.rs | 14 +-- tests/tests/test_infra.py | 7 +- 4 files changed, 78 insertions(+), 105 deletions(-) diff --git a/editoast/src/modelsv2/infra.rs b/editoast/src/modelsv2/infra.rs index 2a8d0d63936..2c67496c8d2 100644 --- a/editoast/src/modelsv2/infra.rs +++ b/editoast/src/modelsv2/infra.rs @@ -7,13 +7,11 @@ mod voltage; use std::pin::Pin; -use async_trait::async_trait; use chrono::NaiveDateTime; use chrono::Utc; use derivative::Derivative; use diesel::sql_query; use diesel::sql_types::BigInt; -use diesel::QueryDsl; use diesel_async::RunQueryDsl; use editoast_derive::ModelV2; use futures::future::try_join_all; @@ -29,8 +27,6 @@ use uuid::Uuid; use crate::error::Result; use crate::generated_data; use crate::infra_cache::InfraCache; -use crate::models::List; -use crate::models::NoParams; use crate::modelsv2::get_geometry_layer_table; use crate::modelsv2::get_table; use crate::modelsv2::prelude::*; @@ -39,8 +35,6 @@ use crate::modelsv2::Create; use crate::modelsv2::DbConnection; use crate::modelsv2::DbConnectionPool; use crate::tables::infra::dsl; -use crate::views::pagination::Paginate; -use crate::views::pagination::PaginatedResponse; use editoast_schemas::infra::RailJson; use editoast_schemas::infra::RAILJSON_VERSION; use editoast_schemas::primitives::ObjectType; @@ -227,33 +221,6 @@ impl Infra { } } -#[async_trait] -impl List for Infra { - async fn list_conn( - conn: &mut DbConnection, - page: i64, - page_size: i64, - _: NoParams, - ) -> Result> { - let PaginatedResponse { - count, - previous, - next, - results, - } = dsl::infra - .distinct() - .paginate(page, page_size) - .load_and_count::>(conn) - .await?; - Ok(PaginatedResponse { - count, - previous, - next, - results: results.into_iter().map(Self::from_row).collect(), - }) - } -} - #[cfg(test)] pub mod tests { use actix_web::test as actix_test; diff --git a/editoast/src/views/infra/mod.rs b/editoast/src/views/infra/mod.rs index e3ad4716ac8..b7cfc935428 100644 --- a/editoast/src/views/infra/mod.rs +++ b/editoast/src/views/infra/mod.rs @@ -26,6 +26,7 @@ use editoast_derive::EditoastError; use serde::Deserialize; use serde::Serialize; use std::collections::HashMap; +use std::ops::DerefMut as _; use std::sync::Arc; use thiserror::Error; use utoipa::IntoParams; @@ -33,6 +34,7 @@ use utoipa::ToSchema; use self::edition::edit; use self::edition::split_track_section; +use super::pagination::PaginationStats; use super::params::List; use crate::core::infra_loading::InfraLoadRequest; use crate::core::infra_state::InfraStateRequest; @@ -44,12 +46,10 @@ use crate::infra_cache::InfraCache; use crate::infra_cache::ObjectCache; use crate::map; use crate::map::MapLayers; -use crate::models::List as ModelList; -use crate::models::NoParams; use crate::modelsv2::prelude::*; use crate::modelsv2::DbConnectionPool; use crate::modelsv2::Infra; -use crate::views::pagination::PaginatedResponse; +use crate::views::pagination::PaginatedList as _; use crate::views::pagination::PaginationQueryParam; use crate::RedisClient; use editoast_schemas::infra::SwitchType; @@ -211,40 +211,47 @@ async fn refresh( Ok(Json(RefreshResponse { infra_refreshed })) } +#[derive(Serialize, ToSchema)] +struct InfraListResponse { + #[serde(flatten)] + stats: PaginationStats, + results: Vec, +} + /// Return a list of infras #[get("")] async fn list( db_pool: Data, core: Data, pagination_params: Query, -) -> Result>> { - let (page, per_page) = pagination_params +) -> Result> { + let settings = pagination_params .validate(1000)? .warn_page_size(100) - .unpack(); - let db_pool = db_pool.into_inner(); - let infras = Infra::list(db_pool.clone(), page, per_page, NoParams).await?; - let infra_state = call_core_infra_state(None, db_pool, core).await?; - let infras_with_state: Vec = infras - .results - .into_iter() - .map(|infra| { - let infra_id = infra.id; - let state = infra_state - .get(&infra_id.to_string()) - .unwrap_or(&InfraStateResponse::default()) - .status; - InfraWithState { infra, state } - }) - .collect(); - let infras_with_state = PaginatedResponse:: { - count: infras.count, - previous: infras.previous, - next: infras.next, - results: infras_with_state, + .into_selection_settings(); + + let ((infras, stats), infra_states) = { + let conn = &mut db_pool.get().await?; + futures::try_join!( + Infra::list_paginated(conn, settings), + fetch_all_infra_states(core.as_ref()), + )? }; - Ok(Json(infras_with_state)) + let response = InfraListResponse { + stats, + results: infras + .into_iter() + .map(|infra| { + let state = infra_states + .get(&infra.id.to_string()) + .map(|response| response.status) + .unwrap_or_default(); + InfraWithState { infra, state } + }) + .collect(), + }; + Ok(Json(response)) } #[derive(Debug, Clone, Copy, Default, Deserialize, PartialEq, Eq, Serialize, ToSchema)] @@ -296,11 +303,7 @@ async fn get( let conn = &mut db_pool.get().await?; let infra = Infra::retrieve_or_fail(conn, infra_id, || InfraApiError::NotFound { infra_id }).await?; - let infra_state = call_core_infra_state(Some(infra_id), db_pool.into_inner(), core).await?; - let state = infra_state - .get(&infra_id.to_string()) - .unwrap_or(&InfraStateResponse::default()) - .status; + let state = fetch_infra_state(infra.id, core.as_ref()).await?.status; Ok(Json(InfraWithState { infra, state })) } @@ -562,12 +565,6 @@ async fn unlock( Ok(HttpResponse::NoContent().finish()) } -#[derive(Debug, Default, Deserialize)] - -pub struct StatePayload { - infra: Option, -} - /// Instructs Core to load an infra #[utoipa::path( tag = "infra", @@ -595,21 +592,9 @@ async fn load( Ok(HttpResponse::NoContent().finish()) } -/// Builds a Core cache_status request, runs it -pub async fn call_core_infra_state( - infra_id: Option, - db_pool: Arc, - core: Data, -) -> Result> { - if let Some(infra_id) = infra_id { - let conn = &mut db_pool.get().await?; - if !Infra::exists(conn, infra_id).await? { - return Err(InfraApiError::NotFound { infra_id }.into()); - } - } - let infra_request = InfraStateRequest { infra: infra_id }; - let response = infra_request.fetch(core.as_ref()).await?; - Ok(response) +#[derive(Debug, Default, Deserialize)] +pub struct StatePayload { + infra: Option, } #[get("/cache_status")] @@ -618,14 +603,36 @@ async fn cache_status( db_pool: Data, core: Data, ) -> Result>> { - let payload = match payload { - Either::Left(state) => state.into_inner(), - Either::Right(_) => Default::default(), - }; - let infra_id = payload.infra; - Ok(Json( - call_core_infra_state(infra_id, db_pool.into_inner(), core).await?, - )) + if let Either::Left(Json(StatePayload { + infra: Some(infra_id), + })) = payload + { + if !Infra::exists(db_pool.get().await?.deref_mut(), infra_id).await? { + return Err(InfraApiError::NotFound { infra_id }.into()); + } + let infra_state = fetch_infra_state(infra_id, core.as_ref()).await?; + Ok(Json(HashMap::from([(infra_id.to_string(), infra_state)]))) + } else { + Ok(Json(fetch_all_infra_states(core.as_ref()).await?)) + } +} + +/// Builds a Core cache_status request, runs it +pub async fn fetch_infra_state(infra_id: i64, core: &CoreClient) -> Result { + Ok(InfraStateRequest { + infra: Some(infra_id), + } + .fetch(core) + .await? + .get(&infra_id.to_string()) + .cloned() + .unwrap_or_default()) +} + +pub async fn fetch_all_infra_states( + core: &CoreClient, +) -> Result> { + InfraStateRequest::default().fetch(core).await } #[cfg(test)] diff --git a/editoast/src/views/timetable/import.rs b/editoast/src/views/timetable/import.rs index 34d09b3326f..3d1367f423d 100644 --- a/editoast/src/views/timetable/import.rs +++ b/editoast/src/views/timetable/import.rs @@ -36,7 +36,7 @@ use crate::modelsv2::Infra; use crate::modelsv2::OperationalPointModel; use crate::modelsv2::RetrieveBatch; use crate::modelsv2::RollingStockModel; -use crate::views::infra::call_core_infra_state; +use crate::views::infra::fetch_infra_state; use crate::views::infra::InfraApiError; use crate::views::infra::InfraState; use crate::views::pathfinding::save_core_pathfinding; @@ -207,17 +207,13 @@ pub async fn post_timetable( .await?; // Check infra is loaded - let db_pool = db_pool.into_inner(); - let mut infra_state = - call_core_infra_state(Some(infra_id), db_pool.clone(), core_client.clone()).await?; - let infra_status = infra_state - .remove(&infra_id.to_string()) - .unwrap_or_default() - .status; + let core_client = core_client.as_ref(); + let infra_status = fetch_infra_state(infra_id, core_client).await?.status; if infra_status != InfraState::Cached { return Err(TimetableError::InfraNotLoaded { infra_id }.into()); } + let db_pool = db_pool.into_inner(); let mut item_futures = Vec::new(); for item in data.into_inner() { @@ -227,7 +223,7 @@ pub async fn post_timetable( db_pool.clone(), item, timetable_id, - &core_client, + core_client, )); } let item_results = try_join_all(item_futures).await?; diff --git a/tests/tests/test_infra.py b/tests/tests/test_infra.py index 6c3d20acf5b..4ac1ed76845 100644 --- a/tests/tests/test_infra.py +++ b/tests/tests/test_infra.py @@ -23,8 +23,11 @@ class _InfraDetails: @dataclass(frozen=True) class _InfraResponse: count: int - next: Optional[Any] - previous: Optional[Any] + page_size: int + page_count: int + current: int + previous: Optional[int] + next: Optional[int] results: Iterable[Mapping[str, Any]] From 96c9c814a112848a851f333cfae7892901c7caa2 Mon Sep 17 00:00:00 2001 From: Leo Valais Date: Tue, 28 May 2024 22:37:32 +0200 Subject: [PATCH 2/2] editoast: add utoipa annotation to infra list endpoint --- editoast/openapi.yaml | 40 +++++++-------- editoast/openapi_legacy.yaml | 37 -------------- editoast/src/views/infra/mod.rs | 10 +++- front/src/common/api/osrdEditoastApi.ts | 67 ++++++++++++------------- 4 files changed, 59 insertions(+), 95 deletions(-) diff --git a/editoast/openapi.yaml b/editoast/openapi.yaml index d8f44be2867..9d1ad1f7a26 100644 --- a/editoast/openapi.yaml +++ b/editoast/openapi.yaml @@ -9287,42 +9287,40 @@ paths: /infra/: get: parameters: - - description: Page number - in: query + - in: query name: page + required: false schema: default: 1 + format: int64 minimum: 1 type: integer - - description: Number of elements by page - in: query + - in: query name: page_size + required: false schema: default: 25 - maximum: 10000 + format: int64 minimum: 1 + nullable: true type: integer responses: '200': content: application/json: schema: - properties: - count: - type: number - next: {} - previous: {} - results: - items: - $ref: '#/components/schemas/Infra' - type: array - required: - - count - - next - - previous - type: object - description: The infras list - summary: Paginated list of all available infras + allOf: + - $ref: '#/components/schemas/PaginationStats' + - properties: + results: + items: + $ref: '#/components/schemas/InfraWithState' + type: array + required: + - results + type: object + description: All infras, paginated + summary: Lists all infras along with their current loading state in Core tags: - infra post: diff --git a/editoast/openapi_legacy.yaml b/editoast/openapi_legacy.yaml index 8f6fa600775..9d0d230aa81 100644 --- a/editoast/openapi_legacy.yaml +++ b/editoast/openapi_legacy.yaml @@ -22,43 +22,6 @@ tags: paths: /infra/: - get: - tags: - - infra - summary: Paginated list of all available infras - parameters: - - description: Page number - in: query - name: page - schema: - default: 1 - minimum: 1 - type: integer - - description: Number of elements by page - in: query - name: page_size - schema: - default: 25 - maximum: 10000 - minimum: 1 - type: integer - responses: - 200: - description: The infras list - content: - application/json: - schema: - type: object - properties: - count: - type: number - next: {} - previous: {} - results: - type: array - items: - $ref: "#/components/schemas/Infra" - required: [count, next, previous] post: tags: - infra diff --git a/editoast/src/views/infra/mod.rs b/editoast/src/views/infra/mod.rs index b7cfc935428..3433f0bd7da 100644 --- a/editoast/src/views/infra/mod.rs +++ b/editoast/src/views/infra/mod.rs @@ -76,6 +76,7 @@ crate::routes! { get_voltages, get_switch_types, }, + list, get_all_voltages, railjson::routes(), }, @@ -218,7 +219,14 @@ struct InfraListResponse { results: Vec, } -/// Return a list of infras +/// Lists all infras along with their current loading state in Core +#[utoipa::path( + tag = "infra", + params(PaginationQueryParam), + responses( + (status = 200, description = "All infras, paginated", body = inline(InfraListResponse)) + ), +)] #[get("")] async fn list( db_pool: Data, diff --git a/front/src/common/api/osrdEditoastApi.ts b/front/src/common/api/osrdEditoastApi.ts index 9c4ce190b35..c6f031ac2da 100644 --- a/front/src/common/api/osrdEditoastApi.ts +++ b/front/src/common/api/osrdEditoastApi.ts @@ -1049,17 +1049,12 @@ export type GetElectricalProfileSetByElectricalProfileSetIdLevelOrderApiArg = { }; export type GetHealthApiResponse = unknown; export type GetHealthApiArg = void; -export type GetInfraApiResponse = /** status 200 The infras list */ { - count: number; - next: any; - previous: any; - results?: Infra[]; +export type GetInfraApiResponse = /** status 200 All infras, paginated */ PaginationStats & { + results: InfraWithState[]; }; export type GetInfraApiArg = { - /** Page number */ page?: number; - /** Number of elements by page */ - pageSize?: number; + pageSize?: number | null; }; export type PostInfraApiResponse = /** status 201 The created infra */ Infra; export type PostInfraApiArg = { @@ -1894,6 +1889,20 @@ export type ElectricalProfileSet = { id: number; name: string; }; +export type PaginationStats = { + /** The total number of items */ + count: number; + /** The current page number */ + current: number; + /** The next page number, if any */ + next: number | null; + /** The total number of pages */ + page_count: number; + /** The number of items per page */ + page_size: number; + /** The previous page number, if any */ + previous: number | null; +}; export type Infra = { created: string; generated_version: string | null; @@ -1904,6 +1913,20 @@ export type Infra = { railjson_version: string; version: string; }; +export type InfraState = + | 'NOT_LOADED' + | 'INITIALIZING' + | 'DOWNLOADING' + | 'PARSING_JSON' + | 'PARSING_INFRA' + | 'LOADING_SIGNALS' + | 'BUILDING_BLOCKS' + | 'CACHED' + | 'TRANSIENT_ERROR' + | 'ERROR'; +export type InfraWithState = Infra & { + state: InfraState; +}; export type BufferStop = { extensions?: { sncf?: { @@ -2267,20 +2290,6 @@ export type InfraError = { obj_type: 'TrackSection' | 'Signal' | 'BufferStop' | 'Detector' | 'Switch' | 'Route'; }; }; -export type InfraState = - | 'NOT_LOADED' - | 'INITIALIZING' - | 'DOWNLOADING' - | 'PARSING_JSON' - | 'PARSING_INFRA' - | 'LOADING_SIGNALS' - | 'BUILDING_BLOCKS' - | 'CACHED' - | 'TRANSIENT_ERROR' - | 'ERROR'; -export type InfraWithState = Infra & { - state: InfraState; -}; export type BoundingBox = (number & number)[][]; export type PathfindingOutput = { detectors: string[]; @@ -2469,20 +2478,6 @@ export type ElectrificationsOnPathResponse = { electrification_ranges: RangedValue[]; warnings: InternalError[]; }; -export type PaginationStats = { - /** The total number of items */ - count: number; - /** The current page number */ - current: number; - /** The next page number, if any */ - next: number | null; - /** The total number of pages */ - page_count: number; - /** The number of items per page */ - page_size: number; - /** The previous page number, if any */ - previous: number | null; -}; export type Tags = string[]; export type Project = { budget?: number | null;