@adcops/autocore-react 3.3.100 → 3.3.105

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectManager.d.ts","sourceRoot":"","sources":["../../../src/components/tis/ProjectManager.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAuD,MAAM,OAAO,CAAC;AAU5E,MAAM,WAAW,mBAAmB;IAChC,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAyBD,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CA+UxD,CAAC"}
1
+ {"version":3,"file":"ProjectManager.d.ts","sourceRoot":"","sources":["../../../src/components/tis/ProjectManager.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAuD,MAAM,OAAO,CAAC;AAU5E,MAAM,WAAW,mBAAmB;IAChC,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AA+BD,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CA2YxD,CAAC"}
@@ -1 +1 @@
1
- import{jsx as _jsx,Fragment as _Fragment,jsxs as _jsxs}from"react/jsx-runtime";import React,{useCallback,useContext,useEffect,useState}from"react";import{Button}from"primereact/button";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{confirmDialog}from"primereact/confirmdialog";import{ProgressBar}from"primereact/progressbar";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";const formatBytes=e=>{if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KiB","MiB","GiB","TiB","PiB"];let r=0,s=e;for(;s>=1024&&r<t.length-1;)s/=1024,r++;return`${s.toFixed(s>=100?0:s>=10?1:2)} ${t[r]}`},formatDate=e=>{if(!e)return"";const t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleString()};export const ProjectManager=e=>{const t=useTis(),r=e.projectId??t.selection.projectId,{invoke:s}=useContext(EventEmitterContext),[a,i]=useState([]),[o,n]=useState(!1),[l,c]=useState(null),[d,m]=useState(!1),[p,u]=useState(!1),[f,h]=useState(null),[g,_]=useState(""),y=useCallback(async()=>{if(r){n(!0);try{const e=await s("tis.list_tests",MessageType.Request,{project_id:r});e?.success&&e.data?.tests?i(e.data.tests):i([])}catch(e){i([])}finally{n(!1)}}else i([])},[r,s]),j=useCallback(async()=>{try{const e=await s("tis.disk_usage",MessageType.Request,{});e?.success&&e.data?(h(e.data),_("")):(h(null),_(e?.error_message??"disk_usage failed"))}catch(e){h(null),_(e instanceof Error?e.message:String(e))}},[s]);useEffect(()=>{y()},[y,t.state.activeRunId]),useEffect(()=>{j()},[j]);const b=e=>{const a=e?.run_id??"",i=e?.sample_id??"";confirmDialog({header:"Delete test",icon:"pi pi-exclamation-triangle",acceptLabel:"Delete",rejectLabel:"Cancel",acceptClassName:"p-button-danger",message:_jsxs("div",{children:[_jsxs("p",{style:{marginTop:0},children:["Permanently delete run ",_jsx("code",{children:a}),i?_jsxs(_Fragment,{children:[" (sample ",_jsx("code",{children:i}),")"]}):null,"?"]}),_jsxs("p",{style:{marginBottom:0,fontSize:"0.875rem",color:"#9ca3af"},children:["Removes ",_jsx("code",{children:"test.json"}),", cycles, raw and filtered data for this run. This action cannot be undone."]})]}),accept:()=>{(async e=>{const a=e?.run_id,i=e?.method_id;if(r&&i&&a){c(a);try{const e=await s("tis.delete_test",MessageType.Request,{project_id:r,method_id:i,run_id:a});if(!e?.success)return void alert(`Failed to delete test ${a}`+(e?.error_message?`: ${e.error_message}`:""));await y(),t.selection.runId===a&&t.setSelection({runId:null})}catch(e){alert(`Delete failed: ${e instanceof Error?e.message:String(e)}`)}finally{c(null)}}})(e)}})},x=e=>{const t="string"==typeof e?.sample_id?e.sample_id:"";if(t)return t;const r=e?.config;return r&&"object"==typeof r&&"string"==typeof r.sample_id?r.sample_id:""},v=f&&f.total_bytes>0?Math.min(100,Math.round(f.used_bytes/f.total_bytes*100)):0;return _jsxs("div",{style:{width:"100%",maxWidth:"100%",boxSizing:"border-box"},children:[_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"1rem",gap:"0.5rem",flexWrap:"wrap"},children:[_jsx("h3",{style:{margin:0},children:r?`Project Manager: ${r}`:"Project Manager (no project selected)"}),_jsxs("div",{style:{display:"flex",gap:"0.4rem",alignItems:"center",flexWrap:"wrap"},children:[_jsx(Button,{icon:p?"pi pi-spin pi-spinner":"pi pi-box",label:"Download Archive",size:"small",outlined:!0,disabled:!r||p||d,onClick:async()=>{if(r){u(!0);try{const e=await s("tis.export_project_zip",MessageType.Request,{project_id:r});if(!e?.success)return void alert("Failed to build project archive"+(e?.error_message?`: ${e.error_message}`:""));const t="string"==typeof e.data?.download_url?e.data.download_url:"";if(!t)return void alert("Server did not return a download URL for the archive.");const a=document.createElement("a");a.href=t,a.download="string"==typeof e.data?.filename?e.data.filename:`${r}_project_archive.zip`,document.body.appendChild(a),a.click(),a.remove()}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{u(!1)}}},tooltip:"Download a ZIP of the entire project directory",tooltipOptions:{position:"bottom"}}),_jsx(Button,{icon:d?"pi pi-spin pi-spinner":"pi pi-trash",label:"Delete Project",size:"small",severity:"danger",disabled:!r||d||p,onClick:()=>{if(!r)return;const e=a.length;confirmDialog({header:"Delete project",icon:"pi pi-exclamation-triangle",acceptLabel:"Delete Project",rejectLabel:"Cancel",acceptClassName:"p-button-danger",message:_jsxs("div",{children:[_jsxs("p",{style:{marginTop:0},children:["Permanently delete project ",_jsx("code",{children:r})," and all ",_jsx("strong",{children:e})," test",1===e?"":"s"," inside it?"]}),_jsx("p",{style:{marginBottom:0,fontSize:"0.875rem",color:"#9ca3af"},children:"This removes every method, run, cycle, and raw/filtered blob under the project. Consider downloading the archive first. This action cannot be undone."})]}),accept:()=>{(async()=>{if(r){m(!0);try{const e=await s("tis.delete_project",MessageType.Request,{project_id:r});if(!e?.success)return void alert(`Failed to delete project ${r}`+(e?.error_message?`: ${e.error_message}`:""));t.setSelection({projectId:null,methodId:null,sampleId:null,runId:null}),await t.refreshProjects(),await j(),i([])}catch(e){alert(`Delete failed: ${e instanceof Error?e.message:String(e)}`)}finally{m(!1)}}})()}})},tooltip:"Permanently delete the project and every test inside it",tooltipOptions:{position:"bottom"}}),_jsx(Button,{icon:"pi pi-refresh",label:"Refresh",size:"small",onClick:()=>{y(),j()},disabled:o})]})]}),_jsxs("div",{style:{marginBottom:"1rem",padding:"0.75rem 1rem",border:"1px solid #2a2a2a",borderRadius:4,background:"#161616"},children:[_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"baseline",flexWrap:"wrap",gap:"0.5rem"},children:[_jsx("strong",{children:"Server Disk Space"}),f?_jsxs("span",{style:{fontSize:"0.875rem",color:"#9ca3af"},children:[formatBytes(f.available_bytes)," free of ",formatBytes(f.total_bytes)," ","(",100-v,"% available)"]}):_jsx("span",g?{style:{fontSize:"0.875rem",color:"#f87171"},children:g}:{style:{fontSize:"0.875rem",color:"#9ca3af"},children:"Loading…"})]}),f&&_jsxs(_Fragment,{children:[_jsx(ProgressBar,{value:v,showValue:!1,style:{height:"0.5rem",marginTop:"0.5rem"},color:v>=90?"#dc2626":v>=75?"#f59e0b":void 0}),_jsx("div",{style:{fontSize:"0.75rem",color:"#6b7280",marginTop:"0.4rem"},children:_jsx("code",{children:f.base_directory})})]})]}),_jsxs(DataTable,{value:a,loading:o,paginator:!0,rows:10,emptyMessage:r?"No tests in this project.":"Select a project to manage.",scrollable:!0,scrollHeight:"flex",tableStyle:{minWidth:0},style:{width:"100%"},children:[_jsx(Column,{header:"Sample ID",body:x,sortable:!0,sortFunction:e=>{const t=[...e.data];return t.sort((t,r)=>x(t).localeCompare(x(r))*(e.order??1)),t},style:{minWidth:"8rem"}}),_jsx(Column,{field:"start_time",header:"Date/Time",sortable:!0,body:e=>formatDate(e.start_time),style:{minWidth:"12rem"}}),_jsx(Column,{field:"method_id",header:"Test Method",sortable:!0,style:{minWidth:"10rem"}}),_jsx(Column,{field:"run_id",header:"Run ID",sortable:!0,style:{minWidth:"12rem"}}),_jsx(Column,{header:"Action",style:{width:"8rem"},body:e=>{const t=l===e.run_id;return _jsx(Button,{icon:t?"pi pi-spin pi-spinner":"pi pi-trash",label:"Delete",size:"small",severity:"danger",outlined:!0,disabled:null!==l||d,onClick:()=>b(e),tooltip:"Permanently delete this test",tooltipOptions:{position:"left"}})}})]})]})};
1
+ import{jsx as _jsx,Fragment as _Fragment,jsxs as _jsxs}from"react/jsx-runtime";import React,{useCallback,useContext,useEffect,useState}from"react";import{Button}from"primereact/button";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{confirmDialog}from"primereact/confirmdialog";import{ProgressBar}from"primereact/progressbar";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";const formatBytes=e=>{if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KiB","MiB","GiB","TiB","PiB"];let r=0,s=e;for(;s>=1024&&r<t.length-1;)s/=1024,r++;return`${s.toFixed(s>=100?0:s>=10?1:2)} ${t[r]}`},formatDate=e=>{if(!e)return"";const t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleString()};export const ProjectManager=e=>{const t=useTis(),r=e.projectId??t.selection.projectId,{invoke:s}=useContext(EventEmitterContext),[a,i]=useState([]),[o,n]=useState(!1),[l,c]=useState(null),[d,m]=useState(!1),[p,u]=useState(!1),[f,_]=useState(null),[h,g]=useState(""),[y,j]=useState(null),[b,x]=useState(""),v=useCallback(async()=>{if(r){n(!0);try{const e=await s("tis.list_tests",MessageType.Request,{project_id:r});e?.success&&e.data?.tests?i(e.data.tests):i([])}catch(e){i([])}finally{n(!1)}}else i([])},[r,s]),S=useCallback(async()=>{try{const e=await s("tis.disk_usage",MessageType.Request,{});e?.success&&e.data?(_(e.data),g("")):(_(null),g(e?.error_message??"disk_usage failed"))}catch(e){_(null),g(e instanceof Error?e.message:String(e))}},[s]),w=useCallback(async()=>{if(!r)return j(null),void x("");try{const e=await s("tis.project_size",MessageType.Request,{project_id:r});e?.success&&e.data?(j(e.data),x("")):(j(null),x(e?.error_message??"project_size failed"))}catch(e){j(null),x(e instanceof Error?e.message:String(e))}},[r,s]);useEffect(()=>{v()},[v,t.state.activeRunId]),useEffect(()=>{S()},[S]),useEffect(()=>{w()},[w,t.state.activeRunId]);const C=e=>{const a=e?.run_id??"",i=e?.sample_id??"";confirmDialog({header:"Delete test",icon:"pi pi-exclamation-triangle",acceptLabel:"Delete",rejectLabel:"Cancel",acceptClassName:"p-button-danger",message:_jsxs("div",{children:[_jsxs("p",{style:{marginTop:0},children:["Permanently delete run ",_jsx("code",{children:a}),i?_jsxs(_Fragment,{children:[" (sample ",_jsx("code",{children:i}),")"]}):null,"?"]}),_jsxs("p",{style:{marginBottom:0,fontSize:"0.875rem",color:"#9ca3af"},children:["Removes ",_jsx("code",{children:"test.json"}),", cycles, raw and filtered data for this run. This action cannot be undone."]})]}),accept:()=>{(async e=>{const a=e?.run_id,i=e?.method_id;if(r&&i&&a){c(a);try{const e=await s("tis.delete_test",MessageType.Request,{project_id:r,method_id:i,run_id:a});if(!e?.success)return void alert(`Failed to delete test ${a}`+(e?.error_message?`: ${e.error_message}`:""));await v(),w(),t.selection.runId===a&&t.setSelection({runId:null})}catch(e){alert(`Delete failed: ${e instanceof Error?e.message:String(e)}`)}finally{c(null)}}})(e)}})},T=e=>{const t="string"==typeof e?.sample_id?e.sample_id:"";if(t)return t;const r=e?.config;return r&&"object"==typeof r&&"string"==typeof r.sample_id?r.sample_id:""},D=f&&f.total_bytes>0?Math.min(100,Math.round(f.used_bytes/f.total_bytes*100)):0;return _jsxs("div",{style:{width:"100%",maxWidth:"100%",boxSizing:"border-box"},children:[_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"1rem",gap:"0.5rem",flexWrap:"wrap"},children:[_jsx("h3",{style:{margin:0},children:r?`Project Manager: ${r}`:"Project Manager (no project selected)"}),_jsxs("div",{style:{display:"flex",gap:"0.4rem",alignItems:"center",flexWrap:"wrap"},children:[_jsx(Button,{icon:p?"pi pi-spin pi-spinner":"pi pi-box",label:"Download Archive",size:"small",outlined:!0,disabled:!r||p||d,onClick:async()=>{if(r){u(!0);try{const e=await s("tis.export_project_zip",MessageType.Request,{project_id:r});if(!e?.success)return void alert("Failed to build project archive"+(e?.error_message?`: ${e.error_message}`:""));const t="string"==typeof e.data?.download_url?e.data.download_url:"";if(!t)return void alert("Server did not return a download URL for the archive.");const a=document.createElement("a");a.href=t,a.download="string"==typeof e.data?.filename?e.data.filename:`${r}_project_archive.zip`,document.body.appendChild(a),a.click(),a.remove()}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{u(!1)}}},tooltip:"Download a ZIP of the entire project directory",tooltipOptions:{position:"bottom"}}),_jsx(Button,{icon:d?"pi pi-spin pi-spinner":"pi pi-trash",label:"Delete Project",size:"small",severity:"danger",disabled:!r||d||p,onClick:()=>{if(!r)return;const e=a.length;confirmDialog({header:"Delete project",icon:"pi pi-exclamation-triangle",acceptLabel:"Delete Project",rejectLabel:"Cancel",acceptClassName:"p-button-danger",message:_jsxs("div",{children:[_jsxs("p",{style:{marginTop:0},children:["Permanently delete project ",_jsx("code",{children:r})," and all ",_jsx("strong",{children:e})," test",1===e?"":"s"," inside it?"]}),_jsx("p",{style:{marginBottom:0,fontSize:"0.875rem",color:"#9ca3af"},children:"This removes every method, run, cycle, and raw/filtered blob under the project. Consider downloading the archive first. This action cannot be undone."})]}),accept:()=>{(async()=>{if(r){m(!0);try{const e=await s("tis.delete_project",MessageType.Request,{project_id:r});if(!e?.success)return void alert(`Failed to delete project ${r}`+(e?.error_message?`: ${e.error_message}`:""));t.setSelection({projectId:null,methodId:null,sampleId:null,runId:null}),await t.refreshProjects(),await S(),i([])}catch(e){alert(`Delete failed: ${e instanceof Error?e.message:String(e)}`)}finally{m(!1)}}})()}})},tooltip:"Permanently delete the project and every test inside it",tooltipOptions:{position:"bottom"}}),_jsx(Button,{icon:"pi pi-refresh",label:"Refresh",size:"small",onClick:()=>{v(),S(),w()},disabled:o})]})]}),_jsxs("div",{style:{marginBottom:"1rem",padding:"0.75rem 1rem",border:"1px solid #2a2a2a",borderRadius:4,background:"#161616"},children:[_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"baseline",flexWrap:"wrap",gap:"0.5rem"},children:[_jsx("strong",{children:"Server Disk Space"}),f?_jsxs("span",{style:{fontSize:"0.875rem",color:"#9ca3af"},children:[formatBytes(f.available_bytes)," free of ",formatBytes(f.total_bytes)," ","(",100-D,"% available)"]}):_jsx("span",h?{style:{fontSize:"0.875rem",color:"#f87171"},children:h}:{style:{fontSize:"0.875rem",color:"#9ca3af"},children:"Loading…"})]}),f&&_jsxs(_Fragment,{children:[_jsx(ProgressBar,{value:D,showValue:!1,style:{height:"0.5rem",marginTop:"0.5rem"},color:D>=90?"#dc2626":D>=75?"#f59e0b":void 0}),_jsx("div",{style:{fontSize:"0.75rem",color:"#6b7280",marginTop:"0.4rem"},children:_jsx("code",{children:f.base_directory})})]}),r&&_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"baseline",flexWrap:"wrap",gap:"0.5rem",marginTop:"0.6rem",paddingTop:"0.6rem",borderTop:"1px solid #2a2a2a"},children:[_jsxs("span",{style:{fontSize:"0.875rem"},children:["This project on disk",f&&y&&f.total_bytes>0?` (${(()=>{const e=y.total_bytes/f.total_bytes*100;return e>0&&e<1?"<1%":`${Math.round(e)}%`})()} of disk)`:""]}),y?_jsxs("span",{style:{fontSize:"0.875rem",color:"#9ca3af"},children:[formatBytes(y.total_bytes)," ","(",y.file_count.toLocaleString()," file",1===y.file_count?"":"s",")"]}):_jsx("span",b?{style:{fontSize:"0.875rem",color:"#f87171"},children:b}:{style:{fontSize:"0.875rem",color:"#9ca3af"},children:"Loading…"})]})]}),_jsxs(DataTable,{value:a,loading:o,paginator:!0,rows:10,emptyMessage:r?"No tests in this project.":"Select a project to manage.",scrollable:!0,scrollHeight:"flex",tableStyle:{minWidth:0},style:{width:"100%"},children:[_jsx(Column,{header:"Sample ID",body:T,sortable:!0,sortFunction:e=>{const t=[...e.data];return t.sort((t,r)=>T(t).localeCompare(T(r))*(e.order??1)),t},style:{minWidth:"8rem"}}),_jsx(Column,{field:"start_time",header:"Date/Time",sortable:!0,body:e=>formatDate(e.start_time),style:{minWidth:"12rem"}}),_jsx(Column,{field:"method_id",header:"Test Method",sortable:!0,style:{minWidth:"10rem"}}),_jsx(Column,{field:"run_id",header:"Run ID",sortable:!0,style:{minWidth:"12rem"}}),_jsx(Column,{header:"Action",style:{width:"8rem"},body:e=>{const t=l===e.run_id;return _jsx(Button,{icon:t?"pi pi-spin pi-spinner":"pi pi-trash",label:"Delete",size:"small",severity:"danger",outlined:!0,disabled:null!==l||d,onClick:()=>C(e),tooltip:"Permanently delete this test",tooltipOptions:{position:"left"}})}})]})]})};
@@ -51,6 +51,13 @@ export interface ChartRegion {
51
51
  export interface ChartView {
52
52
  title?: string;
53
53
  type: 'cycle_scatter' | 'raw_trace';
54
+ /**
55
+ * Which columnar blob a `raw_trace` view charts: `'raw'` (default —
56
+ * the trace written by `tis.add_raw_data`) or `'filtered'` (the
57
+ * post-filter trace written by `tis.add_filtered_data`). Ignored for
58
+ * `cycle_scatter` views.
59
+ */
60
+ source?: 'raw' | 'filtered';
54
61
  x: ChartAxis;
55
62
  y: ChartSeries[];
56
63
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"TestDataView.d.ts","sourceRoot":"","sources":["../../../src/components/tis/TestDataView.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TestDataView.d.ts","sourceRoot":"","sources":["../../../src/components/tis/TestDataView.tsx"],"names":[],"mappings":"AA0BA,OAAO,KAAwE,MAAM,OAAO,CAAC;AA+B7F,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;uEAGmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;sEAEkE;IAClE,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;CAC9E;AAED,MAAM,WAAW,SAAS;IAAI,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CAAE;AAChF,MAAM,WAAW,WAAW;IAAG,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAAE;AAC5G;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IACxB,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AACD,MAAM,WAAW,SAAS;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,eAAe,GAAG,WAAW,CAAC;IACpC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,KAAK,GAAG,UAAU,CAAC;IAC5B,CAAC,EAAE,SAAS,CAAC;IACb,CAAC,EAAE,WAAW,EAAE,CAAC;IACjB;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;CAC3B;AACD,MAAM,WAAW,SAAS;IAAG,MAAM,EAAE,MAAM,CAAC;CAAE;AAC9C,MAAM,WAAW,YAAY;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB;;8CAE0C;IAC1C,OAAO,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IACtC,KAAK,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;CACrC;AACD;oDACoD;AACpD,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uEAAuE;IACvE,QAAQ,CAAC,EAAE;QAAE,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;CACvC;AACD,MAAM,WAAW,UAAU;IACvB,cAAc,EAAG,YAAY,EAAE,CAAC;IAChC,aAAa,EAAI,YAAY,EAAE,CAAC;IAChC,YAAY,EAAK,YAAY,EAAE,CAAC;IAChC,cAAc,EAAG,YAAY,EAAE,CAAC;IAChC,QAAQ,CAAC,EAAQ,YAAY,GAAG,IAAI,CAAC;IACrC,KAAK,CAAC,EAAW;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IAC/C,wDAAwD;IACxD,KAAK,CAAC,EAAW,MAAM,CAAC;IACxB,qDAAqD;IACrD,WAAW,CAAC,EAAK,MAAM,CAAC;IACxB,oEAAoE;IACpE,cAAc,CAAC,EAAE,iBAAiB,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,iBAAiB;IAC9B,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,QAAQ,CAAC,EAAG,MAAM,CAAC;IACnB,gEAAgE;IAChE,KAAK,CAAC,EAAM,MAAM,CAAC;IACnB,kEAAkE;IAClE,MAAM,CAAC,EAAK,UAAU,CAAC;IACvB,8EAA8E;IAC9E,UAAU,CAAC,EAAG,MAAM,CAAC;IACrB,wDAAwD;IACxD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;uEACmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAID,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAylBpD,CAAC"}
@@ -1 +1 @@
1
- import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import React,{useCallback,useContext,useEffect,useMemo,useRef,useState}from"react";import{Button}from"primereact/button";import{Column}from"primereact/column";import{DataTable}from"primereact/datatable";import{Dialog}from"primereact/dialog";import{Dropdown}from"primereact/dropdown";import{TabView,TabPanel}from"primereact/tabview";import{Chart as ChartJS,CategoryScale,LinearScale,PointElement,LineElement,Title,Tooltip,Legend}from"chart.js";import zoomPlugin from"chartjs-plugin-zoom";import annotationPlugin from"chartjs-plugin-annotation";import{Line}from"react-chartjs-2";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";import{useRawCycleData}from"./useRawCycleData";ChartJS.register(CategoryScale,LinearScale,PointElement,LineElement,Title,Tooltip,Legend,zoomPlugin,annotationPlugin);export const TestDataView=e=>{const t=useTis(),r=e.projectId??t.selection.projectId,a=e.methodId??t.selection.methodId,n=e.runId??t.selection.runId,l=e.schema??(a?t.schemas[a]:void 0),{throttleMs:o=100,cycleTableHeight:s="400px",chartHeight:i="320px"}=e,{invoke:c,subscribe:d,unsubscribe:u}=useContext(EventEmitterContext),[m,p]=useState(null),[y,h]=useState([]),[f,g]=useState({}),[x,_]=useState(!1),[b,j]=useState(!1),v=useRef(null),[w,C]=useState(null),[S,R]=useState(null),[T,M]=useState(!1),[L,I]=useState(null),[O,A]=useState(null),[D,E]=useState(!1),[N,k]=useState([]),[F,H]=useState(null),z=useMemo(()=>{const e=[];for(const[t,r]of Object.entries(l?.views??{}))e.push({name:t,view:r});return e},[l]),[P,$]=useState(z.length>0?z[0].name:null);useEffect(()=>{if(0===z.length)return;null!==P&&z.some(e=>e.name===P)||$(z[0].name)},[z,P]);const B=z.find(e=>e.name===P)?.view,V="raw_trace"===B?.type,G=useRef([]),W=useRef(null),q=useRef(null),U=()=>{q.current||(q.current=setTimeout(()=>{if(q.current=null,G.current.length>0){const e=G.current;G.current=[],h(t=>[...e.slice().reverse(),...t])}W.current&&(g(W.current),W.current=null)},o))};useEffect(()=>{if(!r||!a||!n)return p(null),h([]),void g({});let e=!1;return(async()=>{try{const t=await c("tis.read_test",MessageType.Request,{project_id:r,method_id:a,run_id:n});!e&&t?.success&&(p(t.data),g(t.data.results??{}));const l=await c("tis.read_cycles",MessageType.Request,{project_id:r,method_id:a,run_id:n,offset:0,limit:200,order:"desc"});!e&&l?.success&&h(l.data.cycles??[])}catch(e){}})(),()=>{e=!0}},[r,a,n,c]),useEffect(()=>{const e=e=>e?.project_id===r&&e?.method_id===a&&e?.run_id===n,t=d("tis.cycle_added",t=>{e(t)&&t.cycle&&(G.current.push(t.cycle),U())}),l=d("tis.results_updated",t=>{e(t)&&(W.current=t.results??{},U())});return()=>{u(t),u(l),q.current&&(clearTimeout(q.current),q.current=null)}},[r,a,n,o]);const J=useRawCycleData({projectId:r,methodId:a,runId:n,blobName:l?.raw_data?.blob_name??"trace",enabled:V}),Y=useMemo(()=>{if(!B)return null;if("cycle_scatter"===B.type){const e=B.x.field;if(!e)return null;const t=[...y].reverse();return{labels:t.map(t=>t[e]),datasets:B.y.map((e,r)=>({label:e.label??e.field,data:t.map(t=>t[e.field]),yAxisID:"right"===e.y_axis?"y1":"y",borderColor:palette(r),backgroundColor:palette(r),tension:.1,pointRadius:2}))}}if("raw_trace"===B.type){if(!J.raw)return null;const e=B.x.column;if(!e)return null;const t=J.raw[e]??[],r=!1!==B.smooth;return{datasets:B.y.map((e,a)=>{const n=J.raw[e.column]??[],l=r?smoothTraceForDisplay(t,n,B.smoothWindow):n.map((e,r)=>({x:t[r],y:e}));return{label:e.label??e.column,data:l,yAxisID:"right"===e.y_axis?"y1":"y",borderColor:palette(a),backgroundColor:palette(a),pointRadius:0,borderWidth:1.5,showLine:!0,...r?{cubicInterpolationMode:"monotone"}:{}}})}}return null},[B,y,J.raw]),Z=B?.y.some(e=>"right"===e.y_axis)??!1,K=useMemo(()=>{if(!V)return[];const e=J.envelope,t=e?.regions??e?.context?.regions;return Array.isArray(t)?t:[]},[V,J.envelope]),Q=useMemo(()=>[...B?.regions??[],...K],[B,K]),X=useMemo(()=>{const e="raw_trace"===B?.type;return{responsive:!0,maintainAspectRatio:!1,parsing:!e&&void 0,scales:{x:e?{type:"linear",title:{display:!!B?.x.label,text:B?.x.label}}:{title:{display:!!B?.x.label,text:B?.x.label}},y:{position:"left",title:{display:!0,text:leftAxisLabel(B)}},...Z?{y1:{position:"right",grid:{drawOnChartArea:!1},title:{display:!0,text:rightAxisLabel(B)}}}:{}},plugins:{legend:{display:!0},zoom:{pan:{enabled:!0,mode:"xy"},zoom:{wheel:{enabled:!0},pinch:{enabled:!0},mode:"xy"}},annotation:{annotations:buildRegionAnnotations(Q)}}}},[B,Z,Q]),ee=l?.raw_data?.blob_name??"trace",te=useRef(""),re=useCallback(async()=>{if(!r||!a||!n)return[];try{const e=await c("tis.list_raw",MessageType.Request,{project_id:r,method_id:a,run_id:n});if(!e?.success)return[];const t=e.data?.cycles??[];return t.filter(e=>e?.name===ee&&"number"==typeof e?.cycle_index).map(e=>e.cycle_index).sort((e,t)=>e-t)}catch{return[]}},[r,a,n,ee,c]),ae=useCallback(async e=>{if(!r||!a||!n)return;const t=`${r}|${a}|${n}|${ee}|${e??"latest"}`;if(te.current===t)return;te.current=t;const l={project_id:r,method_id:a,run_id:n,name:ee};null!=e&&(l.cycle_index=e),M(!0),R(null),C(null);try{const e=await c("tis.read_raw",MessageType.Request,l);e?.success?C(e.data??{}):R(e?.error_message??"No raw data on disk for this run.")}catch(e){R(String(e?.message??e))}finally{M(!1)}E(!0),A(null),I(null);try{const e=await c("tis.read_filtered",MessageType.Request,{project_id:r,method_id:a,run_id:n,name:ee});e?.success?I(e.data??{}):A(e?.error_message??"No filtered data on disk for this run.")}catch(e){A(String(e?.message??e))}finally{E(!1)}},[r,a,n,ee,c]);useEffect(()=>{te.current="",k([]),H(null)},[r,a,n,ee]);return useEffect(()=>{x&&null!=F&&ae(F)},[x,F,ae]),r&&a&&n&&l?_jsxs("div",{className:"vblock",style:{display:"flex",flexDirection:"column",gap:"1rem"},children:[_jsx(Header,{meta:m,config:m?.config,runId:n,projectId:r,methodId:a,canViewRaw:!!l.raw_data,onViewRaw:async()=>{_(!0);let e=N;0===e.length&&(e=await re(),k(e));const t=e.length>0?e[e.length-1]:null,r=F??t;F!==r&&H(r),await ae(r)},onShowConfig:()=>j(!0)}),_jsxs("div",{className:"p-card",style:{padding:"1rem"},children:[_jsxs("div",{className:"flex",style:{gap:"1rem",alignItems:"center",marginBottom:"0.5rem",flexWrap:"wrap"},children:[_jsx(Dropdown,{value:P,options:z.map(e=>({label:e.view.title??e.name,value:e.name})),onChange:e=>$(e.value),placeholder:0===z.length?"No view defined":"Select a view",disabled:0===z.length}),_jsx("h3",{style:{margin:0},children:B?.title??""}),V&&J.cycles.length>1&&_jsxs(_Fragment,{children:[_jsx("label",{htmlFor:"chart-cycle-picker",style:{color:"var(--text-secondary-color)"},children:"Cycle:"}),_jsx(Dropdown,{inputId:"chart-cycle-picker",value:J.selectedCycle,options:J.cycles.map(e=>({label:`Cycle ${e}`,value:e})),onChange:e=>J.setSelectedCycle(Number(e.value)),style:{minWidth:"8rem"}}),_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:["of ",J.cycles.length]})]}),_jsx("div",{style:{flex:1}}),_jsx(Button,{icon:"pi pi-th-large",outlined:!0,rounded:!0,size:"small",onClick:()=>v.current?.resetZoom?.(),disabled:!Y,tooltip:"Reset chart zoom",tooltipOptions:{position:"left"},"aria-label":"Reset chart zoom"})]}),_jsxs("div",{style:{height:i,position:"relative"},children:[V&&J.loading&&_jsx(ChartOverlay,{children:"Loading raw data…"}),V&&J.error&&_jsx(ChartOverlay,{children:J.error}),Y&&_jsx(Line,{ref:v,data:Y,options:X})]})]}),_jsxs("div",{className:"p-card",style:{padding:"1rem"},children:[_jsxs("h3",{style:{marginTop:0},children:["Cycle Data (",y.length,")"]}),(()=>{const e=y.length>CYCLE_VIRTUAL_THRESHOLD;return _jsx(DataTable,{value:y,scrollable:e,scrollHeight:e?s:void 0,virtualScrollerOptions:e?{itemSize:38}:void 0,emptyMessage:"No cycles yet.",children:l.cycle_fields.map(e=>_jsx(Column,{field:e.name,header:e.units?`${e.name} (${e.units})`:e.name,body:t=>formatCell(t[e.name],e.type,e.scale)},e.name))})})()]}),_jsxs("div",{className:"p-card",style:{padding:"1rem"},children:[_jsx("h3",{style:{marginTop:0},children:"Results"}),_jsx(ResultsGrid,{schema:l.results_fields,values:f})]}),l.raw_data&&_jsxs(Dialog,{visible:x,onHide:()=>_(!1),header:`Run Data — ${n}`,style:{width:"90vw",height:"80vh"},maximizable:!0,children:[_jsx(CyclePickerBar,{cycles:N,selected:F,onChange:H}),_jsx(RawEnvelopeHeader,{envelope:w}),_jsxs(TabView,{style:{height:"100%"},children:[_jsx(TabPanel,{header:"Raw Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(w),loading:T,error:S,rawData:l.raw_data})}),_jsx(TabPanel,{header:"Filtered Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(L),loading:D,error:O,rawData:l.raw_data,emptyMessage:"Filtered data is written by post-processing — none on disk for this run yet."})})]})]}),_jsx(Dialog,{visible:b,onHide:()=>j(!1),header:"Test Configuration",style:{width:"min(640px, 90vw)"},modal:!0,children:_jsx(ConfigList,{config:m?.config})})]}):_jsx("div",{className:"p-card",style:{padding:"1rem",color:"var(--text-secondary-color)"},children:"No test selected. Pick a row from the History tab or start a run."})};const Header=({meta:e,config:t,runId:r,projectId:a,methodId:n,canViewRaw:l,onViewRaw:o,onShowConfig:s})=>{const i="string"==typeof e?.sample_id&&e.sample_id||"string"==typeof e?.config?.sample_id&&e.config.sample_id||"",c=t&&"object"==typeof t&&Object.entries(t).some(([e])=>"sample_id"!==e);return _jsx("div",{className:"p-card",style:{padding:"1rem"},children:_jsxs("div",{className:"flex",style:{justifyContent:"space-between",alignItems:"flex-start",gap:"1rem"},children:[_jsxs("div",{children:[_jsxs("h2",{style:{margin:0,display:"flex",alignItems:"center",gap:"0.5rem"},children:[i||r,c&&_jsx(Button,{icon:"pi pi-info-circle",type:"button",rounded:!0,text:!0,onClick:s,tooltip:"Show test configuration",tooltipOptions:{position:"top"},style:{width:"2rem",height:"2rem",padding:0},"aria-label":"Show test configuration"})]}),_jsxs("div",{style:{color:"var(--text-secondary-color)",fontSize:"0.85em"},children:["project: ",a," · method: ",n," · run: ",r,e?.start_time&&_jsxs(_Fragment,{children:[" · started: ",new Date(e.start_time).toLocaleString()]})]})]}),l&&_jsx(Button,{icon:"pi pi-table",label:"View Raw Data",onClick:o,outlined:!0})]})})},ConfigList=({config:e})=>{const t=e&&"object"==typeof e?Object.entries(e).filter(([e])=>"sample_id"!==e):[];return 0===t.length?_jsx("div",{style:{color:"var(--text-secondary-color)"},children:"No configuration recorded for this run."}):_jsx("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr",gap:"0.5rem 1rem",fontSize:"0.95em"},children:t.map(([e,t])=>_jsxs(React.Fragment,{children:[_jsx("div",{style:{color:"var(--text-secondary-color)"},children:e}),_jsx("div",{children:formatCell(t,"string")})]},e))})},unwrapEnvelope=e=>e&&"object"==typeof e&&"data"in e&&e.data&&"object"==typeof e.data&&Object.values(e.data).some(e=>Array.isArray(e))?e.data:e,CyclePickerBar=({cycles:e,selected:t,onChange:r})=>e.length<=1?null:_jsxs("div",{style:{display:"flex",alignItems:"center",gap:"0.5rem",padding:"0.25rem 0.5rem 0.5rem"},children:[_jsx("label",{htmlFor:"raw-cycle-picker",style:{color:"var(--text-secondary-color)"},children:"Cycle:"}),_jsx(Dropdown,{inputId:"raw-cycle-picker",value:t,options:e.map(e=>({label:`Cycle ${e}`,value:e})),onChange:e=>r(Number(e.value)),style:{minWidth:"8rem"}}),_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:["(",e.length," cycles recorded)"]})]}),RawEnvelopeHeader=({envelope:e})=>{if(!e||"object"!=typeof e)return null;const t=e.cycle_index,r=e.cycle_fields,a=e.context;if(null==t&&!r&&!a)return null;const n=e=>{if(!e||"object"!=typeof e)return null;const t=Object.entries(e).filter(([,e])=>null!==e&&"object"!=typeof e);return 0===t.length?null:t.map(([e,t])=>_jsxs("span",{style:{marginRight:"1rem"},children:[_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:[e,": "]}),_jsx("span",{children:String(t)})]},e))};return _jsxs("div",{style:{padding:"0.5rem",borderBottom:"1px solid var(--surface-border)",fontSize:"0.9rem"},children:[null!=t&&_jsx("div",{children:_jsxs("strong",{children:["Cycle ",t]})}),r&&_jsx("div",{style:{marginTop:"0.25rem"},children:n(r)}),a&&_jsx("div",{style:{marginTop:"0.25rem"},children:n(a)})]})},DataBlobTable=({blob:e,loading:t,error:r,rawData:a,emptyMessage:n})=>{if(t)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:"Loading…"});if(r)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:n??r});if(!e||"object"!=typeof e)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:"No data."});const l=Object.keys(a.columns??{}),o=Object.keys(e).filter(t=>Array.isArray(e[t])),s=[];for(const t of l)Array.isArray(e[t])&&s.push(t);for(const e of o)s.includes(e)||s.push(e);if(0===s.length)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:n??"No columnar data in this blob."});const i=s.reduce((t,r)=>Math.min(t,e[r].length),Number.POSITIVE_INFINITY),c=Number.isFinite(i)?i:0,d=Array.from({length:c},(t,r)=>{const a={__i:r};for(const t of s)a[t]=e[t][r];return a}),u=e=>{const t=a.units?.[e];return t?`${e} [${t}]`:e};return _jsxs(DataTable,{value:d,scrollable:!0,scrollHeight:"60vh",virtualScrollerOptions:{itemSize:32},emptyMessage:n??"No data.",size:"small",stripedRows:!0,children:[_jsx(Column,{field:"__i",header:"#",style:{width:"5rem",textAlign:"right"},bodyStyle:{fontVariantNumeric:"tabular-nums",textAlign:"right"}}),s.map(e=>_jsx(Column,{field:e,header:u(e),style:{minWidth:"8rem"},bodyStyle:{fontVariantNumeric:"tabular-nums",textAlign:"right"},body:t=>formatNumeric(t[e])},e))]})},formatNumeric=e=>null==e?"":"number"==typeof e&&Number.isFinite(e)?Number.parseFloat(e.toPrecision(6)).toString():String(e),ResultsGrid=({schema:e,values:t})=>t&&0!==Object.keys(t).length?_jsx("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(220px, 1fr))",gap:"0.5rem 1rem"},children:e.map(e=>_jsxs("div",{children:[_jsxs("div",{style:{fontSize:"0.8em",color:"var(--text-secondary-color)"},children:[e.name,e.units?` (${e.units})`:""]}),_jsx("div",{children:formatCell(t[e.name],e.type,e.scale)})]},e.name))}):_jsx("div",{style:{color:"var(--text-secondary-color)"},children:"No results yet."}),CYCLE_VIRTUAL_THRESHOLD=30,CHART_COLORS=["#4ea8de","#f59e0b","#22c55e","#a855f7","#ef4444","#14b8a6","#eab308","#ec4899"],palette=e=>CHART_COLORS[e%CHART_COLORS.length],SG_ORDER=3,SMOOTH_TARGET_POINTS=800;function invertMatrix(e){const t=e.length,r=e.map((e,r)=>[...e,...Array.from({length:t},(e,t)=>r===t?1:0)]);for(let e=0;e<t;e++){let a=e;for(let n=e+1;n<t;n++)Math.abs(r[n][e])>Math.abs(r[a][e])&&(a=n);if(Math.abs(r[a][e])<1e-12)return null;[r[e],r[a]]=[r[a],r[e]];const n=r[e][e];for(let a=0;a<2*t;a++)r[e][a]/=n;for(let a=0;a<t;a++){if(a===e)continue;const n=r[a][e];if(0!==n)for(let l=0;l<2*t;l++)r[a][l]-=n*r[e][l]}}return r.map(e=>e.slice(t))}function sgWeights(e,t){const r=2*e+1,a=Math.min(t,r-1),n=[];for(let t=-e;t<=e;t++){const e=[];let r=1;for(let n=0;n<=a;n++)e.push(r),r*=t;n.push(e)}const l=a+1,o=Array.from({length:l},()=>new Array(l).fill(0));for(let e=0;e<l;e++)for(let t=0;t<l;t++){let a=0;for(let l=0;l<r;l++)a+=n[l][e]*n[l][t];o[e][t]=a}const s=invertMatrix(o);if(!s)return new Array(r).fill(1/r);const i=new Array(r);for(let e=0;e<r;e++){let t=0;for(let r=0;r<l;r++)t+=s[0][r]*n[e][r];i[e]=t}return i}function savitzkyGolay(e,t){const r=e.length;if(0===r||t<1)return e.slice();const a=new Map,n=e=>{let t=a.get(e);return t||(t=sgWeights(e,3),a.set(e,t)),t},l=new Array(r);for(let a=0;a<r;a++){const o=Math.min(t,a,r-1-a);if(o<1){l[a]=e[a];continue}const s=n(o);let i=0;for(let t=-o;t<=o;t++)i+=s[t+o]*e[a+t];l[a]=i}return l}function smoothTraceForDisplay(e,t,r){const a=Math.min(e.length,t.length);if(0===a)return[];const n=r&&r>1?Math.max(1,Math.floor((Math.min(r,a)-1)/2)):Math.max(2,Math.min(40,Math.round(a/250))),l=savitzkyGolay(t.slice(0,a),n),o=Math.max(1,Math.ceil(a/SMOOTH_TARGET_POINTS)),s=[];for(let t=0;t<a;t+=o)s.push({x:e[t],y:l[t]});return(a-1)%o!=0&&s.push({x:e[a-1],y:l[a-1]}),s}const DEFAULT_REGION_FILL="rgba(78, 168, 222, 0.15)",REGION_LABEL_COLOR="rgba(226, 232, 240, 0.85)";function buildRegionAnnotations(e){if(!e||0===e.length)return{};const t={};return e.forEach((e,r)=>{const a=!!e.borderColor;t[`region-${r}`]={type:"box",xScaleID:"x",xMin:e.xMin,xMax:e.xMax,backgroundColor:e.color??DEFAULT_REGION_FILL,borderColor:a?e.borderColor:"transparent",borderWidth:a?1:0,drawTime:"beforeDatasetsDraw",...e.label?{label:{display:!0,content:e.label,position:{x:"center",y:"start"},color:REGION_LABEL_COLOR,font:{size:11}}}:{}}}),t}const ChartOverlay=({children:e})=>_jsx("div",{style:{position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",color:"var(--text-secondary-color)",pointerEvents:"none"},children:e}),seriesLabel=e=>e.label??e.field??e.column??"",leftAxisLabel=e=>e?.y.filter(e=>"right"!==e.y_axis).map(seriesLabel).join(" / ")??"",rightAxisLabel=e=>e?.y.filter(e=>"right"===e.y_axis).map(seriesLabel).join(" / ")??"",formatCell=(e,t,r)=>null==e?"":(r&&1!==r&&"number"==typeof e&&Number.isFinite(e)&&(e*=r),"f32"===t||"f64"===t?"number"==typeof e?e.toFixed(4):String(e):"object"==typeof e?JSON.stringify(e):String(e));
1
+ import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import React,{useCallback,useContext,useEffect,useMemo,useRef,useState}from"react";import{Button}from"primereact/button";import{Column}from"primereact/column";import{DataTable}from"primereact/datatable";import{Dialog}from"primereact/dialog";import{Dropdown}from"primereact/dropdown";import{TabView,TabPanel}from"primereact/tabview";import{Chart as ChartJS,CategoryScale,LinearScale,PointElement,LineElement,Title,Tooltip,Legend}from"chart.js";import zoomPlugin from"chartjs-plugin-zoom";import annotationPlugin from"chartjs-plugin-annotation";import{Line}from"react-chartjs-2";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";import{useRawCycleData}from"./useRawCycleData";ChartJS.register(CategoryScale,LinearScale,PointElement,LineElement,Title,Tooltip,Legend,zoomPlugin,annotationPlugin);export const TestDataView=e=>{const t=useTis(),r=e.projectId??t.selection.projectId,a=e.methodId??t.selection.methodId,n=e.runId??t.selection.runId,l=e.schema??(a?t.schemas[a]:void 0),{throttleMs:o=100,cycleTableHeight:s="400px",chartHeight:i="320px"}=e,{invoke:c,subscribe:d,unsubscribe:u}=useContext(EventEmitterContext),[m,p]=useState(null),[y,h]=useState([]),[f,g]=useState({}),[x,_]=useState(!1),[b,j]=useState(!1),v=useRef(null),[w,C]=useState(null),[S,R]=useState(null),[T,M]=useState(!1),[L,I]=useState(null),[O,A]=useState(null),[D,E]=useState(!1),[N,k]=useState([]),[F,H]=useState(null),z=useMemo(()=>{const e=[];for(const[t,r]of Object.entries(l?.views??{}))e.push({name:t,view:r});return e},[l]),[P,$]=useState(z.length>0?z[0].name:null);useEffect(()=>{if(0===z.length)return;null!==P&&z.some(e=>e.name===P)||$(z[0].name)},[z,P]);const B=z.find(e=>e.name===P)?.view,V="raw_trace"===B?.type,G=useRef([]),W=useRef(null),q=useRef(null),U=()=>{q.current||(q.current=setTimeout(()=>{if(q.current=null,G.current.length>0){const e=G.current;G.current=[],h(t=>[...e.slice().reverse(),...t])}W.current&&(g(W.current),W.current=null)},o))};useEffect(()=>{if(!r||!a||!n)return p(null),h([]),void g({});let e=!1;return(async()=>{try{const t=await c("tis.read_test",MessageType.Request,{project_id:r,method_id:a,run_id:n});!e&&t?.success&&(p(t.data),g(t.data.results??{}));const l=await c("tis.read_cycles",MessageType.Request,{project_id:r,method_id:a,run_id:n,offset:0,limit:200,order:"desc"});!e&&l?.success&&h(l.data.cycles??[])}catch(e){}})(),()=>{e=!0}},[r,a,n,c]),useEffect(()=>{const e=e=>e?.project_id===r&&e?.method_id===a&&e?.run_id===n,t=d("tis.cycle_added",t=>{e(t)&&t.cycle&&(G.current.push(t.cycle),U())}),l=d("tis.results_updated",t=>{e(t)&&(W.current=t.results??{},U())});return()=>{u(t),u(l),q.current&&(clearTimeout(q.current),q.current=null)}},[r,a,n,o]);const J=useRawCycleData({projectId:r,methodId:a,runId:n,blobName:l?.raw_data?.blob_name??"trace",source:B?.source??"raw",enabled:V}),Y=useMemo(()=>{if(!B)return null;if("cycle_scatter"===B.type){const e=B.x.field;if(!e)return null;const t=[...y].reverse();return{labels:t.map(t=>t[e]),datasets:B.y.map((e,r)=>({label:e.label??e.field,data:t.map(t=>t[e.field]),yAxisID:"right"===e.y_axis?"y1":"y",borderColor:palette(r),backgroundColor:palette(r),tension:.1,pointRadius:2}))}}if("raw_trace"===B.type){if(!J.raw)return null;const e=B.x.column;if(!e)return null;const t=J.raw[e]??[],r=!1!==B.smooth;return{datasets:B.y.map((e,a)=>{const n=J.raw[e.column]??[],l=r?smoothTraceForDisplay(t,n,B.smoothWindow):n.map((e,r)=>({x:t[r],y:e}));return{label:e.label??e.column,data:l,yAxisID:"right"===e.y_axis?"y1":"y",borderColor:palette(a),backgroundColor:palette(a),pointRadius:0,borderWidth:1.5,showLine:!0,...r?{cubicInterpolationMode:"monotone"}:{}}})}}return null},[B,y,J.raw]),Z=B?.y.some(e=>"right"===e.y_axis)??!1,K=useMemo(()=>{if(!V)return[];const e=J.envelope,t=e?.regions??e?.context?.regions;return Array.isArray(t)?t:[]},[V,J.envelope]),Q=useMemo(()=>[...B?.regions??[],...K],[B,K]),X=useMemo(()=>{const e="raw_trace"===B?.type;return{responsive:!0,maintainAspectRatio:!1,parsing:!e&&void 0,scales:{x:e?{type:"linear",title:{display:!!B?.x.label,text:B?.x.label}}:{title:{display:!!B?.x.label,text:B?.x.label}},y:{position:"left",title:{display:!0,text:leftAxisLabel(B)}},...Z?{y1:{position:"right",grid:{drawOnChartArea:!1},title:{display:!0,text:rightAxisLabel(B)}}}:{}},plugins:{legend:{display:!0},zoom:{pan:{enabled:!0,mode:"xy"},zoom:{wheel:{enabled:!0},pinch:{enabled:!0},mode:"xy"}},annotation:{annotations:buildRegionAnnotations(Q)}}}},[B,Z,Q]),ee=l?.raw_data?.blob_name??"trace",te=useRef(""),re=useCallback(async()=>{if(!r||!a||!n)return[];try{const e=await c("tis.list_raw",MessageType.Request,{project_id:r,method_id:a,run_id:n});if(!e?.success)return[];const t=e.data?.cycles??[];return t.filter(e=>e?.name===ee&&"number"==typeof e?.cycle_index).map(e=>e.cycle_index).sort((e,t)=>e-t)}catch{return[]}},[r,a,n,ee,c]),ae=useCallback(async e=>{if(!r||!a||!n)return;const t=`${r}|${a}|${n}|${ee}|${e??"latest"}`;if(te.current===t)return;te.current=t;const l={project_id:r,method_id:a,run_id:n,name:ee};null!=e&&(l.cycle_index=e),M(!0),R(null),C(null);try{const e=await c("tis.read_raw",MessageType.Request,l);e?.success?C(e.data??{}):R(e?.error_message??"No raw data on disk for this run.")}catch(e){R(String(e?.message??e))}finally{M(!1)}E(!0),A(null),I(null);try{const e=await c("tis.read_filtered",MessageType.Request,l);e?.success?I(e.data??{}):A(e?.error_message??"No filtered data on disk for this run.")}catch(e){A(String(e?.message??e))}finally{E(!1)}},[r,a,n,ee,c]);useEffect(()=>{te.current="",k([]),H(null)},[r,a,n,ee]);return useEffect(()=>{x&&null!=F&&ae(F)},[x,F,ae]),r&&a&&n&&l?_jsxs("div",{className:"vblock",style:{display:"flex",flexDirection:"column",gap:"1rem"},children:[_jsx(Header,{meta:m,config:m?.config,runId:n,projectId:r,methodId:a,canViewRaw:!!l.raw_data,onViewRaw:async()=>{_(!0);let e=N;0===e.length&&(e=await re(),k(e));const t=e.length>0?e[e.length-1]:null,r=F??t;F!==r&&H(r),await ae(r)},onShowConfig:()=>j(!0)}),_jsxs("div",{className:"p-card",style:{padding:"1rem"},children:[_jsxs("div",{className:"flex",style:{gap:"1rem",alignItems:"center",marginBottom:"0.5rem",flexWrap:"wrap"},children:[_jsx(Dropdown,{value:P,options:z.map(e=>({label:e.view.title??e.name,value:e.name})),onChange:e=>$(e.value),placeholder:0===z.length?"No view defined":"Select a view",disabled:0===z.length}),_jsx("h3",{style:{margin:0},children:B?.title??""}),V&&J.cycles.length>1&&_jsxs(_Fragment,{children:[_jsx("label",{htmlFor:"chart-cycle-picker",style:{color:"var(--text-secondary-color)"},children:"Cycle:"}),_jsx(Dropdown,{inputId:"chart-cycle-picker",value:J.selectedCycle,options:J.cycles.map(e=>({label:`Cycle ${e}`,value:e})),onChange:e=>J.setSelectedCycle(Number(e.value)),style:{minWidth:"8rem"}}),_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:["of ",J.cycles.length]})]}),_jsx("div",{style:{flex:1}}),_jsx(Button,{icon:"pi pi-th-large",outlined:!0,rounded:!0,size:"small",onClick:()=>v.current?.resetZoom?.(),disabled:!Y,tooltip:"Reset chart zoom",tooltipOptions:{position:"left"},"aria-label":"Reset chart zoom"})]}),_jsxs("div",{style:{height:i,position:"relative"},children:[V&&J.loading&&_jsx(ChartOverlay,{children:"Loading raw data…"}),V&&J.error&&_jsx(ChartOverlay,{children:J.error}),Y&&_jsx(Line,{ref:v,data:Y,options:X})]})]}),_jsxs("div",{className:"p-card",style:{padding:"1rem"},children:[_jsxs("h3",{style:{marginTop:0},children:["Cycle Data (",y.length,")"]}),(()=>{const e=y.length>CYCLE_VIRTUAL_THRESHOLD;return _jsx(DataTable,{value:y,scrollable:e,scrollHeight:e?s:void 0,virtualScrollerOptions:e?{itemSize:38}:void 0,emptyMessage:"No cycles yet.",children:l.cycle_fields.map(e=>_jsx(Column,{field:e.name,header:e.units?`${e.name} (${e.units})`:e.name,body:t=>formatCell(t[e.name],e.type,e.scale)},e.name))})})()]}),_jsxs("div",{className:"p-card",style:{padding:"1rem"},children:[_jsx("h3",{style:{marginTop:0},children:"Results"}),_jsx(ResultsGrid,{schema:l.results_fields,values:f})]}),l.raw_data&&_jsxs(Dialog,{visible:x,onHide:()=>_(!1),header:`Run Data — ${n}`,style:{width:"90vw",height:"80vh"},maximizable:!0,children:[_jsx(CyclePickerBar,{cycles:N,selected:F,onChange:H}),_jsx(RawEnvelopeHeader,{envelope:w}),_jsxs(TabView,{style:{height:"100%"},children:[_jsx(TabPanel,{header:"Raw Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(w),loading:T,error:S,rawData:l.raw_data})}),_jsx(TabPanel,{header:"Filtered Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(L),loading:D,error:O,rawData:l.raw_data,emptyMessage:"Filtered data is written by post-processing — none on disk for this run yet."})})]})]}),_jsx(Dialog,{visible:b,onHide:()=>j(!1),header:"Test Configuration",style:{width:"min(640px, 90vw)"},modal:!0,children:_jsx(ConfigList,{config:m?.config})})]}):_jsx("div",{className:"p-card",style:{padding:"1rem",color:"var(--text-secondary-color)"},children:"No test selected. Pick a row from the History tab or start a run."})};const Header=({meta:e,config:t,runId:r,projectId:a,methodId:n,canViewRaw:l,onViewRaw:o,onShowConfig:s})=>{const i="string"==typeof e?.sample_id&&e.sample_id||"string"==typeof e?.config?.sample_id&&e.config.sample_id||"",c=t&&"object"==typeof t&&Object.entries(t).some(([e])=>"sample_id"!==e);return _jsx("div",{className:"p-card",style:{padding:"1rem"},children:_jsxs("div",{className:"flex",style:{justifyContent:"space-between",alignItems:"flex-start",gap:"1rem"},children:[_jsxs("div",{children:[_jsxs("h2",{style:{margin:0,display:"flex",alignItems:"center",gap:"0.5rem"},children:[i||r,c&&_jsx(Button,{icon:"pi pi-info-circle",type:"button",rounded:!0,text:!0,onClick:s,tooltip:"Show test configuration",tooltipOptions:{position:"top"},style:{width:"2rem",height:"2rem",padding:0},"aria-label":"Show test configuration"})]}),_jsxs("div",{style:{color:"var(--text-secondary-color)",fontSize:"0.85em"},children:["project: ",a," · method: ",n," · run: ",r,e?.start_time&&_jsxs(_Fragment,{children:[" · started: ",new Date(e.start_time).toLocaleString()]})]})]}),l&&_jsx(Button,{icon:"pi pi-table",label:"View Raw Data",onClick:o,outlined:!0})]})})},ConfigList=({config:e})=>{const t=e&&"object"==typeof e?Object.entries(e).filter(([e])=>"sample_id"!==e):[];return 0===t.length?_jsx("div",{style:{color:"var(--text-secondary-color)"},children:"No configuration recorded for this run."}):_jsx("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr",gap:"0.5rem 1rem",fontSize:"0.95em"},children:t.map(([e,t])=>_jsxs(React.Fragment,{children:[_jsx("div",{style:{color:"var(--text-secondary-color)"},children:e}),_jsx("div",{children:formatCell(t,"string")})]},e))})},unwrapEnvelope=e=>e&&"object"==typeof e&&"data"in e&&e.data&&"object"==typeof e.data&&Object.values(e.data).some(e=>Array.isArray(e))?e.data:e,CyclePickerBar=({cycles:e,selected:t,onChange:r})=>e.length<=1?null:_jsxs("div",{style:{display:"flex",alignItems:"center",gap:"0.5rem",padding:"0.25rem 0.5rem 0.5rem"},children:[_jsx("label",{htmlFor:"raw-cycle-picker",style:{color:"var(--text-secondary-color)"},children:"Cycle:"}),_jsx(Dropdown,{inputId:"raw-cycle-picker",value:t,options:e.map(e=>({label:`Cycle ${e}`,value:e})),onChange:e=>r(Number(e.value)),style:{minWidth:"8rem"}}),_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:["(",e.length," cycles recorded)"]})]}),RawEnvelopeHeader=({envelope:e})=>{if(!e||"object"!=typeof e)return null;const t=e.cycle_index,r=e.cycle_fields,a=e.context;if(null==t&&!r&&!a)return null;const n=e=>{if(!e||"object"!=typeof e)return null;const t=Object.entries(e).filter(([,e])=>null!==e&&"object"!=typeof e);return 0===t.length?null:t.map(([e,t])=>_jsxs("span",{style:{marginRight:"1rem"},children:[_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:[e,": "]}),_jsx("span",{children:String(t)})]},e))};return _jsxs("div",{style:{padding:"0.5rem",borderBottom:"1px solid var(--surface-border)",fontSize:"0.9rem"},children:[null!=t&&_jsx("div",{children:_jsxs("strong",{children:["Cycle ",t]})}),r&&_jsx("div",{style:{marginTop:"0.25rem"},children:n(r)}),a&&_jsx("div",{style:{marginTop:"0.25rem"},children:n(a)})]})},DataBlobTable=({blob:e,loading:t,error:r,rawData:a,emptyMessage:n})=>{if(t)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:"Loading…"});if(r)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:n??r});if(!e||"object"!=typeof e)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:"No data."});const l=Object.keys(a.columns??{}),o=Object.keys(e).filter(t=>Array.isArray(e[t])),s=[];for(const t of l)Array.isArray(e[t])&&s.push(t);for(const e of o)s.includes(e)||s.push(e);if(0===s.length)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:n??"No columnar data in this blob."});const i=s.reduce((t,r)=>Math.min(t,e[r].length),Number.POSITIVE_INFINITY),c=Number.isFinite(i)?i:0,d=Array.from({length:c},(t,r)=>{const a={__i:r};for(const t of s)a[t]=e[t][r];return a}),u=e=>{const t=a.units?.[e];return t?`${e} [${t}]`:e};return _jsxs(DataTable,{value:d,scrollable:!0,scrollHeight:"60vh",virtualScrollerOptions:{itemSize:32},emptyMessage:n??"No data.",size:"small",stripedRows:!0,children:[_jsx(Column,{field:"__i",header:"#",style:{width:"5rem",textAlign:"right"},bodyStyle:{fontVariantNumeric:"tabular-nums",textAlign:"right"}}),s.map(e=>_jsx(Column,{field:e,header:u(e),style:{minWidth:"8rem"},bodyStyle:{fontVariantNumeric:"tabular-nums",textAlign:"right"},body:t=>formatNumeric(t[e])},e))]})},formatNumeric=e=>null==e?"":"number"==typeof e&&Number.isFinite(e)?Number.parseFloat(e.toPrecision(6)).toString():String(e),ResultsGrid=({schema:e,values:t})=>t&&0!==Object.keys(t).length?_jsx("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(220px, 1fr))",gap:"0.5rem 1rem"},children:e.map(e=>_jsxs("div",{children:[_jsxs("div",{style:{fontSize:"0.8em",color:"var(--text-secondary-color)"},children:[e.name,e.units?` (${e.units})`:""]}),_jsx("div",{children:formatCell(t[e.name],e.type,e.scale)})]},e.name))}):_jsx("div",{style:{color:"var(--text-secondary-color)"},children:"No results yet."}),CYCLE_VIRTUAL_THRESHOLD=30,CHART_COLORS=["#4ea8de","#f59e0b","#22c55e","#a855f7","#ef4444","#14b8a6","#eab308","#ec4899"],palette=e=>CHART_COLORS[e%CHART_COLORS.length],SG_ORDER=3,SMOOTH_TARGET_POINTS=800;function invertMatrix(e){const t=e.length,r=e.map((e,r)=>[...e,...Array.from({length:t},(e,t)=>r===t?1:0)]);for(let e=0;e<t;e++){let a=e;for(let n=e+1;n<t;n++)Math.abs(r[n][e])>Math.abs(r[a][e])&&(a=n);if(Math.abs(r[a][e])<1e-12)return null;[r[e],r[a]]=[r[a],r[e]];const n=r[e][e];for(let a=0;a<2*t;a++)r[e][a]/=n;for(let a=0;a<t;a++){if(a===e)continue;const n=r[a][e];if(0!==n)for(let l=0;l<2*t;l++)r[a][l]-=n*r[e][l]}}return r.map(e=>e.slice(t))}function sgWeights(e,t){const r=2*e+1,a=Math.min(t,r-1),n=[];for(let t=-e;t<=e;t++){const e=[];let r=1;for(let n=0;n<=a;n++)e.push(r),r*=t;n.push(e)}const l=a+1,o=Array.from({length:l},()=>new Array(l).fill(0));for(let e=0;e<l;e++)for(let t=0;t<l;t++){let a=0;for(let l=0;l<r;l++)a+=n[l][e]*n[l][t];o[e][t]=a}const s=invertMatrix(o);if(!s)return new Array(r).fill(1/r);const i=new Array(r);for(let e=0;e<r;e++){let t=0;for(let r=0;r<l;r++)t+=s[0][r]*n[e][r];i[e]=t}return i}function savitzkyGolay(e,t){const r=e.length;if(0===r||t<1)return e.slice();const a=new Map,n=e=>{let t=a.get(e);return t||(t=sgWeights(e,3),a.set(e,t)),t},l=new Array(r);for(let a=0;a<r;a++){const o=Math.min(t,a,r-1-a);if(o<1){l[a]=e[a];continue}const s=n(o);let i=0;for(let t=-o;t<=o;t++)i+=s[t+o]*e[a+t];l[a]=i}return l}function smoothTraceForDisplay(e,t,r){const a=Math.min(e.length,t.length);if(0===a)return[];const n=r&&r>1?Math.max(1,Math.floor((Math.min(r,a)-1)/2)):Math.max(2,Math.min(40,Math.round(a/250))),l=savitzkyGolay(t.slice(0,a),n),o=Math.max(1,Math.ceil(a/SMOOTH_TARGET_POINTS)),s=[];for(let t=0;t<a;t+=o)s.push({x:e[t],y:l[t]});return(a-1)%o!=0&&s.push({x:e[a-1],y:l[a-1]}),s}const DEFAULT_REGION_FILL="rgba(78, 168, 222, 0.15)",REGION_LABEL_COLOR="rgba(226, 232, 240, 0.85)";function buildRegionAnnotations(e){if(!e||0===e.length)return{};const t={};return e.forEach((e,r)=>{const a=!!e.borderColor;t[`region-${r}`]={type:"box",xScaleID:"x",xMin:e.xMin,xMax:e.xMax,backgroundColor:e.color??DEFAULT_REGION_FILL,borderColor:a?e.borderColor:"transparent",borderWidth:a?1:0,drawTime:"beforeDatasetsDraw",...e.label?{label:{display:!0,content:e.label,position:{x:"center",y:"start"},color:REGION_LABEL_COLOR,font:{size:11}}}:{}}}),t}const ChartOverlay=({children:e})=>_jsx("div",{style:{position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",color:"var(--text-secondary-color)",pointerEvents:"none"},children:e}),seriesLabel=e=>e.label??e.field??e.column??"",leftAxisLabel=e=>e?.y.filter(e=>"right"!==e.y_axis).map(seriesLabel).join(" / ")??"",rightAxisLabel=e=>e?.y.filter(e=>"right"===e.y_axis).map(seriesLabel).join(" / ")??"",formatCell=(e,t,r)=>null==e?"":(r&&1!==r&&"number"==typeof e&&Number.isFinite(e)&&(e*=r),"f32"===t||"f64"===t?"number"==typeof e?e.toFixed(4):String(e):"object"==typeof e?JSON.stringify(e):String(e));
@@ -1 +1 @@
1
- {"version":3,"file":"TestRawDataView.d.ts","sourceRoot":"","sources":["../../../src/components/tis/TestRawDataView.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAoC,MAAM,OAAO,CAAC;AAWzD,OAAO,KAAK,EAAa,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAS5D,MAAM,WAAW,oBAAoB;IACjC,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,QAAQ,CAAC,EAAG,MAAM,CAAC;IACnB,gEAAgE;IAChE,KAAK,CAAC,EAAM,MAAM,CAAC;IACnB,kEAAkE;IAClE,MAAM,CAAC,EAAK,UAAU,CAAC;IACvB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAoJ1D,CAAC"}
1
+ {"version":3,"file":"TestRawDataView.d.ts","sourceRoot":"","sources":["../../../src/components/tis/TestRawDataView.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAoC,MAAM,OAAO,CAAC;AAWzD,OAAO,KAAK,EAAa,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAS5D,MAAM,WAAW,oBAAoB;IACjC,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,QAAQ,CAAC,EAAG,MAAM,CAAC;IACnB,gEAAgE;IAChE,KAAK,CAAC,EAAM,MAAM,CAAC;IACnB,kEAAkE;IAClE,MAAM,CAAC,EAAK,UAAU,CAAC;IACvB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAqJ1D,CAAC"}
@@ -1 +1 @@
1
- import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import React,{useMemo,useRef,useState}from"react";import{Button}from"primereact/button";import{Dropdown}from"primereact/dropdown";import{Chart as ChartJS,CategoryScale,LinearScale,PointElement,LineElement,Title,Tooltip,Legend}from"chart.js";import zoomPlugin from"chartjs-plugin-zoom";import{Line}from"react-chartjs-2";import{useTis}from"./TisProvider";import{useRawCycleData}from"./useRawCycleData";ChartJS.register(CategoryScale,LinearScale,PointElement,LineElement,Title,Tooltip,Legend,zoomPlugin);export const TestRawDataView=e=>{const t=useTis(),r=e.projectId??t.selection.projectId,l=e.methodId??t.selection.methodId,a=e.runId??t.selection.runId,o=e.schema??(l?t.schemas[l]:void 0),{blobName:s,chartHeight:n="60vh"}=e,i=useRef(null),c=useMemo(()=>{const e=[];for(const[t,r]of Object.entries(o?.views??{}))"raw_trace"===r.type&&e.push({name:t,view:r});return e},[o]),[d,m]=useState(c.length>0?c[0].name:null),p=s??o?.raw_data?.blob_name??"trace",{cycles:y,selectedCycle:x,setSelectedCycle:h,raw:u,envelope:g,loading:j,error:f}=useRawCycleData({projectId:r,methodId:l,runId:a,blobName:p,enabled:!!r&&!!l&&!!a}),v=useMemo(()=>{if(!u||!d)return null;const e=c.find(e=>e.name===d)?.view;if(!e)return null;const t=e.x.column,r=u[t]??[];return{datasets:e.y.map((e,t)=>({label:e.label??e.column,data:(u[e.column]??[]).map((e,t)=>({x:r[t],y:e})),yAxisID:"right"===e.y_axis?"y1":"y",borderColor:palette(t),backgroundColor:palette(t),pointRadius:0,borderWidth:1.5,showLine:!0}))}},[u,d,c]),_=c.find(e=>e.name===d)?.view,b=_?.y.some(e=>"right"===e.y_axis)??!1,w=useMemo(()=>({responsive:!0,maintainAspectRatio:!1,parsing:!1,scales:{x:{type:"linear",title:{display:!!_?.x.label,text:_?.x.label}},y:{position:"left",title:{display:!0,text:axisLabel(_,"left")}},...b?{y1:{position:"right",grid:{drawOnChartArea:!1},title:{display:!0,text:axisLabel(_,"right")}}}:{}},plugins:{legend:{display:!0},zoom:{pan:{enabled:!0,mode:"xy"},zoom:{wheel:{enabled:!0},pinch:{enabled:!0},drag:{enabled:!0,modifierKey:"shift"},mode:"xy"}}}}),[_,b]);return r&&l&&a?o?o.raw_data?0===c.length?_jsx(EmptyState,{message:"No raw_trace views declared. Add one to schema.views in project.json."}):_jsxs("div",{className:"vblock",style:{display:"flex",flexDirection:"column",gap:"1rem",height:"100%"},children:[_jsxs("div",{className:"flex",style:{gap:"1rem",alignItems:"center",flexWrap:"wrap"},children:[_jsx(Dropdown,{value:d,options:c.map(e=>({label:e.view.title??e.name,value:e.name})),onChange:e=>m(e.value),placeholder:"Select a view"}),_jsx("h3",{style:{margin:0},children:_?.title??""}),y.length>1&&_jsxs(_Fragment,{children:[_jsx("label",{htmlFor:"rawview-cycle-picker",style:{color:"var(--text-secondary-color)"},children:"Cycle:"}),_jsx(Dropdown,{inputId:"rawview-cycle-picker",value:x,options:y.map(e=>({label:`Cycle ${e}`,value:e})),onChange:e=>h(Number(e.value)),style:{minWidth:"8rem"}}),_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:["of ",y.length]})]}),_jsx("div",{style:{flex:1}}),_jsx(Button,{icon:"pi pi-refresh",label:"Reset Zoom",outlined:!0,onClick:()=>i.current?.resetZoom?.()})]}),_jsx(EnvelopeMetaStrip,{envelope:g}),_jsxs("div",{style:{flex:1,minHeight:0,height:n,position:"relative"},children:[j&&_jsx(Overlay,{children:"Loading raw data…"}),f&&_jsx(Overlay,{children:f}),v&&!j&&!f&&_jsx(Line,{ref:i,data:v,options:w})]})]}):_jsx(EmptyState,{message:"No raw_data is declared for this test method."}):_jsx(EmptyState,{message:"Schema not loaded yet."}):_jsx(EmptyState,{message:"No test selected."})};const Overlay=({children:e})=>_jsx("div",{style:{position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",color:"var(--text-secondary-color)",pointerEvents:"none"},children:e}),EmptyState=({message:e})=>_jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:e}),EnvelopeMetaStrip=({envelope:e})=>{if(!e||"object"!=typeof e)return null;const t=e.cycle_index,r=e.cycle_fields,l=e.context;if(null==t&&!r&&!l)return null;const a=e=>e&&"object"==typeof e?Object.entries(e).filter(([,e])=>null!==e&&"object"!=typeof e).map(([e,t])=>_jsxs("span",{style:{marginRight:"1rem"},children:[_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:[e,": "]}),_jsx("span",{children:String(t)})]},e)):null;return _jsxs("div",{style:{padding:"0.5rem 0.25rem",fontSize:"0.9rem",borderTop:"1px solid var(--surface-border)",borderBottom:"1px solid var(--surface-border)"},children:[null!=t&&_jsx("div",{children:_jsxs("strong",{children:["Cycle ",t]})}),r&&_jsx("div",{style:{marginTop:"0.25rem"},children:a(r)}),l&&_jsx("div",{style:{marginTop:"0.25rem"},children:a(l)})]})},CHART_COLORS=["#4ea8de","#f59e0b","#22c55e","#a855f7","#ef4444","#14b8a6","#eab308","#ec4899"],palette=e=>CHART_COLORS[e%CHART_COLORS.length],axisLabel=(e,t)=>e?.y.filter(e=>(e.y_axis??"left")===t).map(e=>e.label??e.column).join(" / ")??"";
1
+ import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import React,{useMemo,useRef,useState}from"react";import{Button}from"primereact/button";import{Dropdown}from"primereact/dropdown";import{Chart as ChartJS,CategoryScale,LinearScale,PointElement,LineElement,Title,Tooltip,Legend}from"chart.js";import zoomPlugin from"chartjs-plugin-zoom";import{Line}from"react-chartjs-2";import{useTis}from"./TisProvider";import{useRawCycleData}from"./useRawCycleData";ChartJS.register(CategoryScale,LinearScale,PointElement,LineElement,Title,Tooltip,Legend,zoomPlugin);export const TestRawDataView=e=>{const t=useTis(),r=e.projectId??t.selection.projectId,l=e.methodId??t.selection.methodId,a=e.runId??t.selection.runId,o=e.schema??(l?t.schemas[l]:void 0),{blobName:s,chartHeight:n="60vh"}=e,i=useRef(null),c=useMemo(()=>{const e=[];for(const[t,r]of Object.entries(o?.views??{}))"raw_trace"===r.type&&e.push({name:t,view:r});return e},[o]),[d,m]=useState(c.length>0?c[0].name:null),p=s??o?.raw_data?.blob_name??"trace",y=c.find(e=>e.name===d)?.view,{cycles:x,selectedCycle:h,setSelectedCycle:u,raw:g,envelope:j,loading:f,error:v}=useRawCycleData({projectId:r,methodId:l,runId:a,blobName:p,source:y?.source??"raw",enabled:!!r&&!!l&&!!a}),_=useMemo(()=>{if(!g||!d)return null;const e=c.find(e=>e.name===d)?.view;if(!e)return null;const t=e.x.column,r=g[t]??[];return{datasets:e.y.map((e,t)=>({label:e.label??e.column,data:(g[e.column]??[]).map((e,t)=>({x:r[t],y:e})),yAxisID:"right"===e.y_axis?"y1":"y",borderColor:palette(t),backgroundColor:palette(t),pointRadius:0,borderWidth:1.5,showLine:!0}))}},[g,d,c]),b=y?.y.some(e=>"right"===e.y_axis)??!1,w=useMemo(()=>({responsive:!0,maintainAspectRatio:!1,parsing:!1,scales:{x:{type:"linear",title:{display:!!y?.x.label,text:y?.x.label}},y:{position:"left",title:{display:!0,text:axisLabel(y,"left")}},...b?{y1:{position:"right",grid:{drawOnChartArea:!1},title:{display:!0,text:axisLabel(y,"right")}}}:{}},plugins:{legend:{display:!0},zoom:{pan:{enabled:!0,mode:"xy"},zoom:{wheel:{enabled:!0},pinch:{enabled:!0},drag:{enabled:!0,modifierKey:"shift"},mode:"xy"}}}}),[y,b]);return r&&l&&a?o?o.raw_data?0===c.length?_jsx(EmptyState,{message:"No raw_trace views declared. Add one to schema.views in project.json."}):_jsxs("div",{className:"vblock",style:{display:"flex",flexDirection:"column",gap:"1rem",height:"100%"},children:[_jsxs("div",{className:"flex",style:{gap:"1rem",alignItems:"center",flexWrap:"wrap"},children:[_jsx(Dropdown,{value:d,options:c.map(e=>({label:e.view.title??e.name,value:e.name})),onChange:e=>m(e.value),placeholder:"Select a view"}),_jsx("h3",{style:{margin:0},children:y?.title??""}),x.length>1&&_jsxs(_Fragment,{children:[_jsx("label",{htmlFor:"rawview-cycle-picker",style:{color:"var(--text-secondary-color)"},children:"Cycle:"}),_jsx(Dropdown,{inputId:"rawview-cycle-picker",value:h,options:x.map(e=>({label:`Cycle ${e}`,value:e})),onChange:e=>u(Number(e.value)),style:{minWidth:"8rem"}}),_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:["of ",x.length]})]}),_jsx("div",{style:{flex:1}}),_jsx(Button,{icon:"pi pi-refresh",label:"Reset Zoom",outlined:!0,onClick:()=>i.current?.resetZoom?.()})]}),_jsx(EnvelopeMetaStrip,{envelope:j}),_jsxs("div",{style:{flex:1,minHeight:0,height:n,position:"relative"},children:[f&&_jsx(Overlay,{children:"Loading raw data…"}),v&&_jsx(Overlay,{children:v}),_&&!f&&!v&&_jsx(Line,{ref:i,data:_,options:w})]})]}):_jsx(EmptyState,{message:"No raw_data is declared for this test method."}):_jsx(EmptyState,{message:"Schema not loaded yet."}):_jsx(EmptyState,{message:"No test selected."})};const Overlay=({children:e})=>_jsx("div",{style:{position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",color:"var(--text-secondary-color)",pointerEvents:"none"},children:e}),EmptyState=({message:e})=>_jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:e}),EnvelopeMetaStrip=({envelope:e})=>{if(!e||"object"!=typeof e)return null;const t=e.cycle_index,r=e.cycle_fields,l=e.context;if(null==t&&!r&&!l)return null;const a=e=>e&&"object"==typeof e?Object.entries(e).filter(([,e])=>null!==e&&"object"!=typeof e).map(([e,t])=>_jsxs("span",{style:{marginRight:"1rem"},children:[_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:[e,": "]}),_jsx("span",{children:String(t)})]},e)):null;return _jsxs("div",{style:{padding:"0.5rem 0.25rem",fontSize:"0.9rem",borderTop:"1px solid var(--surface-border)",borderBottom:"1px solid var(--surface-border)"},children:[null!=t&&_jsx("div",{children:_jsxs("strong",{children:["Cycle ",t]})}),r&&_jsx("div",{style:{marginTop:"0.25rem"},children:a(r)}),l&&_jsx("div",{style:{marginTop:"0.25rem"},children:a(l)})]})},CHART_COLORS=["#4ea8de","#f59e0b","#22c55e","#a855f7","#ef4444","#14b8a6","#eab308","#ec4899"],palette=e=>CHART_COLORS[e%CHART_COLORS.length],axisLabel=(e,t)=>e?.y.filter(e=>(e.y_axis??"left")===t).map(e=>e.label??e.column).join(" / ")??"";
@@ -4,6 +4,12 @@ export interface UseRawCycleDataOptions {
4
4
  runId?: string;
5
5
  /** Blob name (e.g. "trace"); usually schema.raw_data.blob_name. */
6
6
  blobName: string;
7
+ /** Which columnar blob family to fetch: 'raw' (default —
8
+ * tis.list_raw / tis.read_raw / tis.raw_data_added) or 'filtered'
9
+ * (tis.list_filtered / tis.read_filtered / tis.filtered_data_added).
10
+ * Wire to the selected view's `source` so a `raw_trace` view with
11
+ * `source: "filtered"` charts the post-filter trace. */
12
+ source?: 'raw' | 'filtered';
7
13
  /** When false, the hook does no fetching and returns empty state.
8
14
  * Wire to "is the current chart view a raw_trace?" so scatter
9
15
  * selections don't trigger a blob round-trip. */
@@ -1 +1 @@
1
- {"version":3,"file":"useRawCycleData.d.ts","sourceRoot":"","sources":["../../../src/components/tis/useRawCycleData.ts"],"names":[],"mappings":"AAqCA,MAAM,WAAW,sBAAsB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAG,MAAM,CAAC;IACnB,KAAK,CAAC,EAAM,MAAM,CAAC;IACnB,mEAAmE;IACnE,QAAQ,EAAI,MAAM,CAAC;IACnB;;sDAEkD;IAClD,OAAO,EAAK,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IAClC;kEAC8D;IAC9D,MAAM,EAAY,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAK,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC7C;4EACwE;IACxE,GAAG,EAAe,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC;IAClD;yEACqE;IACrE,QAAQ,EAAU,GAAG,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAW,OAAO,CAAC;IAC1B,KAAK,EAAa,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,qBAAqB,CAgMnF;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAOlE"}
1
+ {"version":3,"file":"useRawCycleData.d.ts","sourceRoot":"","sources":["../../../src/components/tis/useRawCycleData.ts"],"names":[],"mappings":"AAsCA,MAAM,WAAW,sBAAsB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAG,MAAM,CAAC;IACnB,KAAK,CAAC,EAAM,MAAM,CAAC;IACnB,mEAAmE;IACnE,QAAQ,EAAI,MAAM,CAAC;IACnB;;;;6DAIyD;IACzD,MAAM,CAAC,EAAK,KAAK,GAAG,UAAU,CAAC;IAC/B;;sDAEkD;IAClD,OAAO,EAAK,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IAClC;kEAC8D;IAC9D,MAAM,EAAY,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAK,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC7C;4EACwE;IACxE,GAAG,EAAe,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC;IAClD;yEACqE;IACrE,QAAQ,EAAU,GAAG,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAW,OAAO,CAAC;IAC1B,KAAK,EAAa,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,qBAAqB,CA0MnF;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAOlE"}
@@ -1 +1 @@
1
- import{useCallback,useContext,useEffect,useRef,useState}from"react";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";export function useRawCycleData(e){const{projectId:t,methodId:r,runId:n,blobName:u,enabled:a}=e,{invoke:s,subscribe:l,unsubscribe:c}=useContext(EventEmitterContext),[i,o]=useState([]),[d,f]=useState(null),[m,y]=useState(null),[_,p]=useState(null),[b,g]=useState(!1),[h,E]=useState(null),[w,C]=useState(0),x=useRef(!1),v=useCallback(async()=>{if(!t||!r||!n)return[];try{const e=await s("tis.list_raw",MessageType.Request,{project_id:t,method_id:r,run_id:n});if(!e?.success)return[];return(e.data?.cycles??[]).filter(e=>e?.name===u&&"number"==typeof e?.cycle_index).map(e=>e.cycle_index).sort((e,t)=>e-t)}catch{return[]}},[t,r,n,u,s]);useEffect(()=>{o([]),f(null),y(null),p(null),E(null),x.current=!1},[t,r,n,u]),useEffect(()=>{if(!(a&&t&&r&&n))return;let e=!1;return(async()=>{const t=await v();e||(o(t),t.length>0&&f(e=>e??t[t.length-1]))})(),()=>{e=!0}},[a,t,r,n,u,v]);const S=useRef(i);S.current=i;const j=useRef(d);j.current=d,useEffect(()=>{if(!(a&&t&&r&&n))return;const e=l("tis.raw_data_added",async e=>{if(e?.project_id!==t)return;if(e?.method_id!==r)return;if(e?.run_id!==n)return;if(e?.name!==u)return;const a=await v();if(0===a.length)return;o(a);const s=S.current.length>0?S.current[S.current.length-1]:null,l=a[a.length-1];!x.current&&(null===j.current||j.current===s)&&l!==j.current&&f(l)}),s=l("tis.chart_regions_set",e=>{e?.project_id===t&&e?.method_id===r&&e?.run_id===n&&e?.name===u&&e?.cycle_index===j.current&&C(e=>e+1)});return()=>{c(e),c(s)}},[a,t,r,n,u,v,l,c]),useEffect(()=>{if(!(a&&t&&r&&n))return y(null),p(null),g(!1),void E(null);if(null==d)return y(null),p(null),g(!1),void E(null);let e=!1;return g(!0),E(null),(async()=>{try{const a=await s("tis.read_raw",MessageType.Request,{project_id:t,method_id:r,run_id:n,name:u,cycle_index:d});if(e)return;if(a?.success){const e=a.data??{};p(e),y(unwrapEnvelope(e))}else E(a?.error_message??"Failed to read raw data")}catch(t){e||E(String(t?.message??t))}finally{e||g(!1)}})(),()=>{e=!0}},[a,t,r,n,u,d,s,w]);const R=useCallback(e=>{x.current=null!=e,f(e)},[]);return{cycles:i,selectedCycle:d,setSelectedCycle:R,raw:m,envelope:_,loading:b,error:h}}export function unwrapEnvelope(e){return e&&"object"==typeof e?"data"in e&&e.data&&"object"==typeof e.data&&Object.values(e.data).some(e=>Array.isArray(e))?e.data:e:{}}
1
+ import{useCallback,useContext,useEffect,useRef,useState}from"react";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";export function useRawCycleData(e){const{projectId:t,methodId:r,runId:n,blobName:u,enabled:l,source:a="raw"}=e,{invoke:s,subscribe:c,unsubscribe:i}=useContext(EventEmitterContext),o="filtered"===a?"tis.list_filtered":"tis.list_raw",d="filtered"===a?"tis.read_filtered":"tis.read_raw",f="filtered"===a?"tis.filtered_data_added":"tis.raw_data_added",[_,m]=useState([]),[y,p]=useState(null),[b,g]=useState(null),[h,w]=useState(null),[E,C]=useState(!1),[x,v]=useState(null),[S,j]=useState(0),R=useRef(!1),k=useCallback(async()=>{if(!t||!r||!n)return[];try{const e=await s(o,MessageType.Request,{project_id:t,method_id:r,run_id:n});if(!e?.success)return[];return(e.data?.cycles??[]).filter(e=>e?.name===u&&"number"==typeof e?.cycle_index).map(e=>e.cycle_index).sort((e,t)=>e-t)}catch{return[]}},[t,r,n,u,o,s]);useEffect(()=>{m([]),p(null),g(null),w(null),v(null),R.current=!1},[t,r,n,u,a]),useEffect(()=>{if(!(l&&t&&r&&n))return;let e=!1;return(async()=>{const t=await k();e||(m(t),t.length>0&&p(e=>e??t[t.length-1]))})(),()=>{e=!0}},[l,t,r,n,u,k]);const M=useRef(_);M.current=_;const I=useRef(y);I.current=y,useEffect(()=>{if(!(l&&t&&r&&n))return;const e=c(f,async e=>{if(e?.project_id!==t)return;if(e?.method_id!==r)return;if(e?.run_id!==n)return;if(e?.name!==u)return;const l=await k();if(0===l.length)return;m(l);const a=M.current.length>0?M.current[M.current.length-1]:null,s=l[l.length-1];!R.current&&(null===I.current||I.current===a)&&s!==I.current&&p(s)}),s="raw"===a?c("tis.chart_regions_set",e=>{e?.project_id===t&&e?.method_id===r&&e?.run_id===n&&e?.name===u&&e?.cycle_index===I.current&&j(e=>e+1)}):null;return()=>{i(e),null!=s&&i(s)}},[l,t,r,n,u,a,f,k,c,i]),useEffect(()=>{if(!(l&&t&&r&&n))return g(null),w(null),C(!1),void v(null);if(null==y)return g(null),w(null),C(!1),void v(null);let e=!1;return C(!0),v(null),(async()=>{try{const l=await s(d,MessageType.Request,{project_id:t,method_id:r,run_id:n,name:u,cycle_index:y});if(e)return;if(l?.success){const e=l.data??{};w(e),g(unwrapEnvelope(e))}else v(l?.error_message??`Failed to read ${a} data`)}catch(t){e||v(String(t?.message??t))}finally{e||C(!1)}})(),()=>{e=!0}},[l,t,r,n,u,a,d,y,s,S]);const T=useCallback(e=>{R.current=null!=e,p(e)},[]);return{cycles:_,selectedCycle:y,setSelectedCycle:T,raw:b,envelope:h,loading:E,error:x}}export function unwrapEnvelope(e){return e&&"object"==typeof e?"data"in e&&e.data&&"object"==typeof e.data&&Object.values(e.data).some(e=>Array.isArray(e))?e.data:e:{}}
@@ -1 +1 @@
1
- {"version":3,"file":"TisConfigEditor.d.ts","sourceRoot":"","sources":["../../../src/components/tis-editor/TisConfigEditor.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAM5E,OAAO,uBAAuB,CAAC;AAE/B,MAAM,WAAW,oBAAoB;IACjC;+EAC2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,OAAO,CAAC,EAAE,aAAa,CAAC;CAC3B;AAkBD,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAoQ1D,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"TisConfigEditor.d.ts","sourceRoot":"","sources":["../../../src/components/tis-editor/TisConfigEditor.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAM5E,OAAO,uBAAuB,CAAC;AAE/B,MAAM,WAAW,oBAAoB;IACjC;+EAC2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,OAAO,CAAC,EAAE,aAAa,CAAC;CAC3B;AAkBD,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAyQ1D,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -1 +1 @@
1
- import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import{useEffect,useMemo,useState}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Button}from"primereact/button";import{InputText}from"primereact/inputtext";import{Dialog}from"primereact/dialog";import{useContext}from"react";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTisConfig}from"../../hooks/useTisConfig";import{useAmsAssetTypes}from"../../hooks/useAmsAssetTypes";import{MethodFormEditor}from"./editor/MethodFormEditor";import{SaveDiffDialog}from"./editor/SaveDiffDialog";import"./TisConfigEditor.css";const EMPTY_METHOD={label:"",description:"",project_fields:[],config_fields:[],cycle_fields:[],results_fields:[],views:{},asset_refs:[]};export const TisConfigEditor=({projectId:e,invoker:t})=>{const s=useContext(EventEmitterContext),i=t??(async(e,t)=>await s.invoke(e,MessageType.Request,t)),o=useTisConfig(e,{invoker:i}),a=useAmsAssetTypes({invoker:i}),[n,r]=useState(null),[l,d]=useState(null),[c,m]=useState(!1),[f,p]=useState(!1),[h,u]=useState(!1),[_,g]=useState(""),x=useMemo(()=>o.config?Object.entries(o.config.methods).map(([e,t])=>({id:e,label:t?.label??""})):[],[o.config]);useEffect(()=>{if(!n&&o.config){const e=o.config.defaultMethodId||Object.keys(o.config.methods)[0]||null;r(e)}},[o.config,n]),useEffect(()=>{d(null)},[n]);return _jsxs("div",{className:"tis-editor",children:[_jsxs("header",{className:"tis-editor__header",children:[_jsxs("h2",{children:["Test Methods"," ",o.config?.dirty&&_jsx("span",{className:"tis-editor__dirty-pill",children:"unsaved"})]}),_jsxs("div",{className:"tis-editor__header-actions",children:[_jsx(Button,{label:"Save…",icon:"pi pi-save",disabled:c||!o.config?.dirty,onClick:()=>p(!0)}),_jsx(Button,{label:"Revert",icon:"pi pi-undo",className:"p-button-secondary",disabled:c||!o.config?.dirty,onClick:async()=>{if(window.confirm("Discard all in-progress edits? This cannot be undone.")){m(!0);try{await o.revert()}catch(e){d(String(e?.message??e))}finally{m(!1)}}}})]})]}),o.error&&_jsxs("div",{className:"tis-editor__error",children:[_jsx("strong",{children:"Error:"})," ",_jsx("pre",{children:o.error})]}),_jsxs("div",{className:"tis-editor__body",children:[_jsxs("aside",{className:"tis-editor__sidebar",children:[_jsxs("div",{className:"tis-editor__sidebar-actions",children:[_jsx(Button,{label:"New",icon:"pi pi-plus",disabled:c,onClick:()=>u(!0)}),_jsx(Button,{label:"Duplicate",icon:"pi pi-clone",className:"p-button-secondary",disabled:c||!n,onClick:async()=>{if(!n||!o.config)return;const e=o.config.methods[n];if(!e)return;let t=`${n}_copy`,s=2;for(;o.config.methods[t];)t=`${n}_copy_${s++}`;m(!0);try{await o.putMethod(t,JSON.parse(JSON.stringify(e))),r(t)}catch(e){d(String(e?.message??e))}finally{m(!1)}}}),_jsx(Button,{label:"Delete",icon:"pi pi-trash",className:"p-button-danger",disabled:c||!n,onClick:async()=>{if(n&&window.confirm(`Remove method "${n}"? This is staged — Save persists it.`)){m(!0);try{await o.removeMethod(n),r(null)}catch(e){d(String(e?.message??e))}finally{m(!1)}}}})]}),_jsxs(DataTable,{value:x,selection:x.find(e=>e.id===n)??null,onSelectionChange:e=>r(e.value?.id??null),selectionMode:"single",dataKey:"id",scrollable:!0,scrollHeight:"flex",emptyMessage:o.loading?"Loading…":"No test methods defined.",children:[_jsx(Column,{field:"id",header:"Method ID"}),_jsx(Column,{field:"label",header:"Label"})]})]}),_jsx("section",{className:"tis-editor__detail",children:n&&o.config?.methods[n]?_jsxs(_Fragment,{children:[l&&_jsx("div",{className:"tis-editor__error",children:_jsx("pre",{children:l})}),_jsx(MethodFormEditor,{methodId:n,method:o.config.methods[n],onApply:async e=>{if(n){m(!0);try{await o.putMethod(n,e),d(null)}catch(e){d(String(e?.message??e))}finally{m(!1)}}},busy:c,knownAssetTypes:a.types})]}):_jsx("div",{className:"tis-editor__empty",children:"Select a test method on the left, or create a new one."})})]}),_jsxs(Dialog,{header:"New Test Method",visible:h,onHide:()=>u(!1),style:{width:"24rem"},children:[_jsxs("label",{className:"tis-editor__new-method-label",children:["Method ID",_jsx(InputText,{value:_,onChange:e=>g(e.target.value),placeholder:"e.g. translational_traction",autoFocus:!0})]}),_jsx("small",{children:"Canonical key — appears in wire payloads, on-disk paths, and generated code."}),_jsxs("div",{style:{display:"flex",gap:"0.5rem",justifyContent:"flex-end",marginTop:"1rem"},children:[_jsx(Button,{label:"Cancel",className:"p-button-text",onClick:()=>u(!1)}),_jsx(Button,{label:"Create",disabled:!_.trim()||c,onClick:async()=>{const e=_.trim();if(e)if(o.config?.methods[e])d(`A method named "${e}" already exists.`);else{m(!0);try{await o.putMethod(e,EMPTY_METHOD),r(e),u(!1),g("")}catch(e){d(String(e?.message??e))}finally{m(!1)}}}})]})]}),_jsx(SaveDiffDialog,{visible:f,staged:o.config?.methods??{},invoker:i,onConfirm:async()=>{m(!0);try{await o.save(),p(!1)}catch(e){d(String(e?.message??e))}finally{m(!1)}},onCancel:()=>p(!1)})]})};export default TisConfigEditor;
1
+ import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import{useEffect,useMemo,useState}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Button}from"primereact/button";import{InputText}from"primereact/inputtext";import{Dialog}from"primereact/dialog";import{useContext}from"react";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTisConfig}from"../../hooks/useTisConfig";import{useAmsAssetTypes}from"../../hooks/useAmsAssetTypes";import{MethodFormEditor}from"./editor/MethodFormEditor";import{SaveDiffDialog}from"./editor/SaveDiffDialog";import"./TisConfigEditor.css";const EMPTY_METHOD={label:"",description:"",project_fields:[],config_fields:[],cycle_fields:[],results_fields:[],views:{},asset_refs:[]};export const TisConfigEditor=({projectId:e,invoker:t})=>{const s=useContext(EventEmitterContext),i=useMemo(()=>t??(async(e,t)=>await s.invoke(e,MessageType.Request,t)),[t,s]),o=useTisConfig(e,{invoker:i}),a=useAmsAssetTypes({invoker:i}),[n,r]=useState(null),[l,d]=useState(null),[c,m]=useState(!1),[f,p]=useState(!1),[h,u]=useState(!1),[_,g]=useState(""),x=useMemo(()=>o.config?Object.entries(o.config.methods).map(([e,t])=>({id:e,label:t?.label??""})):[],[o.config]);useEffect(()=>{if(!n&&o.config){const e=o.config.defaultMethodId||Object.keys(o.config.methods)[0]||null;r(e)}},[o.config,n]),useEffect(()=>{d(null)},[n]);return _jsxs("div",{className:"tis-editor",children:[_jsxs("header",{className:"tis-editor__header",children:[_jsxs("h2",{children:["Test Methods"," ",o.config?.dirty&&_jsx("span",{className:"tis-editor__dirty-pill",children:"unsaved"})]}),_jsxs("div",{className:"tis-editor__header-actions",children:[_jsx(Button,{label:"Save…",icon:"pi pi-save",disabled:c||!o.config?.dirty,onClick:()=>p(!0)}),_jsx(Button,{label:"Revert",icon:"pi pi-undo",className:"p-button-secondary",disabled:c||!o.config?.dirty,onClick:async()=>{if(window.confirm("Discard all in-progress edits? This cannot be undone.")){m(!0);try{await o.revert()}catch(e){d(String(e?.message??e))}finally{m(!1)}}}})]})]}),o.error&&_jsxs("div",{className:"tis-editor__error",children:[_jsx("strong",{children:"Error:"})," ",_jsx("pre",{children:o.error})]}),_jsxs("div",{className:"tis-editor__body",children:[_jsxs("aside",{className:"tis-editor__sidebar",children:[_jsxs("div",{className:"tis-editor__sidebar-actions",children:[_jsx(Button,{label:"New",icon:"pi pi-plus",disabled:c,onClick:()=>u(!0)}),_jsx(Button,{label:"Duplicate",icon:"pi pi-clone",className:"p-button-secondary",disabled:c||!n,onClick:async()=>{if(!n||!o.config)return;const e=o.config.methods[n];if(!e)return;let t=`${n}_copy`,s=2;for(;o.config.methods[t];)t=`${n}_copy_${s++}`;m(!0);try{await o.putMethod(t,JSON.parse(JSON.stringify(e))),r(t)}catch(e){d(String(e?.message??e))}finally{m(!1)}}}),_jsx(Button,{label:"Delete",icon:"pi pi-trash",className:"p-button-danger",disabled:c||!n,onClick:async()=>{if(n&&window.confirm(`Remove method "${n}"? This is staged — Save persists it.`)){m(!0);try{await o.removeMethod(n),r(null)}catch(e){d(String(e?.message??e))}finally{m(!1)}}}})]}),_jsxs(DataTable,{value:x,selection:x.find(e=>e.id===n)??null,onSelectionChange:e=>r(e.value?.id??null),selectionMode:"single",dataKey:"id",scrollable:!0,scrollHeight:"flex",emptyMessage:o.loading?"Loading…":"No test methods defined.",children:[_jsx(Column,{field:"id",header:"Method ID"}),_jsx(Column,{field:"label",header:"Label"})]})]}),_jsx("section",{className:"tis-editor__detail",children:n&&o.config?.methods[n]?_jsxs(_Fragment,{children:[l&&_jsx("div",{className:"tis-editor__error",children:_jsx("pre",{children:l})}),_jsx(MethodFormEditor,{methodId:n,method:o.config.methods[n],onApply:async e=>{if(n){m(!0);try{await o.putMethod(n,e),d(null)}catch(e){d(String(e?.message??e))}finally{m(!1)}}},busy:c,knownAssetTypes:a.types})]}):_jsx("div",{className:"tis-editor__empty",children:"Select a test method on the left, or create a new one."})})]}),_jsxs(Dialog,{header:"New Test Method",visible:h,onHide:()=>u(!1),style:{width:"24rem"},children:[_jsxs("label",{className:"tis-editor__new-method-label",children:["Method ID",_jsx(InputText,{value:_,onChange:e=>g(e.target.value),placeholder:"e.g. translational_traction",autoFocus:!0})]}),_jsx("small",{children:"Canonical key — appears in wire payloads, on-disk paths, and generated code."}),_jsxs("div",{style:{display:"flex",gap:"0.5rem",justifyContent:"flex-end",marginTop:"1rem"},children:[_jsx(Button,{label:"Cancel",className:"p-button-text",onClick:()=>u(!1)}),_jsx(Button,{label:"Create",disabled:!_.trim()||c,onClick:async()=>{const e=_.trim();if(e)if(o.config?.methods[e])d(`A method named "${e}" already exists.`);else{m(!0);try{await o.putMethod(e,EMPTY_METHOD),r(e),u(!1),g("")}catch(e){d(String(e?.message??e))}finally{m(!1)}}}})]})]}),_jsx(SaveDiffDialog,{visible:f,staged:o.config?.methods??{},invoker:i,onConfirm:async()=>{m(!0);try{await o.save(),p(!1)}catch(e){d(String(e?.message??e))}finally{m(!1)}},onCancel:()=>p(!1)})]})};export default TisConfigEditor;
@@ -1 +1 @@
1
- {"version":3,"file":"useTisConfig.d.ts","sourceRoot":"","sources":["../../src/hooks/useTisConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH;8EAC8E;AAC9E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD,MAAM,WAAW,SAAS;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACpC,KAAK,EAAE,OAAO,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;CAC3B;AAED;wFACwF;AACxF,MAAM,WAAW,aAAa;IAC1B,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QACtC,OAAO,EAAE,OAAO,CAAC;QACjB,IAAI,CAAC,EAAE,GAAG,CAAC;QACX,aAAa,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC,CAAC;CACN;AAED,MAAM,WAAW,kBAAkB;IAC/B,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,mFAAmF;IACnF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAChC,OAAO,CAAC,EAAE,aAAa,CAAC;CAC3B;AAED,wBAAgB,YAAY,CACxB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,mBAAmB,GAC9B,kBAAkB,CAqGpB"}
1
+ {"version":3,"file":"useTisConfig.d.ts","sourceRoot":"","sources":["../../src/hooks/useTisConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH;8EAC8E;AAC9E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD,MAAM,WAAW,SAAS;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACpC,KAAK,EAAE,OAAO,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;CAC3B;AAED;wFACwF;AACxF,MAAM,WAAW,aAAa;IAC1B,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QACtC,OAAO,EAAE,OAAO,CAAC;QACjB,IAAI,CAAC,EAAE,GAAG,CAAC;QACX,aAAa,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC,CAAC;CACN;AAED,MAAM,WAAW,kBAAkB;IAC/B,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,mFAAmF;IACnF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAChC,OAAO,CAAC,EAAE,aAAa,CAAC;CAC3B;AAED,wBAAgB,YAAY,CACxB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,mBAAmB,GAC9B,kBAAkB,CAgHpB"}
@@ -1 +1 @@
1
- import{useCallback,useContext,useEffect,useState}from"react";import{EventEmitterContext}from"../core/EventEmitterContext";import{MessageType}from"../hub/CommandMessage";export function useTisConfig(e,t){const s=useContext(EventEmitterContext),o=t?.invoker??(async(e,t)=>await s.invoke(e,MessageType.Request,t)),[a,r]=useState(null),[i,n]=useState(!0),[c,d]=useState(null),l=useCallback(async()=>{n(!0);try{const t=await o("tis.show_config",{project_id:e});if(!t.success)return d(t.error_message??"tis.show_config failed"),void r(null);const s=t.data??{};r({projectId:e,methods:s.test_methods??{},dirty:!!s.dirty,defaultMethodId:"string"==typeof s.default_method_id?s.default_method_id:""}),d(null)}catch(e){d(String(e?.message??e)),r(null)}finally{n(!1)}},[e,o]);useEffect(()=>{l()},[l]);const u=useCallback(async(t,s)=>{const a=await o("tis.put_method",{project_id:e,method_id:t,method:s});if(!a.success){const e=a.error_message??"tis.put_method failed";throw d(e),new Error(e)}d(null),await l()},[o,e,l]),f=useCallback(async t=>{const s=await o("tis.remove_method",{project_id:e,method_id:t});if(!s.success){const e=s.error_message??"tis.remove_method failed";throw d(e),new Error(e)}d(null),await l()},[o,e,l]),m=useCallback(async()=>{const t=await o("tis.save_config",{project_id:e});if(!t.success){const e=t.error_message??"tis.save_config failed";throw d(e),new Error(e)}d(null),await l()},[o,e,l]),_=useCallback(async()=>{const t=await o("tis.discard_config_changes",{project_id:e});if(!t.success){const e=t.error_message??"tis.discard_config_changes failed";throw d(e),new Error(e)}d(null),await l()},[o,e,l]);return{config:a,loading:i,error:c,refresh:l,putMethod:u,removeMethod:f,save:m,revert:_}}
1
+ import{useCallback,useContext,useEffect,useRef,useState}from"react";import{EventEmitterContext}from"../core/EventEmitterContext";import{MessageType}from"../hub/CommandMessage";export function useTisConfig(e,t){const s=useContext(EventEmitterContext),r=t?.invoker??(async(e,t)=>await s.invoke(e,MessageType.Request,t)),o=useRef(r);o.current=r;const[a,n]=useState(null),[i,c]=useState(!0),[u,d]=useState(null),l=useCallback(async()=>{c(!0);try{const t=await o.current("tis.show_config",{project_id:e});if(!t.success)return d(t.error_message??"tis.show_config failed"),void n(null);const s=t.data??{};n({projectId:e,methods:s.test_methods??{},dirty:!!s.dirty,defaultMethodId:"string"==typeof s.default_method_id?s.default_method_id:""}),d(null)}catch(e){d(String(e?.message??e)),n(null)}finally{c(!1)}},[e]);useEffect(()=>{l()},[l]);const f=useCallback(async(t,s)=>{const r=await o.current("tis.put_method",{project_id:e,method_id:t,method:s});if(!r.success){const e=r.error_message??"tis.put_method failed";throw d(e),new Error(e)}d(null),await l()},[e,l]),m=useCallback(async t=>{const s=await o.current("tis.remove_method",{project_id:e,method_id:t});if(!s.success){const e=s.error_message??"tis.remove_method failed";throw d(e),new Error(e)}d(null),await l()},[e,l]),_=useCallback(async()=>{const t=await o.current("tis.save_config",{project_id:e});if(!t.success){const e=t.error_message??"tis.save_config failed";throw d(e),new Error(e)}d(null),await l()},[e,l]),h=useCallback(async()=>{const t=await o.current("tis.discard_config_changes",{project_id:e});if(!t.success){const e=t.error_message??"tis.discard_config_changes failed";throw d(e),new Error(e)}d(null),await l()},[e,l]);return{config:a,loading:i,error:u,refresh:l,putMethod:f,removeMethod:m,save:_,revert:h}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adcops/autocore-react",
3
- "version": "3.3.100",
3
+ "version": "3.3.105",
4
4
  "description": "A React component library for industrial user interfaces.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -40,6 +40,12 @@ interface DiskUsage {
40
40
  used_bytes: number;
41
41
  }
42
42
 
43
+ interface ProjectSize {
44
+ project_id: string;
45
+ total_bytes: number;
46
+ file_count: number;
47
+ }
48
+
43
49
  const formatBytes = (n: number): string => {
44
50
  if (!Number.isFinite(n) || n <= 0) return '0 B';
45
51
  const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
@@ -67,6 +73,8 @@ export const ProjectManager: React.FC<ProjectManagerProps> = (props) => {
67
73
  const [archiving, setArchiving] = useState(false);
68
74
  const [disk, setDisk] = useState<DiskUsage | null>(null);
69
75
  const [diskError, setDiskError] = useState<string>('');
76
+ const [projSize, setProjSize] = useState<ProjectSize | null>(null);
77
+ const [projSizeError, setProjSizeError] = useState<string>('');
70
78
 
71
79
  const loadTests = useCallback(async () => {
72
80
  if (!projectId) { setTests([]); return; }
@@ -104,8 +112,30 @@ export const ProjectManager: React.FC<ProjectManagerProps> = (props) => {
104
112
  }
105
113
  }, [invoke]);
106
114
 
115
+ const loadProjectSize = useCallback(async () => {
116
+ if (!projectId) { setProjSize(null); setProjSizeError(''); return; }
117
+ try {
118
+ const resp: any = await invoke('tis.project_size' as any, MessageType.Request, {
119
+ project_id: projectId,
120
+ } as any);
121
+ if (resp?.success && resp.data) {
122
+ setProjSize(resp.data as ProjectSize);
123
+ setProjSizeError('');
124
+ } else {
125
+ setProjSize(null);
126
+ setProjSizeError(resp?.error_message ?? 'project_size failed');
127
+ }
128
+ } catch (e) {
129
+ setProjSize(null);
130
+ setProjSizeError(e instanceof Error ? e.message : String(e));
131
+ }
132
+ }, [projectId, invoke]);
133
+
107
134
  useEffect(() => { void loadTests(); }, [loadTests, tis.state.activeRunId]);
108
135
  useEffect(() => { void loadDisk(); }, [loadDisk]);
136
+ // Re-measure the project footprint when the project changes or a run is
137
+ // added/removed (activeRunId flips on start/finish; delete calls it too).
138
+ useEffect(() => { void loadProjectSize(); }, [loadProjectSize, tis.state.activeRunId]);
109
139
 
110
140
  const performDeleteTest = async (rowData: any) => {
111
141
  const runId = rowData?.run_id;
@@ -122,6 +152,7 @@ export const ProjectManager: React.FC<ProjectManagerProps> = (props) => {
122
152
  return;
123
153
  }
124
154
  await loadTests();
155
+ void loadProjectSize(); // footprint shrank — re-measure
125
156
  // Pinned selection may now point at a vanished run; clear it so
126
157
  // sibling views fall back to the active scalars instead of
127
158
  // staying parked on a deleted record.
@@ -297,7 +328,7 @@ export const ProjectManager: React.FC<ProjectManagerProps> = (props) => {
297
328
  icon="pi pi-refresh"
298
329
  label="Refresh"
299
330
  size="small"
300
- onClick={() => { void loadTests(); void loadDisk(); }}
331
+ onClick={() => { void loadTests(); void loadDisk(); void loadProjectSize(); }}
301
332
  disabled={loading}
302
333
  />
303
334
  </div>
@@ -340,6 +371,41 @@ export const ProjectManager: React.FC<ProjectManagerProps> = (props) => {
340
371
  </div>
341
372
  </>
342
373
  )}
374
+
375
+ {/* This project's on-disk footprint — so the operator can see
376
+ when one project's test data (raw/filtered traces) is the
377
+ thing filling the disk above, before it gets excessive. */}
378
+ {projectId && (
379
+ <div style={{
380
+ display: 'flex',
381
+ justifyContent: 'space-between',
382
+ alignItems: 'baseline',
383
+ flexWrap: 'wrap',
384
+ gap: '0.5rem',
385
+ marginTop: '0.6rem',
386
+ paddingTop: '0.6rem',
387
+ borderTop: '1px solid #2a2a2a',
388
+ }}>
389
+ <span style={{ fontSize: '0.875rem' }}>
390
+ This project on disk{disk && projSize && disk.total_bytes > 0
391
+ ? ` (${(() => {
392
+ const p = (projSize.total_bytes / disk.total_bytes) * 100;
393
+ return p > 0 && p < 1 ? '<1%' : `${Math.round(p)}%`;
394
+ })()} of disk)`
395
+ : ''}
396
+ </span>
397
+ {projSize ? (
398
+ <span style={{ fontSize: '0.875rem', color: '#9ca3af' }}>
399
+ {formatBytes(projSize.total_bytes)}
400
+ {' '}({projSize.file_count.toLocaleString()} file{projSize.file_count === 1 ? '' : 's'})
401
+ </span>
402
+ ) : projSizeError ? (
403
+ <span style={{ fontSize: '0.875rem', color: '#f87171' }}>{projSizeError}</span>
404
+ ) : (
405
+ <span style={{ fontSize: '0.875rem', color: '#9ca3af' }}>Loading…</span>
406
+ )}
407
+ </div>
408
+ )}
343
409
  </div>
344
410
 
345
411
  <DataTable
@@ -96,6 +96,13 @@ export interface ChartRegion {
96
96
  export interface ChartView {
97
97
  title?: string;
98
98
  type: 'cycle_scatter' | 'raw_trace';
99
+ /**
100
+ * Which columnar blob a `raw_trace` view charts: `'raw'` (default —
101
+ * the trace written by `tis.add_raw_data`) or `'filtered'` (the
102
+ * post-filter trace written by `tis.add_filtered_data`). Ignored for
103
+ * `cycle_scatter` views.
104
+ */
105
+ source?: 'raw' | 'filtered';
99
106
  x: ChartAxis;
100
107
  y: ChartSeries[];
101
108
  /**
@@ -356,6 +363,7 @@ export const TestDataView: React.FC<TestDataViewProps> = (props) => {
356
363
  const traceFetch = useRawCycleData({
357
364
  projectId, methodId, runId,
358
365
  blobName: traceBlobName,
366
+ source: selectedViewDef?.source ?? 'raw',
359
367
  enabled: isRawTraceView,
360
368
  });
361
369
 
@@ -549,15 +557,15 @@ export const TestDataView: React.FC<TestDataViewProps> = (props) => {
549
557
  // Filtered is optional. The 'no filtered data' case is the
550
558
  // common one (only the post-processing pipeline writes it),
551
559
  // and we render a friendly message rather than an error tone.
552
- // Filtered files are not per-cycle (yet); cycle_index is
553
- // ignored on the filtered side.
560
+ // Filtered files are now per-cycle (parallel to raw_data), so we
561
+ // pass the same baseArgs (incl. cycle_index) and the server returns
562
+ // the filtered trace for the selected cycle.
554
563
  setFilteredLoading(true);
555
564
  setFilteredError(null);
556
565
  setFilteredBlob(null);
557
566
  try {
558
567
  const resp: any = await invoke(
559
- 'tis.read_filtered' as any, MessageType.Request,
560
- { project_id: projectId, method_id: methodId, run_id: runId, name: blobName } as any,
568
+ 'tis.read_filtered' as any, MessageType.Request, baseArgs as any,
561
569
  );
562
570
  if (resp?.success) {
563
571
  setFilteredBlob(resp.data ?? {});
@@ -68,6 +68,7 @@ export const TestRawDataView: React.FC<TestRawDataViewProps> = (props) => {
68
68
  );
69
69
 
70
70
  const effectiveBlobName = blobName ?? schema?.raw_data?.blob_name ?? 'trace';
71
+ const selectedViewDef = traceViews.find(v => v.name === selectedView)?.view;
71
72
 
72
73
  // Cycle discovery + per-cycle blob fetch live in the shared hook so
73
74
  // <TestDataView>'s unified panel can reuse the exact same fetch path
@@ -76,6 +77,7 @@ export const TestRawDataView: React.FC<TestRawDataViewProps> = (props) => {
76
77
  useRawCycleData({
77
78
  projectId, methodId, runId,
78
79
  blobName: effectiveBlobName,
80
+ source: selectedViewDef?.source ?? 'raw',
79
81
  // This component only renders raw_trace charts, so always
80
82
  // fetch as long as the run identity is in scope. The early
81
83
  // returns below cover the "no test selected" / "no schema"
@@ -105,7 +107,6 @@ export const TestRawDataView: React.FC<TestRawDataViewProps> = (props) => {
105
107
  return { datasets };
106
108
  }, [raw, selectedView, traceViews]);
107
109
 
108
- const selectedViewDef = traceViews.find(v => v.name === selectedView)?.view;
109
110
  const usesRightAxis = selectedViewDef?.y.some(s => s.y_axis === 'right') ?? false;
110
111
 
111
112
  const chartOptions = useMemo(() => ({
@@ -2,6 +2,7 @@
2
2
  * Copyright (C) 2026 Automated Design Corp. All Rights Reserved.
3
3
  *
4
4
  * useRawCycleData — shared hook that fetches `tis.list_raw` + `tis.read_raw`
5
+ * (or, with `source: 'filtered'`, `tis.list_filtered` + `tis.read_filtered`)
5
6
  * for one (project, method, run, blob_name) slice and tracks the
6
7
  * operator's per-cycle selection. Used by:
7
8
  *
@@ -41,6 +42,12 @@ export interface UseRawCycleDataOptions {
41
42
  runId?: string;
42
43
  /** Blob name (e.g. "trace"); usually schema.raw_data.blob_name. */
43
44
  blobName: string;
45
+ /** Which columnar blob family to fetch: 'raw' (default —
46
+ * tis.list_raw / tis.read_raw / tis.raw_data_added) or 'filtered'
47
+ * (tis.list_filtered / tis.read_filtered / tis.filtered_data_added).
48
+ * Wire to the selected view's `source` so a `raw_trace` view with
49
+ * `source: "filtered"` charts the post-filter trace. */
50
+ source?: 'raw' | 'filtered';
44
51
  /** When false, the hook does no fetching and returns empty state.
45
52
  * Wire to "is the current chart view a raw_trace?" so scatter
46
53
  * selections don't trigger a blob round-trip. */
@@ -64,9 +71,15 @@ export interface UseRawCycleDataResult {
64
71
  }
65
72
 
66
73
  export function useRawCycleData(opts: UseRawCycleDataOptions): UseRawCycleDataResult {
67
- const { projectId, methodId, runId, blobName, enabled } = opts;
74
+ const { projectId, methodId, runId, blobName, enabled, source = 'raw' } = opts;
68
75
  const { invoke, subscribe, unsubscribe } = useContext(EventEmitterContext);
69
76
 
77
+ // The raw and filtered blob families have parallel command/broadcast
78
+ // sets; everything below is source-agnostic once these are picked.
79
+ const listCmd = source === 'filtered' ? 'tis.list_filtered' : 'tis.list_raw';
80
+ const readCmd = source === 'filtered' ? 'tis.read_filtered' : 'tis.read_raw';
81
+ const addedTopic = source === 'filtered' ? 'tis.filtered_data_added' : 'tis.raw_data_added';
82
+
70
83
  const [cycles, setCycles] = useState<number[]>([]);
71
84
  const [selectedCycle, setSelectedCycle] = useState<number | null>(null);
72
85
  const [raw, setRaw] = useState<Record<string, number[]> | null>(null);
@@ -96,7 +109,7 @@ export function useRawCycleData(opts: UseRawCycleDataOptions): UseRawCycleDataRe
96
109
  if (!projectId || !methodId || !runId) return [];
97
110
  try {
98
111
  const resp: any = await invoke(
99
- 'tis.list_raw' as any, MessageType.Request as any,
112
+ listCmd as any, MessageType.Request as any,
100
113
  { project_id: projectId, method_id: methodId, run_id: runId } as any,
101
114
  );
102
115
  if (!resp?.success) return [];
@@ -108,7 +121,7 @@ export function useRawCycleData(opts: UseRawCycleDataOptions): UseRawCycleDataRe
108
121
  } catch {
109
122
  return [];
110
123
  }
111
- }, [projectId, methodId, runId, blobName, invoke]);
124
+ }, [projectId, methodId, runId, blobName, listCmd, invoke]);
112
125
 
113
126
  // Reset cycle state when the run identity (or blob) changes — the
114
127
  // new slice has its own cycle list and its own "latest" target.
@@ -121,7 +134,7 @@ export function useRawCycleData(opts: UseRawCycleDataOptions): UseRawCycleDataRe
121
134
  setEnvelope(null);
122
135
  setError(null);
123
136
  userPinnedRef.current = false;
124
- }, [projectId, methodId, runId, blobName]);
137
+ }, [projectId, methodId, runId, blobName, source]);
125
138
 
126
139
  // Cycle-list discovery. Runs ahead of the data fetch so the cycle
127
140
  // picker can render immediately. Filtered to the requested blob name.
@@ -192,10 +205,14 @@ export function useRawCycleData(opts: UseRawCycleDataOptions): UseRawCycleDataRe
192
205
  }
193
206
  };
194
207
 
195
- const id = subscribe('tis.raw_data_added', onRawAdded);
196
- const id2 = subscribe('tis.chart_regions_set', onRegionsSet);
197
- return () => { unsubscribe(id); unsubscribe(id2); };
198
- }, [enabled, projectId, methodId, runId, blobName, listCycles, subscribe, unsubscribe]);
208
+ const id = subscribe(addedTopic as any, onRawAdded);
209
+ // Region bands only exist on raw envelopes; skip the extra
210
+ // subscription (and the resulting refetches) for filtered views.
211
+ const id2 = source === 'raw'
212
+ ? subscribe('tis.chart_regions_set', onRegionsSet)
213
+ : null;
214
+ return () => { unsubscribe(id); if (id2 != null) unsubscribe(id2); };
215
+ }, [enabled, projectId, methodId, runId, blobName, source, addedTopic, listCycles, subscribe, unsubscribe]);
199
216
 
