@@ -744,7 +744,7 @@ pub(crate) async fn get_pipeline_circuit_profile(
744744 responses(
745745 ( status = OK
746746 , description = "Circuit performance profile in JSON format"
747- , content_type = "application/zip "
747+ , content_type = "application/json "
748748 , body = Object ) ,
749749 ( status = NOT_FOUND
750750 , description = "Pipeline with that name does not exist"
@@ -772,6 +772,9 @@ pub(crate) async fn get_pipeline_circuit_json_profile(
772772 request : HttpRequest ,
773773) -> Result < HttpResponse , ManagerError > {
774774 let pipeline_name = path. into_inner ( ) ;
775+
776+ // Get the JSON profile from the pipeline and return it directly
777+ // The Compress middleware will automatically compress the response based on Accept-Encoding
775778 state
776779 . runner
777780 . forward_http_request_to_pipeline_by_name (
@@ -786,6 +789,87 @@ pub(crate) async fn get_pipeline_circuit_json_profile(
786789 . await
787790}
788791
792+ /// Shared helper function to extract dataflow graph from a pipeline's program_info.
793+ ///
794+ /// This function encapsulates the common logic for retrieving and validating
795+ /// the dataflow graph from a pipeline's compiled program information.
796+ pub ( crate ) async fn extract_pipeline_dataflow_graph (
797+ state : & ServerState ,
798+ tenant_id : TenantId ,
799+ pipeline_name : & str ,
800+ ) -> Result < feldera_ir:: Dataflow , ManagerError > {
801+ // Get pipeline from database
802+ let pipeline = state
803+ . db
804+ . lock ( )
805+ . await
806+ . get_pipeline ( tenant_id, pipeline_name)
807+ . await ?;
808+
809+ // Extract dataflow from program_info
810+ if let Some ( program_info_value) = & pipeline. program_info {
811+ // Parse program_info JSON to extract dataflow field
812+ match serde_json:: from_value :: < crate :: db:: types:: program:: ProgramInfo > (
813+ program_info_value. clone ( ) ,
814+ ) {
815+ Ok ( program_info) => {
816+ if let Some ( dataflow) = program_info. dataflow {
817+ Ok ( dataflow)
818+ } else {
819+ Err ( ApiError :: ProgramInfoMissesDataflow {
820+ pipeline_name : pipeline_name. to_string ( ) ,
821+ }
822+ . into ( ) )
823+ }
824+ }
825+ Err ( e) => Err ( ApiError :: InvalidProgramInfo {
826+ error : e. to_string ( ) ,
827+ }
828+ . into ( ) ) ,
829+ }
830+ } else {
831+ Err ( ApiError :: ProgramNotCompiled {
832+ pipeline_name : pipeline_name. to_string ( ) ,
833+ }
834+ . into ( ) )
835+ }
836+ }
837+
838+ /// Get Dataflow Graph
839+ ///
840+ /// Retrieve the dataflow graph of a pipeline.
841+ /// The dataflow graph is generated during SQL compilation and shows the structure
842+ /// of the compiled SQL program including the Calcite plan and MIR nodes.
843+ #[ utoipa:: path(
844+ context_path = "/v0" ,
845+ security( ( "JSON web token (JWT) or API key" = [ ] ) ) ,
846+ params(
847+ ( "pipeline_name" = String , Path , description = "Unique pipeline name" ) ,
848+ ) ,
849+ responses(
850+ ( status = OK
851+ , description = "Dataflow graph retrieved successfully"
852+ , content_type = "application/json"
853+ , body = Object ) ,
854+ ( status = NOT_FOUND
855+ , description = "Pipeline with that name does not exist or dataflow graph is not available"
856+ , body = ErrorResponse
857+ , example = json!( examples:: error_unknown_pipeline_name( ) ) ) ,
858+ ( status = INTERNAL_SERVER_ERROR , body = ErrorResponse ) ,
859+ ) ,
860+ tag = "Metrics & Debugging"
861+ ) ]
862+ #[ get( "/pipelines/{pipeline_name}/dataflow_graph" ) ]
863+ pub ( crate ) async fn get_pipeline_dataflow_graph (
864+ state : WebData < ServerState > ,
865+ tenant_id : ReqData < TenantId > ,
866+ path : web:: Path < String > ,
867+ ) -> Result < HttpResponse , ManagerError > {
868+ let pipeline_name = path. into_inner ( ) ;
869+ let dataflow = extract_pipeline_dataflow_graph ( & state, * tenant_id, & pipeline_name) . await ?;
870+ Ok ( HttpResponse :: Ok ( ) . json ( dataflow) )
871+ }
872+
789873/// Sync Checkpoints To S3
790874///
791875/// Syncs latest checkpoints to the object store configured in pipeline config.
0 commit comments