Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

editoast: add train_schedule object to search endpoint #9178

Merged
merged 1 commit into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading