Skip to content

Commit d1d8524

Browse files
committed
fixup! editoast: add train_schedule object to search endpoint
1 parent 6250bbe commit d1d8524

File tree

4 files changed

+145
-41
lines changed

4 files changed

+145
-41
lines changed

editoast/editoast_schemas/src/train_schedule/train_schedule_options.rs

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ editoast_common::schemas! {
99

1010
#[derive(Debug, Derivative, Clone, Serialize, Deserialize, ToSchema, Hash)]
1111
#[serde(deny_unknown_fields)]
12-
#[schema(as = TrainScheduleOptionsV2)] // Avoiding conflict with v1. TODO: remove after migration to v2
1312
#[derivative(Default)]
1413
pub struct TrainScheduleOptions {
1514
#[derivative(Default(value = "true"))]

editoast/openapi.yaml

+12-17
Original file line numberDiff line numberDiff line change
@@ -9000,16 +9000,15 @@ components:
90009000
- comfort
90019001
- path
90029002
- constraint_distribution
9003-
- speed_limit_tag
90049003
- power_restrictions
90059004
- options
90069005
properties:
90079006
comfort:
90089007
type: integer
9009-
format: int32
9008+
format: int64
90109009
constraint_distribution:
90119010
type: integer
9012-
format: int32
9011+
format: int64
90139012
id:
90149013
type: integer
90159014
format: int64
@@ -9021,38 +9020,34 @@ components:
90219020
type: array
90229021
items:
90239022
type: string
9023+
nullable: true
90249024
margins:
9025-
type: array
9026-
items:
9027-
type: string
9025+
$ref: '#/components/schemas/Margins'
90289026
options:
9029-
type: array
9030-
items:
9031-
type: string
9027+
$ref: '#/components/schemas/TrainScheduleOptions'
90329028
path:
90339029
type: array
90349030
items:
9035-
type: string
9031+
$ref: '#/components/schemas/PathItem'
90369032
power_restrictions:
90379033
type: array
90389034
items:
9039-
type: string
9035+
$ref: '#/components/schemas/PowerRestrictionItem'
90409036
rolling_stock_name:
90419037
type: string
90429038
schedule:
90439039
type: array
90449040
items:
9045-
type: string
9041+
$ref: '#/components/schemas/ScheduleItem'
90469042
speed_limit_tag:
9047-
type: array
9048-
items:
9049-
type: string
9043+
type: string
9044+
nullable: true
90509045
start_time:
90519046
type: string
9047+
format: date-time
90529048
timetable_id:
90539049
type: integer
90549050
format: int64
9055-
minimum: 0
90569051
train_name:
90579052
type: string
90589053
Side:
@@ -10258,7 +10253,7 @@ components:
1025810253
format: int64
1025910254
description: Timetable attached to the train schedule
1026010255
nullable: true
10261-
TrainScheduleOptionsV2:
10256+
TrainScheduleOptions:
1026210257
type: object
1026310258
properties:
1026410259
use_electrical_profiles:

editoast/src/views/search.rs

+90-15
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,9 @@ use axum::extract::Json;
203203
use axum::extract::Query;
204204
use axum::extract::State;
205205
use axum::Extension;
206+
use chrono::DateTime;
206207
use chrono::NaiveDateTime;
208+
use chrono::Utc;
207209
use diesel::pg::Pg;
208210
use diesel::sql_query;
209211
use diesel::sql_types::Jsonb;
@@ -215,6 +217,11 @@ use editoast_common::geometry::GeoJsonPoint;
215217
use editoast_derive::EditoastError;
216218
use editoast_derive::Search;
217219
use editoast_derive::SearchConfigStore;
220+
use editoast_schemas::train_schedule::Margins;
221+
use editoast_schemas::train_schedule::PathItem;
222+
use editoast_schemas::train_schedule::PowerRestrictionItem;
223+
use editoast_schemas::train_schedule::ScheduleItem;
224+
use editoast_schemas::train_schedule::TrainScheduleOptions;
218225
use editoast_search::query_into_sql;
219226
use editoast_search::SearchConfigStore as _;
220227
use editoast_search::SearchError;
@@ -344,9 +351,8 @@ async fn search(
344351
Json(SearchPayload { object, query, dry }): Json<SearchPayload>,
345352
) -> Result<Json<serde_json::Value>> {
346353
let roles: HashSet<BuiltinRole> = match object.as_str() {
347-
"track" | "operationalpoint" | "signal" | "trainschedule" => {
348-
HashSet::from([BuiltinRole::InfraRead])
349-
}
354+
"track" | "operationalpoint" | "signal" => HashSet::from([BuiltinRole::InfraRead]),
355+
"trainschedule" => HashSet::from([BuiltinRole::TimetableRead]),
350356
"project" | "study" | "scenario" => HashSet::from([BuiltinRole::OpsRead]),
351357
_ => {
352358
return Err(SearchApiError::ObjectType {
@@ -659,6 +665,7 @@ pub(super) struct SearchResultItemStudy {
659665
)]
660666
#[allow(unused)]
661667
/// A search result item for a query with `object = "scenario"`
668+
#[derive(serde::Deserialize)]
662669
pub(super) struct SearchResultItemScenario {
663670
#[search(sql = "scenario.id")]
664671
id: u64,
@@ -693,37 +700,38 @@ pub(super) struct SearchResultItemScenario {
693700
)]
694701
#[allow(unused)]
695702
/// A search result item for a query with `object = "trainschedule"`
703+
#[derive(serde::Deserialize)]
696704
pub(super) struct SearchResultItemTrainSchedule {
697705
#[search(sql = "train_schedule.id")]
698706
id: u64,
699707
#[search(sql = "train_schedule.train_name")]
700-
train_name: String, // useless puisqu'on en a besoin pour la request?
708+
train_name: String,
701709
#[search(sql = "train_schedule.labels")]
702-
labels: Vec<String>,
710+
labels: Vec<Option<String>>,
703711
#[search(sql = "train_schedule.rolling_stock_name")]
704712
rolling_stock_name: String,
705713
#[search(sql = "train_schedule.timetable_id")]
706-
timetable_id: u64, // useless puisqu'on en a besoin pour la request?
714+
timetable_id: i64,
707715
#[search(sql = "train_schedule.start_time")]
708-
start_time: String,
716+
start_time: DateTime<Utc>,
709717
#[search(sql = "train_schedule.schedule")]
710-
schedule: Vec<String>,
718+
schedule: Vec<ScheduleItem>,
711719
#[search(sql = "train_schedule.margins")]
712-
margins: Vec<String>,
720+
margins: Margins,
713721
#[search(sql = "train_schedule.initial_speed")]
714722
initial_speed: f64,
715723
#[search(sql = "train_schedule.comfort")]
716-
comfort: i16,
724+
comfort: i64,
717725
#[search(sql = "train_schedule.path")]
718-
path: Vec<String>,
726+
path: Vec<PathItem>,
719727
#[search(sql = "train_schedule.constraint_distribution")]
720-
constraint_distribution: i16,
728+
constraint_distribution: i64,
721729
#[search(sql = "train_schedule.speed_limit_tag")]
722-
speed_limit_tag: Vec<String>,
730+
speed_limit_tag: Option<String>,
723731
#[search(sql = "train_schedule.power_restrictions")]
724-
power_restrictions: Vec<String>,
732+
power_restrictions: Vec<PowerRestrictionItem>,
725733
#[search(sql = "train_schedule.options")]
726-
options: Vec<String>,
734+
options: TrainScheduleOptions,
727735
}
728736

729737
/// See [editoast_search::SearchConfigStore::find]
@@ -738,3 +746,70 @@ pub(super) struct SearchResultItemTrainSchedule {
738746
object(name = "trainschedule", config = SearchResultItemTrainSchedule),
739747
)]
740748
pub struct SearchConfigFinder;
749+
750+
#[cfg(test)]
751+
pub mod test {
752+
753+
use axum::http::StatusCode;
754+
use pretty_assertions::assert_eq;
755+
use rstest::rstest;
756+
use serde_json::json;
757+
758+
use super::*;
759+
use crate::models::fixtures::{create_simple_train_schedule, create_timetable};
760+
use crate::models::train_schedule::TrainSchedule;
761+
use crate::views::test_app::TestAppBuilder;
762+
763+
#[rstest]
764+
async fn search_trainschedule_post() {
765+
let app = TestAppBuilder::default_app();
766+
let pool = app.db_pool();
767+
768+
// Create the timetable in the database
769+
let timetable = create_timetable(&mut pool.get_ok()).await;
770+
let timetable_id = timetable.id;
771+
772+
// Load data of the json file
773+
#[derive(serde::Deserialize)]
774+
struct TrainScheduleInfo {
775+
pub train_name: String,
776+
}
777+
let train_schedule_info: TrainScheduleInfo =
778+
serde_json::from_str(include_str!("../tests/train_schedules/simple.json"))
779+
.expect("Unable to parse");
780+
let train_name = train_schedule_info.train_name;
781+
782+
// Add a train_schedule in the database
783+
let train_schedule: TrainSchedule =
784+
create_simple_train_schedule(&mut pool.get_ok(), timetable_id).await;
785+
assert_eq!(train_schedule.train_name, train_name);
786+
787+
// The body
788+
let request = app.post("/search").json(&json!({
789+
"object": "trainschedule",
790+
"query": [
791+
"and",
792+
[
793+
"search",
794+
[
795+
"train_name"
796+
],
797+
train_name
798+
],
799+
[
800+
"=",
801+
[
802+
803+
"timetable_id"
804+
],
805+
timetable_id
806+
]
807+
],
808+
}));
809+
810+
let response: Vec<SearchResultItemTrainSchedule> =
811+
app.fetch(request).assert_status(StatusCode::OK).json_into();
812+
813+
assert_eq!(response[0].train_name, train_name);
814+
}
815+
}

front/src/common/api/generatedEditoastApi.ts

+43-8
Original file line numberDiff line numberDiff line change
@@ -2846,19 +2846,55 @@ export type SearchResultItemScenario = {
28462846
tags: string[];
28472847
trains_count: number;
28482848
};
2849+
export type Margins = {
2850+
boundaries: string[];
2851+
/** The values of the margins. Must contains one more element than the boundaries
2852+
Can be a percentage `X%` or a time in minutes per 100 kilometer `Xmin/100km` */
2853+
values: string[];
2854+
};
2855+
export type TrainScheduleOptions = {
2856+
use_electrical_profiles?: boolean;
2857+
};
2858+
export type PathItem = PathItemLocation & {
2859+
/** Metadata given to mark a point as wishing to be deleted by the user.
2860+
It's useful for soft deleting the point (waiting to fix / remove all references)
2861+
If true, the train schedule is consider as invalid and must be edited */
2862+
deleted?: boolean;
2863+
id: string;
2864+
};
2865+
export type PowerRestrictionItem = {
2866+
from: string;
2867+
to: string;
2868+
value: string;
2869+
};
2870+
export type ReceptionSignal = 'OPEN' | 'STOP' | 'SHORT_SLIP_STOP';
2871+
export type ScheduleItem = {
2872+
/** The expected arrival time at the stop.
2873+
This will be used to compute the final simulation time. */
2874+
arrival?: string | null;
2875+
at: string;
2876+
/** Whether the schedule item is locked (only for display purposes) */
2877+
locked?: boolean;
2878+
reception_signal?: ReceptionSignal;
2879+
/** Duration of the stop.
2880+
Can be `None` if the train does not stop.
2881+
If `None`, `reception_signal` must be `Open`.
2882+
`Some("PT0S")` means the train stops for 0 seconds. */
2883+
stop_for?: string | null;
2884+
};
28492885
export type SearchResultItemTrainSchedule = {
28502886
comfort: number;
28512887
constraint_distribution: number;
28522888
id: number;
28532889
initial_speed: number;
2854-
labels: string[];
2855-
margins: string[];
2856-
options: string[];
2857-
path: string[];
2858-
power_restrictions: string[];
2890+
labels: (string | null)[];
2891+
margins: Margins;
2892+
options: TrainScheduleOptions;
2893+
path: PathItem[];
2894+
power_restrictions: PowerRestrictionItem[];
28592895
rolling_stock_name: string;
2860-
schedule: string[];
2861-
speed_limit_tag: string[];
2896+
schedule: ScheduleItem[];
2897+
speed_limit_tag?: string | null;
28622898
start_time: string;
28632899
timetable_id: number;
28642900
train_name: string;
@@ -3048,7 +3084,6 @@ export type PathfindingItem = {
30483084
timing_data?: StepTimingData | null;
30493085
};
30503086
export type Distribution = 'STANDARD' | 'MARECO';
3051-
export type ReceptionSignal = 'OPEN' | 'STOP' | 'SHORT_SLIP_STOP';
30523087
export type TrainScheduleBase = {
30533088
comfort?: Comfort;
30543089
constraint_distribution: Distribution;

0 commit comments

Comments
 (0)