200
217
  // Lazy blob fetch — runs whenever identifiers / selectedCycle change.
201
218
  // Suppressed entirely when no cycle is selected (e.g. test just
@@ -218,7 +235,7 @@ export function useRawCycleData(opts: UseRawCycleDataOptions): UseRawCycleDataRe
218
235
  (async () => {
219
236
  try {
220
237
  const resp: any = await invoke(
221
- 'tis.read_raw' as any, MessageType.Request as any,
238
+ readCmd as any, MessageType.Request as any,
222
239
  {
223
240
  project_id: projectId, method_id: methodId,
224
241
  run_id: runId, name: blobName,
@@ -231,7 +248,7 @@ export function useRawCycleData(opts: UseRawCycleDataOptions): UseRawCycleDataRe
231
248
  setEnvelope(payload);
232
249
  setRaw(unwrapEnvelope(payload));
233
250
  } else {
234
- setError(resp?.error_message ?? 'Failed to read raw data');
251
+ setError(resp?.error_message ?? `Failed to read ${source} data`);
235
252
  }
236
253
  } catch (e: any) {
237
254
  if (!cancelled) setError(String(e?.message ?? e));
@@ -240,7 +257,7 @@ export function useRawCycleData(opts: UseRawCycleDataOptions): UseRawCycleDataRe
240
257
  }
241
258
  })();
242
259
  return () => { cancelled = true; };
243
- }, [enabled, projectId, methodId, runId, blobName, selectedCycle, invoke, refreshNonce]);
260
+ }, [enabled, projectId, methodId, runId, blobName, source, readCmd, selectedCycle, invoke, refreshNonce]);
244
261
 
