Skip to content

Commit

Permalink
editoast: add rolling stock usage endoint
Browse files Browse the repository at this point in the history
This endpoint returns the list of train
schedules associated with a given rolling
stock.
  • Loading branch information
Sh099078 committed Sep 4, 2024
1 parent 43a9891 commit 5af2430
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 9 deletions.
48 changes: 48 additions & 0 deletions editoast/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1779,6 +1779,29 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/RollingStock'
/rolling_stock/{rolling_stock_id}/usage:
get:
tags:
- rolling_stock
summary: List the scenarios (and their respective studies and projects) which use a given rolling stock.
parameters:
- name: rolling_stock_id
in: path
required: true
schema:
type: integer
format: int64
responses:
'200':
description: A list of the associated scenarios and their respective studies and projects.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ScenarioReference'
'404':
description: The requested rolling stock was not found
/search:
post:
tags:
Expand Down Expand Up @@ -8185,6 +8208,31 @@ components:
allOf:
- $ref: '#/components/schemas/Tags'
nullable: true
ScenarioReference:
type: object
required:
- project_id
- project_name
- study_id
- study_name
- scenario_id
- scenario_name
properties:
project_id:
type: integer
format: int64
project_name:
type: string
scenario_id:
type: integer
format: int64
scenario_name:
type: string
study_id:
type: integer
format: int64
study_name:
type: string
ScenarioResponseV2:
allOf:
- $ref: '#/components/schemas/ScenarioV2'
Expand Down
3 changes: 3 additions & 0 deletions editoast/src/modelsv2/rolling_stock_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ use validator::ValidationErrors;

use crate::modelsv2::prelude::*;

mod schedules_from_rolling_stock;
pub use schedules_from_rolling_stock::ScenarioReference;

editoast_common::schemas! {
RollingStockModel,
PowerRestriction,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use diesel::prelude::*;
use diesel_async::RunQueryDsl;
use itertools::Itertools;
#[cfg(test)]
use serde::Deserialize;
use serde::Serialize;
use utoipa::ToSchema;

use crate::error::Result;
use crate::modelsv2::rolling_stock_model::RollingStockModel;
use editoast_models::db_connection_pool::DbConnectionV2;
use editoast_models::tables::{project, rolling_stock, scenario_v2, study, train_schedule_v2};

#[derive(Debug, Serialize, ToSchema)]
#[cfg_attr(test, derive(PartialEq, Eq, PartialOrd, Ord, Deserialize))]
pub struct ScenarioReference {
pub project_id: i64,
pub project_name: String,
pub study_id: i64,
pub study_name: String,
pub scenario_id: i64,
pub scenario_name: String,
}

impl From<SchedulesFromRollingStock> for ScenarioReference {
fn from(value: SchedulesFromRollingStock) -> Self {
let (project_id, project_name, study_id, study_name, scenario_id, scenario_name) = value;
ScenarioReference {
project_id,
project_name,
study_id,
study_name,
scenario_id,
scenario_name,
}
}
}

type SchedulesFromRollingStock = (i64, String, i64, String, i64, String);

impl RollingStockModel {
pub async fn get_usage(&self, conn: &mut DbConnectionV2) -> Result<Vec<ScenarioReference>> {
let schedules: Vec<_> = train_schedule_v2::table
.inner_join(
rolling_stock::table
.on(train_schedule_v2::rolling_stock_name.eq(rolling_stock::name)),
)
.inner_join(
(scenario_v2::table
.on(scenario_v2::timetable_id.eq(train_schedule_v2::timetable_id)))
.inner_join(study::table.inner_join(project::table)),
)
.select((
project::id,
project::name,
study::id,
study::name,
scenario_v2::id,
scenario_v2::name,
))
.filter(rolling_stock::id.eq(self.id))
.filter(train_schedule_v2::id.is_not_null())
.load::<SchedulesFromRollingStock>(conn)
.await?;
let schedules = schedules.into_iter().map_into().collect();
Ok(schedules)
}
}
150 changes: 141 additions & 9 deletions editoast/src/views/rolling_stocks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use crate::error::InternalError;
use crate::error::Result;
use crate::modelsv2::prelude::*;
use crate::modelsv2::rolling_stock_livery::RollingStockLiveryModel;
use crate::modelsv2::rolling_stock_model::ScenarioReference;
use crate::modelsv2::rolling_stock_model::TrainScheduleScenarioStudyProject;
use crate::modelsv2::Document;
use crate::modelsv2::RollingStockModel;
Expand All @@ -53,6 +54,7 @@ crate::routes! {
delete,
"/locked" => update_locked,
"/livery" => create_livery,
"/usage" => get_usage,
},
},
}
Expand All @@ -65,6 +67,7 @@ editoast_common::schemas! {
RollingStockError,
RollingStockKey,
RollingStockWithLiveries,
ScenarioReference,
light_rolling_stock::schemas(),
}

