LCOV - code coverage report
Current view: top level - dashql-app/src/views/query_profile - engine_profile_view.tsx Coverage Total Hit
Test: DashQL 915828b Lines: 85.7 % 56 48
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as React from 'react';
       2              : 
       3              : import * as dashql from '@ankoh/dashql-core';
       4              : import { useDashQLCoreSetup } from '../../runtime/core_provider.js';
       5              : import { ProfileExecutionState } from '../../query/profile_execution_state.js';
       6              : import { createProfileLayoutConfig, QueryProfileView } from './query_profile_view.js';
       7              : import { rotateProfileLayoutOrientation } from './profile_layout_config.js';
       8              : 
       9              : type ProfileEngine = dashql.buffers.profile.ProfileEngine;
      10              : 
      11              : export interface EngineProfileViewProps {
      12              :     engine: ProfileEngine;
      13              :     planText: string;
      14              :     className?: string;
      15              :     fallback?: React.ReactNode;
      16              : }
      17              : 
      18              : export interface QueryProfileState {
      19              :     /// The plan structure
      20              :     plan: dashql.FlatBufferPtr<dashql.buffers.profile.QueryProfile> | null;
      21              :     /// The execution state the plan carries (the rows of an analyzed plan)
      22              :     execution: ProfileExecutionState;
      23              :     /// The plan text is no plan of the engine
      24              :     rejected: boolean;
      25              :     /// Lay out the plan again with the other orientation, absent without plan
      26              :     rotate?: () => void;
      27              : }
      28              : 
      29              : /// Load the plan text of an engine into a core query profile
      30            1 : function loadPlan(viewModel: dashql.DashQLQueryProfile, engine: ProfileEngine, planText: string) {
      31            1 :     switch (engine) {
      32            1 :         case dashql.buffers.profile.ProfileEngine.HYPER:
      33            1 :             return viewModel.loadHyperPlan(planText);
      34            0 :         case dashql.buffers.profile.ProfileEngine.TRINO:
      35            0 :             return viewModel.loadTrinoPlan(planText);
      36            0 :         default:
      37            0 :             throw new Error(`Plans of engine ${engine} are not supported`);
      38            1 :     }
      39            1 : }
      40              : 
      41            1 : export function useQueryProfile(engine: ProfileEngine, planText: string | null): QueryProfileState {
      42            1 :     const coreSetup = useDashQLCoreSetup();
      43            1 :     const layoutConfig = React.useMemo(() => createProfileLayoutConfig(), []);
      44            1 :     const viewModelRef = React.useRef<dashql.DashQLQueryProfile | null>(null);
      45            1 :     const [state, setState] = React.useState<QueryProfileState>({ plan: null, execution: ProfileExecutionState.EMPTY, rejected: false });
      46              : 
      47            1 :     React.useEffect(() => {
      48            1 :         let cancelled = false;
      49            1 :         setState({ plan: null, execution: ProfileExecutionState.EMPTY, rejected: false });
      50            1 :         if (planText == null) return;
      51            1 :         const load = async () => {
      52            1 :             const core = await coreSetup('engine_profile_view');
      53            1 :             if (cancelled) return;
      54            1 :             viewModelRef.current ??= core.createQueryProfile(layoutConfig);
      55            1 :             try {
      56            1 :                 const plan = loadPlan(viewModelRef.current, engine, planText);
      57            1 :                 if (plan.read().operatorsLength() === 0) {
      58            1 :                     throw new Error('Plan contains no operators');
      59              :                 }
      60            1 :                 const execution = ProfileExecutionState.EMPTY.withEvents(viewModelRef.current.snapshotEvents().read());
      61            1 :                 if (!cancelled) {
      62            1 :                     setState({ plan, execution, rejected: false });
      63              :                 }
      64            1 :             } catch (error) {
      65            1 :                 console.warn(error);
      66            1 :                 if (!cancelled) {
      67            1 :                     setState({ plan: null, execution: ProfileExecutionState.EMPTY, rejected: true });
      68              :                 }
      69            1 :             }
      70            1 :         };
      71            1 :         void load();
      72            1 :         return () => { cancelled = true; };
      73            1 :     }, [coreSetup, layoutConfig, engine, planText]);
      74              : 
      75            1 :     React.useEffect(() => () => {
      76            1 :         viewModelRef.current?.destroy();
      77            1 :         viewModelRef.current = null;
      78            1 :     }, []);
      79              : 
      80              :     // The view model keeps the orientation, the next plans are laid out with it as well
      81            1 :     const rotate = React.useCallback(() => {
      82            0 :         const viewModel = viewModelRef.current;
      83            0 :         if (viewModel == null) return;
      84            0 :         const plan = viewModel.relayout(createProfileLayoutConfig(rotateProfileLayoutOrientation(viewModel.layout.orientation)));
      85            0 :         setState(current => current.plan == null ? current : { ...current, plan });
      86            1 :     }, []);
      87              : 
      88            1 :     return React.useMemo(() => state.plan == null ? state : { ...state, rotate }, [state, rotate]);
      89            1 : }
      90              : 
      91            1 : export function EngineProfileView(props: EngineProfileViewProps) {
      92            1 :     const { plan, execution, rejected, rotate } = useQueryProfile(props.engine, props.planText);
      93              : 
      94            1 :     if (rejected) return props.fallback ?? null;
      95            1 :     return <div className={props.className}>{plan != null && <QueryProfileView plan={plan} execution={execution} onRotate={rotate} />}</div>;
      96            1 : }
        

Generated by: LCOV version 2.0-1