245
262
  // Public setter wraps setSelectedCycle and flips userPinnedRef so
246
263
  // an operator's manual pick freezes the picker for the rest of the
@@ -58,10 +58,15 @@ const EMPTY_METHOD: TestMethod = {
58
58
 
59
59
  export const TisConfigEditor: React.FC<TisConfigEditorProps> = ({ projectId, invoker }) => {
60
60
  const ctx = useContext(EventEmitterContext);
61
- // Resolve invoker once so it can be passed into the SaveDiffDialog
62
- // alongside the hook's internal use of it.
63
- const effectiveInvoker: TisIpcInvoker = invoker
64
- ?? (async (topic, payload) => await ctx.invoke(topic as any, MessageType.Request, payload as any));
61
+ // Resolve the invoker ONCE and memoize it so it keeps a stable identity
62
+ // across renders. Both useTisConfig and SaveDiffDialog key effects off
63
+ // this invoker; an invoker rebuilt every render drove a refetch loop in
64
+ // the hook that continuously reset the editor draft and wiped edits.
65
+ const effectiveInvoker: TisIpcInvoker = useMemo(
66
+ () => invoker
67
+ ?? (async (topic, payload) => await ctx.invoke(topic as any, MessageType.Request, payload as any)),
68
+ [invoker, ctx],
69
+ );
65
70
 
66
71
  const tis = useTisConfig(projectId, { invoker: effectiveInvoker });
67
72
  const ams = useAmsAssetTypes({ invoker: effectiveInvoker });
@@ -16,7 +16,7 @@
16
16
  * demo and by tests). When omitted, the hook reads `EventEmitterContext`.
17
17
  */
18
18
 
19
- import { useCallback, useContext, useEffect, useState } from 'react';
19
+ import { useCallback, useContext, useEffect, useRef, useState } from 'react';
20
20
  import { EventEmitterContext } from '../core/EventEmitterContext';
21
21
  import { MessageType } from '../hub/CommandMessage';
22
22
 
@@ -70,6 +70,18 @@ export function useTisConfig(
70
70
  return await ctx.invoke(topic as any, MessageType.Request, payload as any);
71
71
  });
