PR Reviewer Guide 🔍Here are some key observations to aid the review process: | ⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪ | | 🧪 No relevant tests | | 🔒 No security concerns identified | ⚡ Recommended focus areas for review
State ConsistencyEnsure resettingstate.isPartialData = false on all error paths does not mask legitimate partial-data cases (e.g., when partial results are intentionally surfaced alongside an error). Validate that UI logic depending onisPartialData still behaves correctly for mixed success/error scenarios. if(response.type==="error"){state.loading=false;state.loadingTotal=0;state.loadingCompleted=0;state.loadingProgressPercentage=0;state.isOperationCancelled=false;state.isPartialData=false;processApiError(response?.content,"sql");}if(response.type==="end"){state.loading=false;state.loadingTotal=0;state.loadingCompleted=0;state.loadingProgressPercentage=100;// Set to 100% when completestate.isOperationCancelled=false;state.isPartialData=false;// Explicitly set to false when completesaveCurrentStateToCache();}if(response.type==="event_progress"){state.loadingProgressPercentage=response?.content?.percent??0;state.isPartialData=true;saveCurrentStateToCache();}}catch(error:any){state.loading=false;state.isOperationCancelled=false;state.loadingTotal=0;state.loadingCompleted=0;state.loadingProgressPercentage=0;state.isPartialData=false;state.errorDetail={message:error?.message||"Unknown error in search response",code:error?.code??"",};}};constsendSearchMessage=async(payload:any)=>{// check if query is already canceled, if it is, close the socketif(state.isOperationCancelled){state.isOperationCancelled=false;// clean up the listenerscleanUpListeners(payload.traceId);return;}sendSearchMessageBasedOnRequestId({type:"search",content:{trace_id:payload.traceId,payload:{query:{ ...(awaitgetHistogramSearchRequest(payload.queryReq.query,payload.queryReq.it,payload.queryReq.startISOTimestamp,payload.queryReq.endISOTimestamp,null,)),},// pass encodig if enabled,// make sure that `encoding: null` is not being passed, that's why used object extraction logic ...(store.state.zoConfig.sql_base64_enabled ?{encoding:"base64"} :{}),},stream_type:payload.pageType,search_type:searchType.value??"dashboards",org_id:store?.state?.selectedOrganization?.identifier,use_cache:(windowasany).use_cache??true,dashboard_id:dashboardId?.value,dashboard_name:dashboardName?.value,folder_id:folderId?.value,folder_name:folderName?.value,panel_id:panelSchema.value.id,panel_name:panelSchema.value.title,run_id:runId?.value,tab_id:tabId?.value,tab_name:tabName?.value,fallback_order_by_col:getFallbackOrderByCol(),is_ui_histogram:is_ui_histogram.value,},});};consthandleSearchClose=(payload:any,response:any)=>{removeTraceId(payload?.traceId);if(response.type==="error"){processApiError(response?.content,"sql");}consterrorCodes=[1001,1006,1010,1011,1012,1013];if(errorCodes.includes(response.code)){handleSearchError(payload,{content:{message:"WebSocket connection terminated unexpectedly. Please check your network and try again",trace_id:payload.traceId,code:response.code,error_detail:"",},});}// set loading to falsestate.loading=false;state.isOperationCancelled=false;state.isPartialData=false;// save current state to cache// this is async task, which will be executed in background(await is not required)saveCurrentStateToCache();};consthandleSearchReset=(payload:any,traceId?:string)=>{// Save current state to cachesaveCurrentStateToCache();loadData();};consthandleSearchError=(payload:any,response:any)=>{removeTraceId(payload.traceId);// set loading to falsestate.loading=false;state.loadingTotal=0;state.loadingCompleted=0;state.loadingProgressPercentage=0;state.isOperationCancelled=false;state.isPartialData=false;processApiError(response?.content,"sql");};constshouldSkipSearchDueToEmptyVariables=()=>{// Retrieve all variables dataconstallVars=[ ...(getDependentVariablesData()||[]), ...(getDynamicVariablesData()||[]),];// Identify variables with empty valuesconstvariablesToSkip=allVars.filter((v)=>v.value===null||v.value===undefined||(Array.isArray(v.value)&&v.value.length===0),).map((v)=>v.name);// Log variables for which the API will be skippedvariablesToSkip.forEach((variableName)=>{state.loading=false;});// Return true if there are any variables to skip, indicating loading should be continuedreturnvariablesToSkip.length>0;};constgetDataThroughWebSocket=async(query:string,it:any,startISOTimestamp:string,endISOTimestamp:string,pageType:string,currentQueryIndex:number,)=>{try{const{ traceId}=generateTraceContext();addTraceId(traceId);constpayload:{queryReq:any;type:"search"|"histogram"|"pageCount"|"values";isPagination:boolean;traceId:string;org_id:string;pageType:string;meta:any;}={queryReq:{ query, it, startISOTimestamp, endISOTimestamp, currentQueryIndex,// pass encodig if enabled,// make sure that encoding: null is not being passed, that's why used object extraction logic ...(store.state.zoConfig.sql_base64_enabled ?{encoding:"base64"} :{}),},type:"histogram",isPagination:false, traceId,org_id:store?.state?.selectedOrganization?.identifier, pageType,meta:{ currentQueryIndex,panel_id:panelSchema.value.id,panel_name:panelSchema.value.title,run_id:runId?.value,tab_id:tabId?.value,tab_name:tabName?.value,dashboard_name:dashboardName?.value,folder_name:folderName?.value,},};// Add guard hereif(shouldSkipSearchDueToEmptyVariables()){return;}fetchQueryDataWithWebSocket(payload,{open:sendSearchMessage,close:handleSearchClose,error:handleSearchError,message:handleSearchResponse,reset:handleSearchReset,});addTraceId(traceId);}catch(e:any){state.errorDetail={message:e?.message||e,code:e?.code??"",};state.loading=false;state.isOperationCancelled=false;state.isPartialData=false;}};constgetDataThroughStreaming=async(query:string,it:any,startISOTimestamp:string,endISOTimestamp:string,pageType:string,currentQueryIndex:number,abortControllerRef:any,)=>{try{const{ traceId}=generateTraceContext();constpayload:{queryReq:any;type:"search"|"histogram"|"pageCount";isPagination:boolean;traceId:string;org_id:string;pageType:string;searchType:string;meta:any;}={queryReq:{query:{ ...(awaitgetHistogramSearchRequest(query,it,startISOTimestamp,endISOTimestamp,null,)),},},type:"histogram",isPagination:false, traceId,org_id:store?.state?.selectedOrganization?.identifier, pageType,searchType:searchType.value??"dashboards",meta:{ currentQueryIndex,dashboard_id:dashboardId?.value,dashboard_name:dashboardName?.value,folder_id:folderId?.value,folder_name:folderName?.value,panel_id:panelSchema.value.id,panel_name:panelSchema.value.title,run_id:runId?.value,tab_id:tabId?.value,tab_name:tabName?.value,fallback_order_by_col:getFallbackOrderByCol(),is_ui_histogram:is_ui_histogram.value,},};// type: "search",// content: {// trace_id: payload.traceId,// payload: {// query: await getHistogramSearchRequest(// payload.queryReq.query,// payload.queryReq.it,// payload.queryReq.startISOTimestamp,// payload.queryReq.endISOTimestamp,// null,// ),// },// stream_type: payload.pageType,// search_type: searchType.value ?? "dashboards",// org_id: store?.state?.selectedOrganization?.identifier,// use_cache: (window as any).use_cache ?? true,// dashboard_id: dashboardId?.value,// folder_id: folderId?.value,// fallback_order_by_col: getFallbackOrderByCol(),// },// if aborted, returnif(abortControllerRef?.signal?.aborted){// Set partial data flag on abortstate.isPartialData=true;// Save current state to cachesaveCurrentStateToCache();return;}// Add guard hereif(shouldSkipSearchDueToEmptyVariables()){return;}fetchQueryDataWithHttpStream(payload,{data:handleSearchResponse,error:handleSearchError,complete:handleSearchClose,reset:handleSearchReset,});addTraceId(traceId);}catch(e:any){state.errorDetail={message:e?.message||e,code:e?.code??"",};state.loading=false;state.isOperationCancelled=false;state.isPartialData=false;}};CentralizationMultiple error handlers set the same flags. Consider consolidating common reset logic (loading, totals, cancel, partial) into a single helper to avoid drift and ensure future changes remain consistent. if(response.type==="error"){state.loading=false;state.loadingTotal=0;state.loadingCompleted=0;state.loadingProgressPercentage=0;state.isOperationCancelled=false;state.isPartialData=false;processApiError(response?.content,"sql");}if(response.type==="end"){state.loading=false;state.loadingTotal=0;state.loadingCompleted=0;state.loadingProgressPercentage=100;// Set to 100% when completestate.isOperationCancelled=false;state.isPartialData=false;// Explicitly set to false when completesaveCurrentStateToCache();}if(response.type==="event_progress"){state.loadingProgressPercentage=response?.content?.percent??0;state.isPartialData=true;saveCurrentStateToCache();}}catch(error:any){state.loading=false;state.isOperationCancelled=false;state.loadingTotal=0;state.loadingCompleted=0;state.loadingProgressPercentage=0;state.isPartialData=false;state.errorDetail={message:error?.message||"Unknown error in search response",code:error?.code??"",};}};constsendSearchMessage=async(payload:any)=>{// check if query is already canceled, if it is, close the socketif(state.isOperationCancelled){state.isOperationCancelled=false;// clean up the listenerscleanUpListeners(payload.traceId);return;}sendSearchMessageBasedOnRequestId({type:"search",content:{trace_id:payload.traceId,payload:{query:{ ...(awaitgetHistogramSearchRequest(payload.queryReq.query,payload.queryReq.it,payload.queryReq.startISOTimestamp,payload.queryReq.endISOTimestamp,null,)),},// pass encodig if enabled,// make sure that `encoding: null` is not being passed, that's why used object extraction logic ...(store.state.zoConfig.sql_base64_enabled ?{encoding:"base64"} :{}),},stream_type:payload.pageType,search_type:searchType.value??"dashboards",org_id:store?.state?.selectedOrganization?.identifier,use_cache:(windowasany).use_cache??true,dashboard_id:dashboardId?.value,dashboard_name:dashboardName?.value,folder_id:folderId?.value,folder_name:folderName?.value,panel_id:panelSchema.value.id,panel_name:panelSchema.value.title,run_id:runId?.value,tab_id:tabId?.value,tab_name:tabName?.value,fallback_order_by_col:getFallbackOrderByCol(),is_ui_histogram:is_ui_histogram.value,},});};consthandleSearchClose=(payload:any,response:any)=>{removeTraceId(payload?.traceId);if(response.type==="error"){processApiError(response?.content,"sql");}consterrorCodes=[1001,1006,1010,1011,1012,1013];if(errorCodes.includes(response.code)){handleSearchError(payload,{content:{message:"WebSocket connection terminated unexpectedly. Please check your network and try again",trace_id:payload.traceId,code:response.code,error_detail:"",},});}// set loading to falsestate.loading=false;state.isOperationCancelled=false;state.isPartialData=false;// save current state to cache// this is async task, which will be executed in background(await is not required)saveCurrentStateToCache();};consthandleSearchReset=(payload:any,traceId?:string)=>{// Save current state to cachesaveCurrentStateToCache();loadData();};consthandleSearchError=(payload:any,response:any)=>{removeTraceId(payload.traceId);// set loading to falsestate.loading=false;state.loadingTotal=0;state.loadingCompleted=0;state.loadingProgressPercentage=0;state.isOperationCancelled=false;state.isPartialData=false;processApiError(response?.content,"sql");};constshouldSkipSearchDueToEmptyVariables=()=>{// Retrieve all variables dataconstallVars=[ ...(getDependentVariablesData()||[]), ...(getDynamicVariablesData()||[]),];// Identify variables with empty valuesconstvariablesToSkip=allVars.filter((v)=>v.value===null||v.value===undefined||(Array.isArray(v.value)&&v.value.length===0),).map((v)=>v.name);// Log variables for which the API will be skippedvariablesToSkip.forEach((variableName)=>{state.loading=false;});// Return true if there are any variables to skip, indicating loading should be continuedreturnvariablesToSkip.length>0;};constgetDataThroughWebSocket=async(query:string,it:any,startISOTimestamp:string,endISOTimestamp:string,pageType:string,currentQueryIndex:number,)=>{try{const{ traceId}=generateTraceContext();addTraceId(traceId);constpayload:{queryReq:any;type:"search"|"histogram"|"pageCount"|"values";isPagination:boolean;traceId:string;org_id:string;pageType:string;meta:any;}={queryReq:{ query, it, startISOTimestamp, endISOTimestamp, currentQueryIndex,// pass encodig if enabled,// make sure that encoding: null is not being passed, that's why used object extraction logic ...(store.state.zoConfig.sql_base64_enabled ?{encoding:"base64"} :{}),},type:"histogram",isPagination:false, traceId,org_id:store?.state?.selectedOrganization?.identifier, pageType,meta:{ currentQueryIndex,panel_id:panelSchema.value.id,panel_name:panelSchema.value.title,run_id:runId?.value,tab_id:tabId?.value,tab_name:tabName?.value,dashboard_name:dashboardName?.value,folder_name:folderName?.value,},};// Add guard hereif(shouldSkipSearchDueToEmptyVariables()){return;}fetchQueryDataWithWebSocket(payload,{open:sendSearchMessage,close:handleSearchClose,error:handleSearchError,message:handleSearchResponse,reset:handleSearchReset,});addTraceId(traceId);}catch(e:any){state.errorDetail={message:e?.message||e,code:e?.code??"",};state.loading=false;state.isOperationCancelled=false;state.isPartialData=false;}};constgetDataThroughStreaming=async(query:string,it:any,startISOTimestamp:string,endISOTimestamp:string,pageType:string,currentQueryIndex:number,abortControllerRef:any,)=>{try{const{ traceId}=generateTraceContext();constpayload:{queryReq:any;type:"search"|"histogram"|"pageCount";isPagination:boolean;traceId:string;org_id:string;pageType:string;searchType:string;meta:any;}={queryReq:{query:{ ...(awaitgetHistogramSearchRequest(query,it,startISOTimestamp,endISOTimestamp,null,)),},},type:"histogram",isPagination:false, traceId,org_id:store?.state?.selectedOrganization?.identifier, pageType,searchType:searchType.value??"dashboards",meta:{ currentQueryIndex,dashboard_id:dashboardId?.value,dashboard_name:dashboardName?.value,folder_id:folderId?.value,folder_name:folderName?.value,panel_id:panelSchema.value.id,panel_name:panelSchema.value.title,run_id:runId?.value,tab_id:tabId?.value,tab_name:tabName?.value,fallback_order_by_col:getFallbackOrderByCol(),is_ui_histogram:is_ui_histogram.value,},};// type: "search",// content: {// trace_id: payload.traceId,// payload: {// query: await getHistogramSearchRequest(// payload.queryReq.query,// payload.queryReq.it,// payload.queryReq.startISOTimestamp,// payload.queryReq.endISOTimestamp,// null,// ),// },// stream_type: payload.pageType,// search_type: searchType.value ?? "dashboards",// org_id: store?.state?.selectedOrganization?.identifier,// use_cache: (window as any).use_cache ?? true,// dashboard_id: dashboardId?.value,// folder_id: folderId?.value,// fallback_order_by_col: getFallbackOrderByCol(),// },// if aborted, returnif(abortControllerRef?.signal?.aborted){// Set partial data flag on abortstate.isPartialData=true;// Save current state to cachesaveCurrentStateToCache();return;}// Add guard hereif(shouldSkipSearchDueToEmptyVariables()){return;}fetchQueryDataWithHttpStream(payload,{data:handleSearchResponse,error:handleSearchError,complete:handleSearchClose,reset:handleSearchReset,});addTraceId(traceId);}catch(e:any){state.errorDetail={message:e?.message||e,code:e?.code??"",};state.loading=false;state.isOperationCancelled=false;state.isPartialData=false;}Async RaceIf concurrent requests can update sharedstate, resettingisPartialData in late-arriving error handlers might clear a valid partial state from a newer request. Verify request-scoping/traceId guards prevent stale handlers from mutating current state. consthandleSearchError=(payload:any,response:any)=>{removeTraceId(payload.traceId);// set loading to falsestate.loading=false;state.loadingTotal=0;state.loadingCompleted=0;state.loadingProgressPercentage=0;state.isOperationCancelled=false;state.isPartialData=false;processApiError(response?.content,"sql");}; |
|
Uh oh!
There was an error while loading.Please reload this page.
PR Type
Bug fix
Description
Reset
isPartialDataon error pathsPrevent stale partial-data icon after failures
Align error handlers to clear partial state
Diagram Walkthrough
File Walkthrough
usePanelDataLoader.ts
Reset partial-data flag across all error pathsweb/src/composables/dashboard/usePanelDataLoader.ts
state.isPartialData = falsein multiple error branchesprocessApiErroralways resets partial-data state