Skip to content
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
178 changes: 178 additions & 0 deletions clients/typescript/src/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,22 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/nodes": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["listPersonRunNodes"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/control/locations/{location_id}/targets/{target}/statistics": {
parameters: {
query?: never;
Expand All @@ -450,6 +466,38 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/control/locations/{location_id}/targets/{target}/triggers": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["listPersonTriggers"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/control/locations/{location_id}/targets/{target}/triggers/{trigger_id}/ticks": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["listPersonTicks"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/control/runs": {
parameters: {
query?: never;
Expand Down Expand Up @@ -3022,6 +3070,51 @@ export interface operations {
};
};
};
listPersonRunNodes: {
parameters: {
query?: never;
header?: never;
path: {
location_id: string;
target: string;
run_id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["RunNodeRecord"][];
};
};
/** @description Session rejected */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorBody"];
};
};
/** @description Run not found for a person who may view the whole target */
404: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
readPersonStatistics: {
parameters: {
query: {
Expand Down Expand Up @@ -3074,6 +3167,91 @@ export interface operations {
};
};
};
listPersonTriggers: {
parameters: {
query?: never;
header?: never;
path: {
location_id: string;
target: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["TriggerRecord"][];
};
};
/** @description Session rejected */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorBody"];
};
};
};
};
listPersonTicks: {
parameters: {
query?: {
/** @description Maximum recent ticks; defaults to 100, capped at 1000 */
limit?: number;
};
header?: never;
path: {
location_id: string;
target: string;
trigger_id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["TickRecord"][];
};
};
/** @description Session rejected */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorBody"];
};
};
/** @description Trigger not found for a person who may view the whole target */
404: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
requestJobRun: {
parameters: {
query?: never;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ pub(crate) fn person_routes() -> Router<ControlStore> {
"/api/control/locations/{location_id}/targets/{target}/runs",
get(crate::api::_helpers::resources::person_operations::recent_runs),
)
.route(
"/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/nodes",
get(crate::api::_helpers::resources::person_reads::run_nodes),
)
.route(
"/api/control/locations/{location_id}/targets/{target}/triggers",
get(crate::api::_helpers::resources::person_reads::triggers),
)
.route(
"/api/control/locations/{location_id}/targets/{target}/triggers/{trigger_id}/ticks",
get(crate::api::_helpers::resources::person_reads::ticks),
)
.route(
"/api/control/locations/{location_id}/targets/{target}/catalog",
get(crate::api::_helpers::resources::person_operations::catalog),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub(crate) mod person_location_work;
pub(crate) mod person_maintenance;
pub(crate) mod person_node_sql;
pub(crate) mod person_operations;
pub(crate) mod person_reads;
pub(crate) mod router;
pub(crate) mod run_reads;
pub(crate) mod runs;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//! Person reads of run nodes, triggers and ticks; refusal precedes every lookup, so unknown objects look forbidden.

use axum::extract::{Path, Query, State};
use axum::http::StatusCode;
use axum::{Extension, Json};
use uuid::Uuid;

use crate::api::errors::ApiError;
use crate::api::models::ReadQuery;
use crate::credentials::models::{AuditContext, AuthenticatedPerson, DefaultRole};
use crate::declarations::main::list_person_triggers::list_person_triggers;
use crate::declarations::models::TriggerRecord;
use crate::runs::main::list_person_run_nodes::list_person_run_nodes;
use crate::runs::models::RunNodeRecord;
use crate::scheduler::main::list_person_ticks::list_person_ticks;
use crate::scheduler::models::TickRecord;
use crate::store::models::ControlStore;

#[utoipa::path(get, path = "/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/nodes", operation_id = "listPersonRunNodes",
params(("location_id" = Uuid, Path), ("target" = String, Path), ("run_id" = Uuid, Path)),
responses((status = 200, body = Vec<RunNodeRecord>), (status = 401, description = "Session rejected"),
(status = 403, body = crate::api::models::ApiErrorBody),
(status = 404, description = "Run not found for a person who may view the whole target")),
security(("session" = []), ("cliSession" = [])))]
pub(crate) async fn run_nodes(
(State(store), Extension(context)): (State<ControlStore>, Extension<AuditContext>),
Extension(default_role): Extension<DefaultRole>,
Path((location_id, target, run_id)): Path<(Uuid, String, Uuid)>,
Extension(AuthenticatedPerson(principal, _)): Extension<AuthenticatedPerson>,
) -> Result<Json<Vec<RunNodeRecord>>, ApiError> {
list_person_run_nodes(
&store,
(&context, principal),
(location_id, &target, run_id),
default_role,
)
.await?
.map(Json)
.ok_or_else(|| not_found("run not found"))
}

#[utoipa::path(get, path = "/api/control/locations/{location_id}/targets/{target}/triggers", operation_id = "listPersonTriggers",
params(("location_id" = Uuid, Path), ("target" = String, Path)),
responses((status = 200, body = Vec<TriggerRecord>), (status = 401, description = "Session rejected"),
(status = 403, body = crate::api::models::ApiErrorBody)), security(("session" = []), ("cliSession" = [])))]
pub(crate) async fn triggers(
(State(store), Extension(context)): (State<ControlStore>, Extension<AuditContext>),
Extension(default_role): Extension<DefaultRole>,
Path((location_id, target)): Path<(Uuid, String)>,
Extension(AuthenticatedPerson(principal, _)): Extension<AuthenticatedPerson>,
) -> Result<Json<Vec<TriggerRecord>>, ApiError> {
Ok(Json(
list_person_triggers(
&store,
(&context, principal),
(location_id, &target),
default_role,
)
.await?,
))
}