72
72
 
73
+ // Hold the invoker in a ref so the memoized callbacks below keep a STABLE
74
+ // identity even when the caller passes a freshly-built invoker on every
75
+ // render (the common case — TisConfigEditor does exactly that). Depending
76
+ // on `invoker` directly made `refresh` change every render, so the mount
77
+ // effect re-ran in an infinite refetch loop: each refetch replaced
78
+ // `config`, which reset MethodFormEditor's draft, silently wiping
79
+ // in-progress edits (deleted views reappeared, Apply never stayed
80
+ // enabled). The ref is refreshed each render so calls still use the latest
81
+ // invoker without destabilising the callbacks.
82
+ const invokerRef = useRef(invoker);
83
+ invokerRef.current = invoker;
84
+
73
85
  const [config, setConfig] = useState<TisConfig | null>(null);
74
86
  const [loading, setLoading] = useState<boolean>(true);
75
87
  const [error, setError] = useState<string | null>(null);
@@ -80,7 +92,7 @@ export function useTisConfig(
80
92
  const refresh = useCallback(async () => {
81
93
  setLoading(true);
82
94
  try {
83
- const resp = await invoker('tis.show_config', { project_id: projectId });
95
+ const resp = await invokerRef.current('tis.show_config', { project_id: projectId });
84
96
  if (!resp.success) {
85
97
  setError(resp.error_message ?? 'tis.show_config failed');
86
98
  setConfig(null);
@@ -102,15 +114,14 @@ export function useTisConfig(
102
114
  } finally {
103
115
  setLoading(false);
104
116
  }
105
- // eslint-disable-next-line react-hooks/exhaustive-deps
106
- }, [projectId, invoker]);
117
+ }, [projectId]);
107
118
 
108
119
  useEffect(() => {
109
120
  void refresh();
110
121
  }, [refresh]);
111
122
 
112
123
  const putMethod = useCallback(async (methodId: string, method: TestMethod) => {
113
- const resp = await invoker('tis.put_method', {
124
+ const resp = await invokerRef.current('tis.put_method', {
114
125
  project_id: projectId,
115
126
  method_id: methodId,
116
127
  method,
@@ -122,10 +133,10 @@ export function useTisConfig(
122
133
  }
123
134
  setError(null);
124
135
  await refresh();
125
- }, [invoker, projectId, refresh]);
136
+ }, [projectId, refresh]);
126
137
 
127
138
  const removeMethod = useCallback(async (methodId: string) => {
128
- const resp = await invoker('tis.remove_method', {
139
+ const resp = await invokerRef.current('tis.remove_method', {
129
140
  project_id: projectId,
130
141
  method_id: methodId,
131
142
  });
@@ -136,10 +147,10 @@ export function useTisConfig(
136
147
  }
137
148
  setError(null);
138
149
  await refresh();
139
- }, [invoker, projectId, refresh]);
150
+ }, [projectId, refresh]);
140
151
 
141
152
  const save = useCallback(async () => {
142
- const resp = await invoker('tis.save_config', { project_id: projectId });
153
+ const resp = await invokerRef.current('tis.save_config', { project_id: projectId });
143
154
  if (!resp.success) {
144
155
  const m = resp.error_message ?? 'tis.save_config failed';
145
156
  setError(m);
@@ -147,10 +158,10 @@ export function useTisConfig(
147
158
  }
148
159
  setError(null);
149
160
  await refresh();
150
- }, [invoker, projectId, refresh]);
161
+ }, [projectId, refresh]);
151
162
 
152
163
  const revert = useCallback(async () => {
153
- const resp = await invoker('tis.discard_config_changes', { project_id: projectId });
164
+ const resp = await invokerRef.current('tis.discard_config_changes', { project_id: projectId });
154
165
  if (!resp.success) {
155
166
  const m = resp.error_message ?? 'tis.discard_config_changes failed';
156
167
  setError(m);
@@ -158,7 +169,7 @@ export function useTisConfig(
158
169
  }
159
170
  setError(null);
160
171
  await refresh();
161
- }, [invoker, projectId, refresh]);
172
+ }, [projectId, refresh]);
162
173
 
163
174
  return { config, loading, error, refresh, putMethod, removeMethod, save, revert };
164
175
  }