diff --git a/core/bin/dust_api.rs b/core/bin/dust_api.rs index 3423ae6e7d3f1..cb761e7f6d62b 100644 --- a/core/bin/dust_api.rs +++ b/core/bin/dust_api.rs @@ -10,6 +10,7 @@ use axum::{ routing::{delete, get, patch, post}, Router, }; + use dust::{ api_keys::validate_api_key, app, @@ -25,7 +26,7 @@ use dust::{ project, providers::provider::{provider, ProviderID}, run, - search_filter::SearchFilter, + search_filter::{Filterable, SearchFilter}, secondary_api::forward_middleware, sqlite_workers::client::{self, HEARTBEAT_INTERVAL_MS}, stores::{postgres, store}, @@ -1990,10 +1991,34 @@ async fn tables_upsert( } } +/// Retrieve table from a data source. +#[derive(serde::Deserialize)] +struct TableRetrieveQuery { + view_filter: Option, // Parsed as JSON. +} + async fn tables_retrieve( Path((project_id, data_source_id, table_id)): Path<(i64, String, String)>, State(state): State>, + Query(query): Query, ) -> (StatusCode, Json) { + let view_filter: Option = match query + .view_filter + .as_ref() + .and_then(|f| Some(serde_json::from_str(f))) + { + Some(Ok(f)) => Some(f), + None => None, + Some(Err(e)) => { + return error_response( + StatusCode::BAD_REQUEST, + "invalid_view_filter", + "Failed to parse view_filter query parameter", + Some(e.into()), + ) + } + }; + let project = project::Project::new_from_id(project_id); match state @@ -2007,7 +2032,7 @@ async fn tables_retrieve( "Failed to retrieve table", Some(e), ), - Ok(table) => match table { + Ok(table) => match table.filter(|table| table.match_filter(&view_filter)) { None => error_response( StatusCode::NOT_FOUND, "table_not_found", @@ -2030,8 +2055,26 @@ async fn tables_retrieve( async fn tables_list( Path((project_id, data_source_id)): Path<(i64, String)>, State(state): State>, + Query(query): Query, ) -> (StatusCode, Json) { let project = project::Project::new_from_id(project_id); + let view_filter: Option = match query + .view_filter + .as_ref() + .and_then(|f| Some(serde_json::from_str(f))) + { + Some(Ok(f)) => Some(f), + None => None, + Some(Err(e)) => { + return error_response( + StatusCode::BAD_REQUEST, + "invalid_view_filter", + "Failed to parse view_filter query parameter", + Some(e.into()), + ) + } + }; + match state .store .list_tables(&project, &data_source_id, None) @@ -2048,7 +2091,7 @@ async fn tables_list( Json(APIResponse { error: None, response: Some(json!({ - "tables": tables, + "tables": tables.into_iter().filter(|table| table.match_filter(&view_filter)).collect::>(), })), }), ), @@ -2218,8 +2261,25 @@ async fn tables_rows_upsert( async fn tables_rows_retrieve( Path((project_id, data_source_id, table_id, row_id)): Path<(i64, String, String, String)>, State(state): State>, + Query(query): Query, ) -> (StatusCode, Json) { let project = project::Project::new_from_id(project_id); + let view_filter: Option = match query + .view_filter + .as_ref() + .and_then(|f| Some(serde_json::from_str(f))) + { + Some(Ok(f)) => Some(f), + None => None, + Some(Err(e)) => { + return error_response( + StatusCode::BAD_REQUEST, + "invalid_view_filter", + "Failed to parse view_filter query parameter", + Some(e.into()), + ) + } + }; match state .store @@ -2234,39 +2294,41 @@ async fn tables_rows_retrieve( Some(e), ) } - Ok(None) => { - return error_response( - StatusCode::NOT_FOUND, - "table_not_found", - &format!("No table found for id `{}`", table_id), - None, - ) - } - Ok(Some(table)) => match table - .retrieve_row(state.databases_store.clone(), &row_id) - .await - { - Err(e) => error_response( - StatusCode::INTERNAL_SERVER_ERROR, - "internal_server_error", - "Failed to load row", - Some(e), - ), - Ok(None) => error_response( - StatusCode::NOT_FOUND, - "table_row_not_found", - &format!("No table row found for id `{}`", row_id), - None, - ), - Ok(Some(row)) => ( - StatusCode::OK, - Json(APIResponse { - error: None, - response: Some(json!({ - "row": row, - })), - }), - ), + Ok(table) => match table.filter(|table| table.match_filter(&view_filter)) { + None => { + return error_response( + StatusCode::NOT_FOUND, + "table_not_found", + &format!("No table found for id `{}`", table_id), + None, + ) + } + Some(table) => match table + .retrieve_row(state.databases_store.clone(), &row_id) + .await + { + Err(e) => error_response( + StatusCode::INTERNAL_SERVER_ERROR, + "internal_server_error", + "Failed to load row", + Some(e), + ), + Ok(None) => error_response( + StatusCode::NOT_FOUND, + "table_row_not_found", + &format!("No table row found for id `{}`", row_id), + None, + ), + Ok(Some(row)) => ( + StatusCode::OK, + Json(APIResponse { + error: None, + response: Some(json!({ + "row": row, + })), + }), + ), + }, }, } } @@ -2329,6 +2391,7 @@ async fn tables_rows_delete( struct DatabasesRowsListQuery { offset: usize, limit: usize, + view_filter: Option, } async fn tables_rows_list( @@ -2337,6 +2400,22 @@ async fn tables_rows_list( Query(query): Query, ) -> (StatusCode, Json) { let project = project::Project::new_from_id(project_id); + let view_filter: Option = match query + .view_filter + .as_ref() + .and_then(|f| Some(serde_json::from_str(f))) + { + Some(Ok(f)) => Some(f), + None => None, + Some(Err(e)) => { + return error_response( + StatusCode::BAD_REQUEST, + "invalid_view_filter", + "Failed to parse view_filter query parameter", + Some(e.into()), + ) + } + }; match state .store @@ -2351,39 +2430,41 @@ async fn tables_rows_list( Some(e), ) } - Ok(None) => { - return error_response( - StatusCode::NOT_FOUND, - "table_not_found", - &format!("No table found for id `{}`", table_id), - None, - ) - } - Ok(Some(table)) => match table - .list_rows( - state.databases_store.clone(), - Some((query.limit, query.offset)), - ) - .await - { - Err(e) => error_response( - StatusCode::INTERNAL_SERVER_ERROR, - "internal_server_error", - "Failed to list rows", - Some(e), - ), - Ok((rows, total)) => ( - StatusCode::OK, - Json(APIResponse { - error: None, - response: Some(json!({ - "offset": query.offset, - "limit": query.limit, - "total": total, - "rows": rows, - })), - }), - ), + Ok(table) => match table.filter(|table| table.match_filter(&view_filter)) { + None => { + return error_response( + StatusCode::NOT_FOUND, + "table_not_found", + &format!("No table found for id `{}`", table_id), + None, + ) + } + Some(table) => match table + .list_rows( + state.databases_store.clone(), + Some((query.limit, query.offset)), + ) + .await + { + Err(e) => error_response( + StatusCode::INTERNAL_SERVER_ERROR, + "internal_server_error", + "Failed to list rows", + Some(e), + ), + Ok((rows, total)) => ( + StatusCode::OK, + Json(APIResponse { + error: None, + response: Some(json!({ + "offset": query.offset, + "limit": query.limit, + "total": total, + "rows": rows, + })), + }), + ), + }, }, } } @@ -2392,6 +2473,7 @@ async fn tables_rows_list( struct DatabaseQueryRunPayload { query: String, tables: Vec<(i64, String, String)>, + view_filter: Option, } async fn databases_query_run( @@ -2418,7 +2500,15 @@ async fn databases_query_run( ), Ok(tables) => { // Check that all tables exist. - match tables.into_iter().collect::>>() { + match tables + .into_iter() + .filter(|table| { + table + .as_ref() + .map_or(true, |t| t.match_filter(&payload.view_filter)) + }) + .collect::>>() + { None => { return error_response( StatusCode::NOT_FOUND, diff --git a/types/src/front/lib/core_api.ts b/types/src/front/lib/core_api.ts index 04593ec96741d..8328df0af05d7 100644 --- a/types/src/front/lib/core_api.ts +++ b/types/src/front/lib/core_api.ts @@ -1076,18 +1076,23 @@ export class CoreAPI { async getTables({ projectId, dataSourceName, + filter, }: { projectId: string; dataSourceName: string; + filter?: CoreAPISearchFilter | null; }): Promise< CoreAPIResponse<{ tables: CoreAPITable[]; }> > { + const qs = filter + ? `?view_filter=${encodeURIComponent(JSON.stringify(filter))}` + : ""; const response = await this._fetchWithError( `${this._url}/projects/${encodeURIComponent( projectId - )}/data_sources/${encodeURIComponent(dataSourceName)}/tables`, + )}/data_sources/${encodeURIComponent(dataSourceName)}/tables${qs}`, { method: "GET", } @@ -1189,12 +1194,17 @@ export class CoreAPI { dataSourceName, tableId, rowId, + filter, }: { projectId: string; dataSourceName: string; tableId: string; rowId: string; + filter?: CoreAPISearchFilter | null; }): Promise> { + const qs = filter + ? `?view_filter=${encodeURIComponent(JSON.stringify(filter))}` + : ""; const response = await this._fetchWithError( `${this._url}/projects/${encodeURIComponent( projectId @@ -1202,7 +1212,7 @@ export class CoreAPI { dataSourceName )}/tables/${encodeURIComponent(tableId)}/rows/${encodeURIComponent( rowId - )}`, + )}${qs}`, { method: "GET", } @@ -1217,12 +1227,14 @@ export class CoreAPI { tableId, limit, offset, + filter, }: { projectId: string; dataSourceName: string; tableId: string; limit: number; offset: number; + filter?: CoreAPISearchFilter | null; }): Promise< CoreAPIResponse<{ rows: CoreAPIRow[]; @@ -1231,6 +1243,9 @@ export class CoreAPI { total: number; }> > { + const qs = filter + ? `&view_filter=${encodeURIComponent(JSON.stringify(filter))}` + : ""; const response = await this._fetchWithError( `${this._url}/projects/${encodeURIComponent( projectId @@ -1238,7 +1253,7 @@ export class CoreAPI { dataSourceName )}/tables/${encodeURIComponent( tableId - )}/rows?limit=${limit}&offset=${offset}`, + )}/rows?limit=${limit}&offset=${offset}${qs}`, { method: "GET", } @@ -1277,6 +1292,7 @@ export class CoreAPI { async queryDatabase({ tables, query, + filter, }: { tables: Array<{ project_id: string; @@ -1284,6 +1300,7 @@ export class CoreAPI { table_id: string; }>; query: string; + filter?: CoreAPISearchFilter | null; }): Promise< CoreAPIResponse<{ schema: CoreAPITableSchema; @@ -1298,6 +1315,7 @@ export class CoreAPI { body: JSON.stringify({ query, tables, + filter, }), });