#[utoipa::path(get, path = "/api/control/locations/{location_id}/targets/{target}/triggers/{trigger_id}/ticks", operation_id = "listPersonTicks",
params(("location_id" = Uuid, Path), ("target" = String, Path), ("trigger_id" = Uuid, Path),
("limit" = Option<u32>, Query, description = "Maximum recent ticks; defaults to 100, capped at 1000")),
responses((status = 200, body = Vec<TickRecord>), (status = 401, description = "Session rejected"),
(status = 403, body = crate::api::models::ApiErrorBody),
(status = 404, description = "Trigger not found for a person who may view the whole target")),
security(("session" = []), ("cliSession" = [])))]
pub(crate) async fn ticks(
(State(store), Extension(context)): (State<ControlStore>, Extension<AuditContext>),
Extension(default_role): Extension<DefaultRole>,
(Path((location_id, target, trigger_id)), Query(options)): (
Path<(Uuid, String, Uuid)>,
Query<ReadQuery>,
),
Extension(AuthenticatedPerson(principal, _)): Extension<AuthenticatedPerson>,
) -> Result<Json<Vec<TickRecord>>, ApiError> {
list_person_ticks(
&store,
(&context, principal),
(
(location_id, &target),
(trigger_id, options.limit.unwrap_or(100)),
),
default_role,
)
.await?
.map(Json)
.ok_or_else(|| not_found("trigger not found"))
}

fn not_found(message: &str) -> ApiError {
ApiError {
status: StatusCode::NOT_FOUND,
message: message.into(),
decision_id: None,
}
}
5 changes: 4 additions & 1 deletion crates/sqlbuild-orchestration/src/api/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::api::_helpers::authentication::{cli_login, oidc};
use crate::api::_helpers::resources::{
administrators, audit, changes, location_health, location_work, person_bootstrap,
person_checks, person_location_work, person_maintenance, person_node_sql, person_operations,
run_reads, runs, snapshots, trigger_reads, warehouse_metadata,
person_reads, run_reads, runs, snapshots, trigger_reads, warehouse_metadata,
};

#[derive(Debug, utoipa::OpenApi)]
Expand All @@ -20,6 +20,9 @@ use crate::api::_helpers::resources::{
person_operations::rerun,
person_operations::change_trigger,
person_operations::recent_runs,
person_reads::run_nodes,
person_reads::triggers,
person_reads::ticks,
person_operations::catalog,
person_operations::statistics,
person_operations::fingerprint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ pub(crate) mod read_audit_events;
pub(crate) mod read_person_access;
pub mod read_security_audit;
pub(crate) mod refuse_unknown;
pub(crate) mod row_in_target;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//! Whether a run or trigger, including a removed trigger, belongs to one location and target.

use uuid::Uuid;

use crate::store::errors::ControlError;
use crate::store::main::query::query;
use crate::store::models::{ControlTransaction, Parameter, TargetRow};

pub(crate) async fn row_in_target(
transaction: ControlTransaction,
(location_id, target): (Uuid, &str),
row: TargetRow,
) -> Result<(ControlTransaction, bool), ControlError> {
let (sql, id) = match row {
TargetRow::Run(run_id) => (
"SELECT run_id FROM sqb_runs WHERE run_id=$1 AND location_id=$2 AND target=$3",
run_id,
),
TargetRow::Trigger(trigger_id) => (
"SELECT trigger_id FROM sqb_triggers WHERE trigger_id=$1 AND location_id=$2 AND target=$3",
trigger_id,
),
};
let (transaction, rows) = query(
transaction,
sql,
vec![
Parameter::Id(id),
Parameter::Id(location_id),
Parameter::Text(target.into()),
],
)
.await?;
Ok((transaction, !rows.is_empty()))
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ pub(crate) mod documents;
pub(crate) mod operator_change;
pub(crate) mod operator_state;
pub(crate) mod publication;
pub(crate) mod reads;
pub(crate) mod trigger_scope;
pub(crate) mod trigger_state;
Loading
Loading