Skip to content

Commit

Permalink
editoast: add train_schedule object to search endpoint
Browse files Browse the repository at this point in the history
Signed-off-by: Ethan Perruzza <[email protected]>
  • Loading branch information
EthanPERRUZZA committed Oct 11, 2024
1 parent eb992f6 commit 5c58af7
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ editoast_common::schemas! {

#[derive(Debug, Derivative, Clone, Serialize, Deserialize, ToSchema, Hash)]
#[serde(deny_unknown_fields)]
#[schema(as = TrainScheduleOptionsV2)] // Avoiding conflict with v1. TODO: remove after migration to v2
#[derivative(Default)]
pub struct TrainScheduleOptions {
#[derivative(Default(value = "true"))]
Expand Down
69 changes: 68 additions & 1 deletion editoast/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8770,6 +8770,7 @@ components:
- $ref: '#/components/schemas/SearchResultItemProject'
- $ref: '#/components/schemas/SearchResultItemStudy'
- $ref: '#/components/schemas/SearchResultItemScenario'
- $ref: '#/components/schemas/SearchResultItemTrainSchedule'
description: A search result item that depends on the query's `object`
SearchResultItemOperationalPoint:
type: object
Expand Down Expand Up @@ -9003,6 +9004,72 @@ components:
format: int64
line_name:
type: string
SearchResultItemTrainSchedule:
type: object
description: A search result item for a query with `object = "trainschedule"`
required:
- id
- train_name
- labels
- rolling_stock_name
- timetable_id
- start_time
- schedule
- margins
- initial_speed
- comfort
- path
- constraint_distribution
- power_restrictions
- options
properties:
comfort:
type: integer
format: int64
constraint_distribution:
type: integer
format: int64
id:
type: integer
format: int64
minimum: 0
initial_speed:
type: number
format: double
labels:
type: array
items:
type: string
nullable: true
margins:
$ref: '#/components/schemas/Margins'
options:
$ref: '#/components/schemas/TrainScheduleOptions'
path:
type: array
items:
$ref: '#/components/schemas/PathItem'
power_restrictions:
type: array
items:
$ref: '#/components/schemas/PowerRestrictionItem'
rolling_stock_name:
type: string
schedule:
type: array
items:
$ref: '#/components/schemas/ScheduleItem'
speed_limit_tag:
type: string
nullable: true
start_time:
type: string
format: date-time
timetable_id:
type: integer
format: int64
train_name:
type: string
Side:
type: string
enum:
Expand Down Expand Up @@ -10206,7 +10273,7 @@ components:
format: int64
description: Timetable attached to the train schedule
nullable: true
TrainScheduleOptionsV2:
TrainScheduleOptions:
type: object
properties:
use_electrical_profiles:
Expand Down
117 changes: 117 additions & 0 deletions editoast/src/views/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ use axum::extract::Json;
use axum::extract::Query;
use axum::extract::State;
use axum::Extension;
use chrono::DateTime;
use chrono::NaiveDateTime;
use chrono::Utc;
use diesel::pg::Pg;
use diesel::sql_query;
use diesel::sql_types::Jsonb;
Expand All @@ -215,6 +217,11 @@ use editoast_common::geometry::GeoJsonPoint;
use editoast_derive::EditoastError;
use editoast_derive::Search;
use editoast_derive::SearchConfigStore;
use editoast_schemas::train_schedule::Margins;
use editoast_schemas::train_schedule::PathItem;
use editoast_schemas::train_schedule::PowerRestrictionItem;
use editoast_schemas::train_schedule::ScheduleItem;
use editoast_schemas::train_schedule::TrainScheduleOptions;
use editoast_search::query_into_sql;
use editoast_search::SearchConfigStore as _;
use editoast_search::SearchError;
Expand Down Expand Up @@ -345,6 +352,7 @@ async fn search(
) -> Result<Json<serde_json::Value>> {
let roles: HashSet<BuiltinRole> = match object.as_str() {
"track" | "operationalpoint" | "signal" => HashSet::from([BuiltinRole::InfraRead]),
"trainschedule" => HashSet::from([BuiltinRole::TimetableRead]),
"project" | "study" | "scenario" => HashSet::from([BuiltinRole::OpsRead]),
_ => {
return Err(SearchApiError::ObjectType {
Expand Down Expand Up @@ -683,6 +691,48 @@ pub(super) struct SearchResultItemScenario {
tags: Vec<String>,
}

#[derive(Search, Serialize, ToSchema)]
#[cfg_attr(test, derive(serde::Deserialize))]
#[search(
table = "train_schedule",
column(name = "timetable_id", data_type = "integer"),
column(name = "train_name", data_type = "string")
)]
#[allow(unused)]
/// A search result item for a query with `object = "trainschedule"`
pub(super) struct SearchResultItemTrainSchedule {
#[search(sql = "train_schedule.id")]
id: u64,
#[search(sql = "train_schedule.train_name")]
train_name: String,
#[search(sql = "train_schedule.labels")]
labels: Vec<Option<String>>,
#[search(sql = "train_schedule.rolling_stock_name")]
rolling_stock_name: String,
#[search(sql = "train_schedule.timetable_id")]
timetable_id: i64,
#[search(sql = "train_schedule.start_time")]
start_time: DateTime<Utc>,
#[search(sql = "train_schedule.schedule")]
schedule: Vec<ScheduleItem>,
#[search(sql = "train_schedule.margins")]
margins: Margins,
#[search(sql = "train_schedule.initial_speed")]
initial_speed: f64,
#[search(sql = "train_schedule.comfort")]
comfort: i64,
#[search(sql = "train_schedule.path")]
path: Vec<PathItem>,
#[search(sql = "train_schedule.constraint_distribution")]
constraint_distribution: i64,
#[search(sql = "train_schedule.speed_limit_tag")]
speed_limit_tag: Option<String>,
#[search(sql = "train_schedule.power_restrictions")]
power_restrictions: Vec<PowerRestrictionItem>,
#[search(sql = "train_schedule.options")]
options: TrainScheduleOptions,
}

/// See [editoast_search::SearchConfigStore::find]
#[derive(SearchConfigStore)]
#[search_config_store(
Expand All @@ -692,5 +742,72 @@ pub(super) struct SearchResultItemScenario {
object(name = "project", config = SearchResultItemProject),
object(name = "study", config = SearchResultItemStudy),
object(name = "scenario", config = SearchResultItemScenario),
object(name = "trainschedule", config = SearchResultItemTrainSchedule),
)]
pub struct SearchConfigFinder;

#[cfg(test)]
pub mod test {

use axum::http::StatusCode;
use pretty_assertions::assert_eq;
use rstest::rstest;
use serde_json::json;

use super::*;
use crate::models::fixtures::{create_simple_train_schedule, create_timetable};
use crate::views::test_app::TestAppBuilder;

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

// Create the timetable in the database
let timetable = create_timetable(&mut pool.get_ok()).await;
let timetable_id = timetable.id;

// Add a train_schedule in the database
let train = create_simple_train_schedule(&mut pool.get_ok(), timetable_id).await;

// The body
let request = app.post("/search").json(&json!({
"object": "trainschedule",
"query": ["and", ["=", ["train_name"], train.train_name],
["=", ["timetable_id"], timetable_id]],
}));

let response: Vec<SearchResultItemTrainSchedule> =
app.fetch(request).assert_status(StatusCode::OK).json_into();

assert_eq!(response.len(), 1);
assert_eq!(response[0].train_name, train.train_name);
}

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

// Create the timetable in the database
let timetable = create_timetable(&mut pool.get_ok()).await;
let timetable_id = timetable.id;

// Add a train_schedule in the database
create_simple_train_schedule(&mut pool.get_ok(), timetable_id).await;

let train_name = "NonExistingTrain";

// The body
let request = app.post("/search").json(&json!({
"object": "trainschedule",
"query": ["and", ["=", ["train_name"], train_name],
["=", ["timetable_id"], timetable_id]],
}));

let response: Vec<SearchResultItemTrainSchedule> =
app.fetch(request).assert_status(StatusCode::OK).json_into();

assert_eq!(response.len(), 0);
}
}
57 changes: 55 additions & 2 deletions front/src/common/api/generatedEditoastApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2848,13 +2848,67 @@ export type SearchResultItemScenario = {
tags: string[];
trains_count: number;
};
export type Margins = {
boundaries: string[];
/** The values of the margins. Must contains one more element than the boundaries
Can be a percentage `X%` or a time in minutes per 100 kilometer `Xmin/100km` */
values: string[];
};
export type TrainScheduleOptions = {
use_electrical_profiles?: boolean;
};
export type PathItem = PathItemLocation & {
/** Metadata given to mark a point as wishing to be deleted by the user.
It's useful for soft deleting the point (waiting to fix / remove all references)
If true, the train schedule is consider as invalid and must be edited */
deleted?: boolean;
id: string;
};
export type PowerRestrictionItem = {
from: string;
to: string;
value: string;
};
export type ReceptionSignal = 'OPEN' | 'STOP' | 'SHORT_SLIP_STOP';
export type ScheduleItem = {
/** The expected arrival time at the stop.
This will be used to compute the final simulation time. */
arrival?: string | null;
at: string;
/** Whether the schedule item is locked (only for display purposes) */
locked?: boolean;
reception_signal?: ReceptionSignal;
/** Duration of the stop.
Can be `None` if the train does not stop.
If `None`, `reception_signal` must be `Open`.
`Some("PT0S")` means the train stops for 0 seconds. */
stop_for?: string | null;
};
export type SearchResultItemTrainSchedule = {
comfort: number;
constraint_distribution: number;
id: number;
initial_speed: number;
labels: (string | null)[];
margins: Margins;
options: TrainScheduleOptions;
path: PathItem[];
power_restrictions: PowerRestrictionItem[];
rolling_stock_name: string;
schedule: ScheduleItem[];
speed_limit_tag?: string | null;
start_time: string;
timetable_id: number;
train_name: string;
};
export type SearchResultItem =
| SearchResultItemTrack
| SearchResultItemOperationalPoint
| SearchResultItemSignal
| SearchResultItemProject
| SearchResultItemStudy
| SearchResultItemScenario;
| SearchResultItemScenario
| SearchResultItemTrainSchedule;
export type SearchQuery = boolean | number | number | string | (SearchQuery | null)[];
export type SearchPayload = {
/** Whether to return the SQL query instead of executing it
Expand Down Expand Up @@ -3032,7 +3086,6 @@ export type PathfindingItem = {
timing_data?: StepTimingData | null;
};
export type Distribution = 'STANDARD' | 'MARECO';
export type ReceptionSignal = 'OPEN' | 'STOP' | 'SHORT_SLIP_STOP';
export type TrainScheduleBase = {
comfort?: Comfort;
constraint_distribution: Distribution;
Expand Down

0 comments on commit 5c58af7

Please sign in to comment.