Expand Down Expand Up @@ -626,6 +629,34 @@ async fn create_livery(
Ok(Json(rolling_stock_livery))
}

/// List the scenarios (and their respective studies and projects) which use a given rolling stock.
#[utoipa::path(
get, path = "",
tag = "rolling_stock",
params(RollingStockIdParam),
responses(
(status = 200, description = "A list of the associated scenarios and their respective studies and projects.", body = Vec<ScenarioReference>),
(status = 404, description = "The requested rolling stock was not found"),
)
)]
pub async fn get_usage(
State(db_pool): State<DbConnectionPoolV2>,
Path(rolling_stock_id): Path<i64>,
) -> Result<Json<Vec<ScenarioReference>>> {
let mut conn = db_pool.get().await?;

let rolling_stock = RollingStockModel::retrieve_or_fail(&mut conn, rolling_stock_id, || {
RollingStockError::KeyNotFound {
rolling_stock_key: RollingStockKey::Id(rolling_stock_id),
}
})
.await?;

let related_train_schedules = rolling_stock.get_usage(&mut conn).await?;

Ok(Json(related_train_schedules))
}

/// Retrieve a rolling stock by id or by name
pub async fn retrieve_existing_rolling_stock(
conn: &mut DbConnection,
Expand Down Expand Up @@ -742,19 +773,14 @@ pub mod tests {
use pretty_assertions::assert_eq;
use rstest::rstest;
use serde_json::json;
use uuid::Uuid;

use crate::error::InternalError;

use crate::modelsv2::fixtures::create_fast_rolling_stock;

use crate::modelsv2::fixtures::create_rolling_stock_with_energy_sources;

use crate::modelsv2::fixtures::fast_rolling_stock_changeset;
use crate::modelsv2::fixtures::fast_rolling_stock_form;
use crate::modelsv2::fixtures::get_rolling_stock_with_invalid_effort_curves;
use crate::modelsv2::fixtures::rolling_stock_with_energy_sources_form;
use crate::modelsv2::prelude::*;
use super::*;
use crate::modelsv2::fixtures::*;
use crate::modelsv2::rolling_stock_model::RollingStockModel;
use crate::modelsv2::train_schedule::TrainSchedule;
use crate::views::rolling_stocks::rolling_stock_form::RollingStockForm;
use crate::views::test_app::TestApp;
use crate::views::test_app::TestAppBuilder;
Expand Down Expand Up @@ -851,6 +877,112 @@ pub mod tests {
);
}

#[rstest]
async fn get_rolling_stock_usage_with_no_usage_returns_empty_ok() {
let app = TestAppBuilder::default_app();
let stock_name = Uuid::new_v4().to_string();
let rolling_stock = fast_rolling_stock_form(stock_name.as_str());
let request = app.rolling_stock_create_request(&rolling_stock);
let RollingStockModel { id, .. } =
app.fetch(request).assert_status(StatusCode::OK).json_into();
let request = app.get(&format!("/rolling_stock/{id}/usage"));
let related_schedules: Vec<ScenarioReference> =
app.fetch(request).assert_status(StatusCode::OK).json_into();
assert!(related_schedules.is_empty());
}

#[rstest]
async fn get_rolling_stock_usage_with_related_schedules_returns_schedules_list() {
let app = TestAppBuilder::default_app();
let db_pool = app.db_pool();

let create_rolling_stock_request =
app.rolling_stock_create_request(&fast_rolling_stock_form(&Uuid::new_v4().to_string()));
let rolling_stock: RollingStockModel = app
.fetch(create_rolling_stock_request)
.assert_status(StatusCode::OK)
.json_into();

let project =
create_project(db_pool.get_ok().deref_mut(), &Uuid::new_v4().to_string()).await;
let study = create_study(
db_pool.get_ok().deref_mut(),
&Uuid::new_v4().to_string(),
project.id,
)
.await;
let timetable_1 = create_timetable(db_pool.get_ok().deref_mut()).await;
let timetable_2 = create_timetable(db_pool.get_ok().deref_mut()).await;
let infra = create_small_infra(db_pool.get_ok().deref_mut()).await;
let scenario_1 = create_scenario(
db_pool.get_ok().deref_mut(),
&Uuid::new_v4().to_string(),
study.id,
timetable_1.id,
infra.id,
)
.await;
let scenario_2 = create_scenario(
db_pool.get_ok().deref_mut(),
&Uuid::new_v4().to_string(),
study.id,
timetable_2.id,
infra.id,
)
.await;

let mut schedule_form_1 = simple_train_schedule_form(timetable_1.id);
let mut schedule_form_2 = simple_train_schedule_form(timetable_1.id);
schedule_form_1
.train_schedule
.rolling_stock_name
.clone_from(&rolling_stock.name);
schedule_form_2.train_schedule.rolling_stock_name = rolling_stock.name;
let train_schedule_1: Changeset<TrainSchedule> = schedule_form_1.into();
let _ = train_schedule_1
.create(db_pool.get_ok().deref_mut())
.await
.unwrap();
let train_schedule_2: Changeset<TrainSchedule> = schedule_form_2.into();
let _ = train_schedule_2
.create(db_pool.get_ok().deref_mut())
.await
.unwrap();

let request = app.get(&format!("/rolling_stock/{}/usage", rolling_stock.id));
let mut related_scenarios: Vec<ScenarioReference> =
app.fetch(request).assert_status(StatusCode::OK).json_into();
let mut expected_scenarios = [
ScenarioReference {
project_id: project.id,
project_name: project.name.clone(),
study_id: study.id,
study_name: study.name.clone(),
scenario_id: scenario_1.id,
scenario_name: scenario_1.name.clone(),
},
ScenarioReference {
project_id: project.id,
project_name: project.name.clone(),
study_id: study.id,
study_name: study.name.clone(),
scenario_id: scenario_2.id,
scenario_name: scenario_2.name.clone(),
},
];
assert_eq!(related_scenarios.sort(), expected_scenarios.sort());
}

#[rstest]
async fn get_invalid_rolling_stock_id_returns_404_not_found() {
let app = TestAppBuilder::default_app();
let db_pool = app.db_pool();
let _ = RollingStockModel::delete_static(db_pool.get_ok().deref_mut(), 1).await;

let request = app.get("/rolling_stock/1/usage");
app.fetch(request).assert_status(StatusCode::NOT_FOUND);
}

#[rstest]
async fn create_rolling_stock_with_base_power_class_empty() {
// GIVEN
Expand Down
20 changes: 20 additions & 0 deletions front/src/common/api/generatedEditoastApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,13 @@ const injectedRtkApi = api
}),
invalidatesTags: ['rolling_stock'],
}),
getRollingStockByRollingStockIdUsage: build.query<
GetRollingStockByRollingStockIdUsageApiResponse,
GetRollingStockByRollingStockIdUsageApiArg
>({
query: (queryArg) => ({ url: `/rolling_stock/${queryArg.rollingStockId}/usage` }),
providesTags: ['rolling_stock'],
}),
postSearch: build.mutation<PostSearchApiResponse, PostSearchApiArg>({
query: (queryArg) => ({
url: `/search`,
Expand Down Expand Up @@ -1238,6 +1245,11 @@ export type PatchRollingStockByRollingStockIdLockedApiArg = {
rollingStockId: number;
rollingStockLockedUpdateForm: RollingStockLockedUpdateForm;
};
export type GetRollingStockByRollingStockIdUsageApiResponse =
/** status 200 A list of the associated scenarios and their respective studies and projects. */ ScenarioReference[];
export type GetRollingStockByRollingStockIdUsageApiArg = {
rollingStockId: number;
};
export type PostSearchApiResponse = /** status 200 The search results */ SearchResultItem[];
export type PostSearchApiArg = {
page?: number;
Expand Down Expand Up @@ -2424,6 +2436,14 @@ export type RollingStockLockedUpdateForm = {
/** New locked value */
locked: boolean;
};
export type ScenarioReference = {
project_id: number;
project_name: string;
scenario_id: number;
scenario_name: string;
study_id: number;
study_name: string;
};
export type SearchResultItemTrack = {
infra_id: number;
line_code: number;
Expand Down

0 comments on commit 5af2430

Please sign in to comment.