@adcops/autocore-react 3.3.120 → 3.3.123

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.
@@ -5,6 +5,10 @@ export interface TestFieldDef {
5
5
  units?: string;
6
6
  required?: boolean;
7
7
  source?: string;
8
+ /** Operator-facing column header. The tables and the CSV exports both
9
+ * prefer it over `name`; a header reading `cycle_count` is the wire
10
+ * key leaking into a screen that says "Cycles" everywhere else. */
11
+ label?: string;
8
12
  /** Optional display-time scale multiplier. `display = raw * scale`,
9
13
  * default 1.0 = no conversion. Cycle Data and Results panels
10
14
  * apply this when formatting numeric cells. Charts plot raw
@@ -23,6 +27,16 @@ export interface TestFieldDef {
23
27
  * measured columns can never be typed over. Mirrors Rust
24
28
  * `TestField::editable`. */
25
29
  editable?: boolean;
30
+ /** results_fields only: server-derived rollup. `scope` decides whether
31
+ * the value describes this test (default) or every test of this sample
32
+ * in the project — a distinction the table has to make visible, since
33
+ * the two sit side by side. */
34
+ aggregate?: {
35
+ fn: string;
36
+ of?: string;
37
+ from?: 'cycle_fields' | 'results';
38
+ scope?: 'test' | 'project';
39
+ };
26
40
  }
27
41
  export interface ChartAxis {
28
42
  field?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"TestDataView.d.ts","sourceRoot":"","sources":["../../../src/components/tis/TestDataView.tsx"],"names":[],"mappings":"AA0BA,OAAO,KAAwE,MAAM,OAAO,CAAC;AAiC7F,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;IAC3E;;;;iCAI6B;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;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;IAOvB,cAAc,CAAC,EAAE,YAAY,EAAE,CAAC;IAChC,aAAa,CAAC,EAAG,YAAY,EAAE,CAAC;IAChC;;4CAEwC;IACxC,mBAAmB,CAAC,EAAE,YAAY,EAAE,CAAC;IACrC,YAAY,CAAC,EAAI,YAAY,EAAE,CAAC;IAChC,cAAc,CAAC,EAAE,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,CA6sBpD,CAAC"}
1
+ {"version":3,"file":"TestDataView.d.ts","sourceRoot":"","sources":["../../../src/components/tis/TestDataView.tsx"],"names":[],"mappings":"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"}
@@ -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";import{ScienceTable}from"./ScienceTable";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,f]=useState([]),[h,g]=useState({}),[_,x]=useState({}),[b,j]=useState(!1),[v,w]=useState(!1),C=useCallback(async(e,t,l)=>{const o=e?.cycle_index;if(r&&a&&n&&o)try{const e=await c("tis.update_cycle",MessageType.Request,{project_id:r,method_id:a,run_id:n,cycle_index:o,fields:{[t]:l}});if(!1===e?.success)return;f(e=>e.map(e=>e?.cycle_index===o?{...e,[t]:l}:e))}catch(e){}},[c,r,a,n]),S=useRef(null),[T,R]=useState(null),[M,L]=useState(null),[E,I]=useState(!1),[O,A]=useState(null),[D,N]=useState(null),[k,F]=useState(!1),[H,z]=useState([]),[P,B]=useState(null),V=useMemo(()=>{const e=[];for(const[t,r]of Object.entries(l?.views??{}))e.push({name:t,view:r});return e},[l]),[$,G]=useState(V.length>0?V[0].name:null);useEffect(()=>{if(0===V.length)return;null!==$&&V.some(e=>e.name===$)||G(V[0].name)},[V,$]);const W=V.find(e=>e.name===$)?.view,q="raw_trace"===W?.type,U=useRef([]),J=useRef(null),Y=useRef(null),Z=()=>{Y.current||(Y.current=setTimeout(()=>{if(Y.current=null,U.current.length>0){const e=U.current;U.current=[],f(t=>[...e.slice().reverse(),...t])}J.current&&(g(J.current),J.current=null)},o))};useEffect(()=>{if(!r||!a||!n)return p(null),f([]),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&&f(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&&(U.current.push(t.cycle),Z())}),l=d("tis.results_updated",t=>{e(t)&&(J.current=t.results??{},Z())});return()=>{u(t),u(l),Y.current&&(clearTimeout(Y.current),Y.current=null)}},[r,a,n,o]);const K="string"==typeof m?.sample_id&&m.sample_id?m.sample_id:"string"==typeof m?.config?.sample_id?m.config.sample_id:"";useEffect(()=>{if(!r||!a||!K)return void x({});let e=!1;(async()=>{try{const t=await c("tis.sample_summary",MessageType.Request,{project_id:r,method_id:a,sample_id:K});!e&&t?.success&&x(t.data?.summary??{})}catch(e){}})();const t=d("tis.sample_summary_updated",e=>{e?.project_id===r&&e?.method_id===a&&e?.sample_id===K&&x(e.summary??{})});return()=>{e=!0,u(t)}},[r,a,K,c,d,u]);const Q=useRawCycleData({projectId:r,methodId:a,runId:n,blobName:l?.raw_data?.blob_name??"trace",source:W?.source??"raw",enabled:q}),X=useMemo(()=>{if(!W)return null;if("cycle_scatter"===W.type){const e=W.x.field;if(!e)return null;const t=[...y].reverse();return{labels:t.map(t=>t[e]),datasets:W.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"===W.type){if(!Q.raw)return null;const e=W.x.column;if(!e)return null;const t=Q.raw[e]??[],r=!1!==W.smooth;return{datasets:W.y.map((e,a)=>{const n=Q.raw[e.column]??[],l=r?smoothTraceForDisplay(t,n,W.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},[W,y,Q.raw]),ee=W?.y.some(e=>"right"===e.y_axis)??!1,te=useMemo(()=>{if(!q)return[];const e=Q.envelope,t=e?.regions??e?.context?.regions;return Array.isArray(t)?t:[]},[q,Q.envelope]),re=useMemo(()=>[...W?.regions??[],...te],[W,te]),ae=useMemo(()=>{const e="raw_trace"===W?.type;return{responsive:!0,maintainAspectRatio:!1,parsing:!e&&void 0,scales:{x:e?{type:"linear",title:{display:!!W?.x.label,text:W?.x.label}}:{title:{display:!!W?.x.label,text:W?.x.label}},y:{position:"left",title:{display:!0,text:leftAxisLabel(W)}},...ee?{y1:{position:"right",grid:{drawOnChartArea:!1},title:{display:!0,text:rightAxisLabel(W)}}}:{}},plugins:{legend:{display:!0},zoom:{pan:{enabled:!0,mode:"xy"},zoom:{wheel:{enabled:!0},pinch:{enabled:!0},mode:"xy"}},annotation:{annotations:buildRegionAnnotations(re)}}}},[W,ee,re]),ne=l?.raw_data?.blob_name??"trace",le=useRef(""),oe=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===ne&&"number"==typeof e?.cycle_index).map(e=>e.cycle_index).sort((e,t)=>e-t)}catch{return[]}},[r,a,n,ne,c]),se=useCallback(async e=>{if(!r||!a||!n)return;const t=`${r}|${a}|${n}|${ne}|${e??"latest"}`;if(le.current===t)return;le.current=t;const l={project_id:r,method_id:a,run_id:n,name:ne};null!=e&&(l.cycle_index=e),I(!0),L(null),R(null);try{const e=await c("tis.read_raw",MessageType.Request,l);e?.success?R(e.data??{}):L(e?.error_message??"No raw data on disk for this run.")}catch(e){L(String(e?.message??e))}finally{I(!1)}F(!0),N(null),A(null);try{const e=await c("tis.read_filtered",MessageType.Request,l);e?.success?A(e.data??{}):N(e?.error_message??"No filtered data on disk for this run.")}catch(e){N(String(e?.message??e))}finally{F(!1)}},[r,a,n,ne,c]);useEffect(()=>{le.current="",z([]),B(null)},[r,a,n,ne]);return useEffect(()=>{b&&null!=P&&se(P)},[b,P,se]),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()=>{j(!0);let e=H;0===e.length&&(e=await oe(),z(e));const t=e.length>0?e[e.length-1]:null,r=P??t;P!==r&&B(r),await se(r)},onShowConfig:()=>w(!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:$,options:V.map(e=>({label:e.view.title??e.name,value:e.name})),onChange:e=>G(e.value),placeholder:0===V.length?"No view defined":"Select a view",disabled:0===V.length}),_jsx("h3",{style:{margin:0},children:W?.title??""}),q&&Q.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:Q.selectedCycle,options:Q.cycles.map(e=>({label:`Cycle ${e}`,value:e})),onChange:e=>Q.setSelectedCycle(Number(e.value)),style:{minWidth:"8rem"}}),_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:["of ",Q.cycles.length]})]}),_jsx("div",{style:{flex:1}}),_jsx(Button,{icon:"pi pi-th-large",outlined:!0,rounded:!0,size:"small",onClick:()=>S.current?.resetZoom?.(),disabled:!X,tooltip:"Reset chart zoom",tooltipOptions:{position:"left"},"aria-label":"Reset chart zoom"})]}),_jsxs("div",{style:{height:i,position:"relative"},children:[q&&Q.loading&&_jsx(ChartOverlay,{children:"Loading raw data…"}),q&&Q.error&&_jsx(ChartOverlay,{children:Q.error}),X&&_jsx(Line,{ref:S,data:X,options:ae})]})]}),(()=>{const e=y.length>CYCLE_VIRTUAL_THRESHOLD,t=(e,t)=>({field:e.name,label:e.name,units:e.units,editable:t,body:t=>formatCell(t[e.name],e.type,e.scale)}),r=[...(l.cycle_config_fields??[]).map(e=>t(e,!0)),...(l.cycle_fields??[]).map(e=>t(e,!!e.editable))];return _jsx(ScienceTable,{title:`Cycle Data (${y.length})`,columns:r,rows:y,scrollable:e,scrollHeight:e?s:void 0,emptyMessage:"No cycles yet.",onCellEdit:C})})(),(()=>{const e={...h,..._},t=Object.keys(e).length>0,r=(l.results_fields??[]).map(e=>({field:e.name,label:e.name,units:e.units,body:t=>formatCell(t[e.name],e.type,e.scale)}));return _jsx(ScienceTable,{title:"Results",columns:r,rows:t?[e]:[],emptyMessage:"No results yet."})})(),l.raw_data&&_jsxs(Dialog,{visible:b,onHide:()=>j(!1),header:`Run Data — ${n}`,style:{width:"90vw",height:"80vh"},maximizable:!0,children:[_jsx(CyclePickerBar,{cycles:H,selected:P,onChange:B}),_jsx(RawEnvelopeHeader,{envelope:T}),_jsxs(TabView,{style:{height:"100%"},children:[_jsx(TabPanel,{header:"Raw Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(T),loading:E,error:M,rawData:l.raw_data})}),_jsx(TabPanel,{header:"Filtered Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(O),loading:k,error:D,rawData:l.raw_data,emptyMessage:"Filtered data is written by post-processing — none on disk for this run yet."})})]})]}),_jsx(Dialog,{visible:v,onHide:()=>w(!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),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";import{ScienceTable}from"./ScienceTable";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({}),[b,j]=useState(!1),[v,w]=useState(!1),C=useCallback(async(e,t,l)=>{const o=e?.cycle_index;if(r&&a&&n&&o)try{const e=await c("tis.update_cycle",MessageType.Request,{project_id:r,method_id:a,run_id:n,cycle_index:o,fields:{[t]:l}});if(!1===e?.success)return;h(e=>e.map(e=>e?.cycle_index===o?{...e,[t]:l}:e))}catch(e){}},[c,r,a,n]),S=useRef(null),[T,R]=useState(null),[M,E]=useState(null),[L,I]=useState(!1),[A,O]=useState(null),[D,N]=useState(null),[k,F]=useState(!1),[H,P]=useState([]),[z,B]=useState(null),V=useMemo(()=>{const e=[];for(const[t,r]of Object.entries(l?.views??{}))e.push({name:t,view:r});return e},[l]),[$,G]=useState(V.length>0?V[0].name:null);useEffect(()=>{if(0===V.length)return;null!==$&&V.some(e=>e.name===$)||G(V[0].name)},[V,$]);const W=V.find(e=>e.name===$)?.view,q="raw_trace"===W?.type,U=useRef([]),J=useRef(null),Y=useRef(null),Z=()=>{Y.current||(Y.current=setTimeout(()=>{if(Y.current=null,U.current.length>0){const e=U.current;U.current=[],h(t=>[...e.slice().reverse(),...t])}J.current&&(g(J.current),J.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&&(U.current.push(t.cycle),Z())}),l=d("tis.results_updated",t=>{e(t)&&(J.current=t.results??{},Z())});return()=>{u(t),u(l),Y.current&&(clearTimeout(Y.current),Y.current=null)}},[r,a,n,o]);const K="string"==typeof m?.sample_id&&m.sample_id?m.sample_id:"string"==typeof m?.config?.sample_id?m.config.sample_id:"";useEffect(()=>{if(!r||!a||!K)return void x({});let e=!1;const t=async()=>{try{const t=await c("tis.sample_summary",MessageType.Request,{project_id:r,method_id:a,sample_id:K,run_id:n});!e&&t?.success&&x(t.data?.summary??{})}catch(e){}};t();const l=[d("tis.sample_summary_updated",e=>{e?.project_id===r&&e?.method_id===a&&e?.sample_id===K&&t()}),d("tis.cycle_added",e=>{e?.project_id===r&&e?.method_id===a&&t()})];return()=>{e=!0,l.forEach(u)}},[r,a,K,n,c,d,u]);const Q=useRawCycleData({projectId:r,methodId:a,runId:n,blobName:l?.raw_data?.blob_name??"trace",source:W?.source??"raw",enabled:q}),X=useMemo(()=>{if(!W)return null;if("cycle_scatter"===W.type){const e=W.x.field;if(!e)return null;const t=[...y].reverse();return{labels:t.map(t=>t[e]),datasets:W.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"===W.type){if(!Q.raw)return null;const e=W.x.column;if(!e)return null;const t=Q.raw[e]??[],r=!1!==W.smooth;return{datasets:W.y.map((e,a)=>{const n=Q.raw[e.column]??[],l=r?smoothTraceForDisplay(t,n,W.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},[W,y,Q.raw]),ee=W?.y.some(e=>"right"===e.y_axis)??!1,te=useMemo(()=>{if(!q)return[];const e=Q.envelope,t=e?.regions??e?.context?.regions;return Array.isArray(t)?t:[]},[q,Q.envelope]),re=useMemo(()=>[...W?.regions??[],...te],[W,te]),ae=useMemo(()=>{const e="raw_trace"===W?.type;return{responsive:!0,maintainAspectRatio:!1,parsing:!e&&void 0,scales:{x:e?{type:"linear",title:{display:!!W?.x.label,text:W?.x.label}}:{title:{display:!!W?.x.label,text:W?.x.label}},y:{position:"left",title:{display:!0,text:leftAxisLabel(W)}},...ee?{y1:{position:"right",grid:{drawOnChartArea:!1},title:{display:!0,text:rightAxisLabel(W)}}}:{}},plugins:{legend:{display:!0},zoom:{pan:{enabled:!0,mode:"xy"},zoom:{wheel:{enabled:!0},pinch:{enabled:!0},mode:"xy"}},annotation:{annotations:buildRegionAnnotations(re)}}}},[W,ee,re]),ne=l?.raw_data?.blob_name??"trace",le=useRef(""),oe=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===ne&&"number"==typeof e?.cycle_index).map(e=>e.cycle_index).sort((e,t)=>e-t)}catch{return[]}},[r,a,n,ne,c]),se=useCallback(async e=>{if(!r||!a||!n)return;const t=`${r}|${a}|${n}|${ne}|${e??"latest"}`;if(le.current===t)return;le.current=t;const l={project_id:r,method_id:a,run_id:n,name:ne};null!=e&&(l.cycle_index=e),I(!0),E(null),R(null);try{const e=await c("tis.read_raw",MessageType.Request,l);e?.success?R(e.data??{}):E(e?.error_message??"No raw data on disk for this run.")}catch(e){E(String(e?.message??e))}finally{I(!1)}F(!0),N(null),O(null);try{const e=await c("tis.read_filtered",MessageType.Request,l);e?.success?O(e.data??{}):N(e?.error_message??"No filtered data on disk for this run.")}catch(e){N(String(e?.message??e))}finally{F(!1)}},[r,a,n,ne,c]);useEffect(()=>{le.current="",P([]),B(null)},[r,a,n,ne]);return useEffect(()=>{b&&null!=z&&se(z)},[b,z,se]),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()=>{j(!0);let e=H;0===e.length&&(e=await oe(),P(e));const t=e.length>0?e[e.length-1]:null,r=z??t;z!==r&&B(r),await se(r)},onShowConfig:()=>w(!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:$,options:V.map(e=>({label:e.view.title??e.name,value:e.name})),onChange:e=>G(e.value),placeholder:0===V.length?"No view defined":"Select a view",disabled:0===V.length}),_jsx("h3",{style:{margin:0},children:W?.title??""}),q&&Q.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:Q.selectedCycle,options:Q.cycles.map(e=>({label:`Cycle ${e}`,value:e})),onChange:e=>Q.setSelectedCycle(Number(e.value)),style:{minWidth:"8rem"}}),_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:["of ",Q.cycles.length]})]}),_jsx("div",{style:{flex:1}}),_jsx(Button,{icon:"pi pi-th-large",outlined:!0,rounded:!0,size:"small",onClick:()=>S.current?.resetZoom?.(),disabled:!X,tooltip:"Reset chart zoom",tooltipOptions:{position:"left"},"aria-label":"Reset chart zoom"})]}),_jsxs("div",{style:{height:i,position:"relative"},children:[q&&Q.loading&&_jsx(ChartOverlay,{children:"Loading raw data…"}),q&&Q.error&&_jsx(ChartOverlay,{children:Q.error}),X&&_jsx(Line,{ref:S,data:X,options:ae})]})]}),(()=>{const e=y.length>CYCLE_VIRTUAL_THRESHOLD,t=[...(l.cycle_config_fields??[]).map(e=>toScienceColumn(e,!0)),...(l.cycle_fields??[]).map(e=>toScienceColumn(e,!!e.editable))];return _jsx(ScienceTable,{title:`Cycle Data (${y.length})`,columns:t,rows:y,scrollable:e,scrollHeight:e?s:void 0,emptyMessage:"No cycles yet.",onCellEdit:C})})(),(()=>{const e={...f,..._},t=Object.keys(e).length>0,r=l.results_fields??[],a=r.map(e=>toScienceColumn(e)),n=r.some(isProjectScoped);return _jsxs(_Fragment,{children:[_jsx(ScienceTable,{title:"Results",columns:a,rows:t?[e]:[],emptyMessage:"No results yet."}),n&&_jsx("div",{style:{fontSize:"0.8rem",color:"var(--text-secondary-color)",margin:"0.25rem 0 0.75rem"},children:"* across every test of this sample in the project. All other values describe this test."})]})})(),l.raw_data&&_jsxs(Dialog,{visible:b,onHide:()=>j(!1),header:`Run Data — ${n}`,style:{width:"90vw",height:"80vh"},maximizable:!0,children:[_jsx(CyclePickerBar,{cycles:H,selected:z,onChange:B}),_jsx(RawEnvelopeHeader,{envelope:T}),_jsxs(TabView,{style:{height:"100%"},children:[_jsx(TabPanel,{header:"Raw Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(T),loading:L,error:M,rawData:l.raw_data})}),_jsx(TabPanel,{header:"Filtered Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(A),loading:k,error:D,rawData:l.raw_data,emptyMessage:"Filtered data is written by post-processing — none on disk for this run yet."})})]})]}),_jsx(Dialog,{visible:v,onHide:()=>w(!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),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(" / ")??"",isProjectScoped=e=>"project"===e.aggregate?.scope,toScienceColumn=(e,t=!1)=>({field:e.name,label:(e.label&&e.label.length>0?e.label:e.name)+(isProjectScoped(e)?" *":""),units:e.units,editable:t,body:t=>formatCell(t[e.name],e.type,e.scale)}),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":"TestFieldDialog.d.ts","sourceRoot":"","sources":["../../../../src/components/tis-editor/editor/TestFieldDialog.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAiC,MAAM,UAAU,CAAC;AAoCzE,MAAM,WAAW,oBAAoB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,SAAS,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACnC,8EAA8E;IAC9E,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB;8CAC0C;IAC1C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;0EACsE;IACtE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,uEAAuE;IACvE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC;AAID,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAwP1D,CAAC"}
1
+ {"version":3,"file":"TestFieldDialog.d.ts","sourceRoot":"","sources":["../../../../src/components/tis-editor/editor/TestFieldDialog.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAA2C,MAAM,UAAU,CAAC;AA6CnF,MAAM,WAAW,oBAAoB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,SAAS,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACnC,8EAA8E;IAC9E,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB;8CAC0C;IAC1C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;0EACsE;IACtE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,uEAAuE;IACvE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC;AAID,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAqR1D,CAAC"}
@@ -1 +1 @@
1
- import{jsx as _jsx,Fragment as _Fragment,jsxs as _jsxs}from"react/jsx-runtime";import{useEffect,useState}from"react";import{Dialog}from"primereact/dialog";import{Button}from"primereact/button";import{InputText}from"primereact/inputtext";import{InputTextarea}from"primereact/inputtextarea";import{Dropdown}from"primereact/dropdown";import{Checkbox}from"primereact/checkbox";import{InputNumber}from"primereact/inputnumber";import{FormRow}from"../../forms/FormRow";const FIELD_TYPES=[{label:"string",value:"string"},{label:"i32",value:"i32"},{label:"i64",value:"i64"},{label:"u32",value:"u32"},{label:"u64",value:"u64"},{label:"f32",value:"f32"},{label:"f64",value:"f64"},{label:"bool",value:"bool"}],AGG_FNS=[{label:"— none (analysis-fed) —",value:""},{label:"avg",value:"avg"},{label:"min",value:"min"},{label:"max",value:"max"},{label:"stddev",value:"stddev"},{label:"count (how many went into the rollup)",value:"count"}],AGG_FROMS=[{label:"cycle field (every cycle, every test)",value:"cycle_fields"},{label:"result (one value per test)",value:"results"}],COUNT_FROMS=[{label:"cycles (rows across the included tests)",value:"cycle_fields"},{label:"tests (included runs for the sample)",value:"results"}],blank={name:"",type:"f32"};export const TestFieldDialog=({visible:e,initial:a,onCancel:t,onSave:l,siblingNames:r,isResultsField:n=!1,isCycleConfigField:o=!1,cycleFieldNames:i=[],resultsFieldNames:s=[]})=>{const[u,d]=useState(blank),[m,c]=useState(""),[g,h]=useState(null);useEffect(()=>{e&&(d(a?{...a}:{...blank}),c(null==a?.default?"":String(a.default)),h(null))},[e,a]);const p=u.aggregate,f=p?.from??"cycle_fields",v=("results"===f?s:i).filter(e=>e&&e!==u.name).map(e=>({label:e,value:e}));return _jsxs(Dialog,{header:a?`Edit field: ${a.name}`:"New field",visible:e,onHide:t,style:{width:"36rem"},children:[g&&_jsx("div",{style:{color:"#dc2626",marginBottom:"0.75rem"},children:g}),_jsx(FormRow,{label:"Name",required:!0,hint:"Wire-format key. Also the column name in CSV exports.",children:_jsx(InputText,{value:u.name,onChange:e=>d({...u,name:e.target.value})})}),_jsx(FormRow,{label:"Type",required:!0,children:_jsx(Dropdown,{value:u.type,options:FIELD_TYPES,onChange:e=>d({...u,type:e.value}),editable:!0})}),_jsx(FormRow,{label:"Units",hint:"Display label, appended to form labels (e.g. m/s).",children:_jsx(InputText,{value:u.units??"",onChange:e=>d({...u,units:e.target.value||void 0})})}),_jsx(FormRow,{label:"Label",hint:"Pretty form label. Falls back to name when empty.",children:_jsx(InputText,{value:u.label??"",onChange:e=>d({...u,label:e.target.value||void 0})})}),_jsx(FormRow,{label:"Required",hint:o?"Blocks the machine until the operator fills this in — tis.cycle_ready stays false and start_test is refused. This is what stops an unannotated row from being recorded.":void 0,children:_jsx(Checkbox,{checked:!!u.required,onChange:e=>d({...u,required:!!e.checked})})}),o&&_jsx(FormRow,{label:"Autoclear",hint:"Clear the operator's entry once a row has recorded it, so the next row can't inherit a stale value. Fires after each cycle under 'cycle' scope, at the end of the run under 'run' scope. Pair with Required — on its own it turns stale data into silently missing data.",children:_jsx(Checkbox,{checked:!!u.autoclear,onChange:e=>d({...u,autoclear:!!e.checked})})}),_jsx(FormRow,{label:"Source",hint:"Optional gm.* variable to bind this field to.",children:_jsx(InputText,{value:u.source??"",onChange:e=>d({...u,source:e.target.value||void 0}),placeholder:"gm.<variable_name>"})}),_jsx(FormRow,{label:"Scale",hint:"display = raw × scale. Leave blank for 1.0 (no conversion).",children:_jsx(InputNumber,{value:u.scale??null,onValueChange:e=>d({...u,scale:"number"==typeof e.value?e.value:void 0}),mode:"decimal",minFractionDigits:0,maxFractionDigits:9})}),_jsx(FormRow,{label:"Min",hint:"Optional lower bound the operator can enter (display units). Blank = no minimum.",children:_jsx(InputNumber,{value:u.min??null,onValueChange:e=>d({...u,min:"number"==typeof e.value?e.value:void 0}),mode:"decimal",minFractionDigits:0,maxFractionDigits:9})}),_jsx(FormRow,{label:"Max",hint:"Optional upper bound the operator can enter (display units). Blank = no maximum.",children:_jsx(InputNumber,{value:u.max??null,onValueChange:e=>d({...u,max:"number"==typeof e.value?e.value:void 0}),mode:"decimal",minFractionDigits:0,maxFractionDigits:9})}),_jsx(FormRow,{label:"Default",hint:"Applied every time the method loads (operator can override). A literal (display units) like 5, or an FQDN token like ${gm.safe_speed} that snapshots that value on load.",children:_jsx(InputText,{value:m,onChange:e=>c(e.target.value),placeholder:"e.g. 5 or ${gm.safe_speed}"})}),n&&_jsxs(_Fragment,{children:[_jsx("div",{style:{borderTop:"1px solid #e5e7eb",margin:"0.75rem 0 0.25rem"}}),_jsx(FormRow,{label:"Aggregate",hint:"Cross-test rollup. When set, this result is derived server-side over the non-excluded tests sharing the same sample_id — the analysis script does not supply it.",children:_jsx(Dropdown,{value:p?.fn??"",options:AGG_FNS,onChange:e=>(e=>{if(""===e)return void d({...u,aggregate:void 0});if("count"===e)return void d({...u,aggregate:{fn:"count",from:u.aggregate?.from??"cycle_fields"}});const a={fn:e,of:u.aggregate?.of,from:u.aggregate?.from??"cycle_fields"};d({...u,aggregate:a})})(e.value)})}),p&&"count"===p.fn&&_jsx(FormRow,{label:"Count",hint:"What is being counted. Cycles is the number of rows behind an avg/min/max over cycle fields; tests is the number of runs for this sample.",children:_jsx(Dropdown,{value:f,options:COUNT_FROMS,onChange:e=>d({...u,aggregate:{...p,from:e.value}})})}),p&&"count"!==p.fn&&_jsxs(_Fragment,{children:[_jsx(FormRow,{label:"Aggregate from",hint:"Which per-test source the target field is read from.",children:_jsx(Dropdown,{value:f,options:AGG_FROMS,onChange:e=>d({...u,aggregate:{...p,from:e.value}})})}),_jsx(FormRow,{label:"Aggregate of",required:!0,hint:"The field to reduce across tests.",children:_jsx(Dropdown,{value:p.of??null,options:v,onChange:e=>d({...u,aggregate:{...p,of:e.value||void 0}}),editable:!0,placeholder:v.length?"Select a field":`No ${f} defined yet`})})]})]}),_jsx(FormRow,{label:"Description",hint:"Hover tooltip in the form.",children:_jsx(InputTextarea,{rows:2,value:u.description??"",onChange:e=>d({...u,description:e.target.value||void 0})})}),_jsxs("div",{style:{display:"flex",justifyContent:"flex-end",gap:"0.5rem",marginTop:"1rem"},children:[_jsx(Button,{label:"Cancel",className:"p-button-text",onClick:t}),_jsx(Button,{label:"Save",onClick:()=>{const e={...u},t=(e=>{const a=e.trim();if(""!==a){if(a.startsWith("${"))return a;if("true"===a)return!0;if("false"===a)return!1;if(/^-?\d*\.?\d+$/.test(a)){const e=Number(a);if(Number.isFinite(e))return e}return a}})(m);void 0===t?delete e.default:e.default=t;const n=(o=e).name.trim()?/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(o.name)?r.some(e=>e===o.name&&e!==a?.name)?`A field named "${o.name}" already exists in this array.`:"number"==typeof o.min&&"number"==typeof o.max&&o.min>o.max?`Min (${o.min}) cannot be greater than Max (${o.max}).`:o.aggregate&&"count"!==o.aggregate.fn&&!o.aggregate.of?.trim()?`Aggregate "${o.aggregate.fn}" needs a target field (the field to ${o.aggregate.fn} across tests).`:null:"Name must be a valid identifier (letters, digits, underscore; cannot start with a digit).":"Field name is required.";var o;n?h(n):l(e)}})]})]})};
1
+ import{jsx as _jsx,Fragment as _Fragment,jsxs as _jsxs}from"react/jsx-runtime";import{useEffect,useState}from"react";import{Dialog}from"primereact/dialog";import{Button}from"primereact/button";import{InputText}from"primereact/inputtext";import{InputTextarea}from"primereact/inputtextarea";import{Dropdown}from"primereact/dropdown";import{Checkbox}from"primereact/checkbox";import{InputNumber}from"primereact/inputnumber";import{FormRow}from"../../forms/FormRow";const FIELD_TYPES=[{label:"string",value:"string"},{label:"i32",value:"i32"},{label:"i64",value:"i64"},{label:"u32",value:"u32"},{label:"u64",value:"u64"},{label:"f32",value:"f32"},{label:"f64",value:"f64"},{label:"bool",value:"bool"}],AGG_FNS=[{label:"— none (analysis-fed) —",value:""},{label:"avg",value:"avg"},{label:"min",value:"min"},{label:"max",value:"max"},{label:"stddev",value:"stddev"},{label:"count (how many went into the rollup)",value:"count"}],AGG_FROMS=[{label:"cycle field (every cycle, every test)",value:"cycle_fields"},{label:"result (one value per test)",value:"results"}],COUNT_CHOICES=[{label:"cycles in this test",value:"test_cycles",scope:"test",from:"cycle_fields"},{label:"cycles for this sample, all tests",value:"project_cycles",scope:"project",from:"cycle_fields"},{label:"tests for this sample",value:"project_tests",scope:"project",from:"results"}],AGG_SCOPES=[{label:"this test only",value:"test"},{label:"this sample, every test in the project",value:"project"}],blank={name:"",type:"f32"};export const TestFieldDialog=({visible:e,initial:t,onCancel:a,onSave:l,siblingNames:o,isResultsField:r=!1,isCycleConfigField:n=!1,cycleFieldNames:i=[],resultsFieldNames:s=[]})=>{const[u,c]=useState(blank),[d,m]=useState(""),[g,p]=useState(null);useEffect(()=>{e&&(c(t?{...t}:{...blank}),m(null==t?.default?"":String(t.default)),p(null))},[e,t]);const h=u.aggregate,f=h?.from??"cycle_fields",v=h?.scope??"test",b=COUNT_CHOICES.find(e=>e.scope===v&&e.from===f)?.value??"test_cycles",x=("results"===f?s:i).filter(e=>e&&e!==u.name).map(e=>({label:e,value:e}));return _jsxs(Dialog,{header:t?`Edit field: ${t.name}`:"New field",visible:e,onHide:a,style:{width:"36rem"},children:[g&&_jsx("div",{style:{color:"#dc2626",marginBottom:"0.75rem"},children:g}),_jsx(FormRow,{label:"Name",required:!0,hint:"Wire-format key. Also the column name in CSV exports.",children:_jsx(InputText,{value:u.name,onChange:e=>c({...u,name:e.target.value})})}),_jsx(FormRow,{label:"Type",required:!0,children:_jsx(Dropdown,{value:u.type,options:FIELD_TYPES,onChange:e=>c({...u,type:e.value}),editable:!0})}),_jsx(FormRow,{label:"Units",hint:"Display label, appended to form labels (e.g. m/s).",children:_jsx(InputText,{value:u.units??"",onChange:e=>c({...u,units:e.target.value||void 0})})}),_jsx(FormRow,{label:"Label",hint:"Pretty form label. Falls back to name when empty.",children:_jsx(InputText,{value:u.label??"",onChange:e=>c({...u,label:e.target.value||void 0})})}),_jsx(FormRow,{label:"Required",hint:n?"Blocks the machine until the operator fills this in — tis.cycle_ready stays false and start_test is refused. This is what stops an unannotated row from being recorded.":void 0,children:_jsx(Checkbox,{checked:!!u.required,onChange:e=>c({...u,required:!!e.checked})})}),n&&_jsx(FormRow,{label:"Autoclear",hint:"Clear the operator's entry once a row has recorded it, so the next row can't inherit a stale value. Fires after each cycle under 'cycle' scope, at the end of the run under 'run' scope. Pair with Required — on its own it turns stale data into silently missing data.",children:_jsx(Checkbox,{checked:!!u.autoclear,onChange:e=>c({...u,autoclear:!!e.checked})})}),_jsx(FormRow,{label:"Source",hint:"Optional gm.* variable to bind this field to.",children:_jsx(InputText,{value:u.source??"",onChange:e=>c({...u,source:e.target.value||void 0}),placeholder:"gm.<variable_name>"})}),_jsx(FormRow,{label:"Scale",hint:"display = raw × scale. Leave blank for 1.0 (no conversion).",children:_jsx(InputNumber,{value:u.scale??null,onValueChange:e=>c({...u,scale:"number"==typeof e.value?e.value:void 0}),mode:"decimal",minFractionDigits:0,maxFractionDigits:9})}),_jsx(FormRow,{label:"Min",hint:"Optional lower bound the operator can enter (display units). Blank = no minimum.",children:_jsx(InputNumber,{value:u.min??null,onValueChange:e=>c({...u,min:"number"==typeof e.value?e.value:void 0}),mode:"decimal",minFractionDigits:0,maxFractionDigits:9})}),_jsx(FormRow,{label:"Max",hint:"Optional upper bound the operator can enter (display units). Blank = no maximum.",children:_jsx(InputNumber,{value:u.max??null,onValueChange:e=>c({...u,max:"number"==typeof e.value?e.value:void 0}),mode:"decimal",minFractionDigits:0,maxFractionDigits:9})}),_jsx(FormRow,{label:"Default",hint:"Applied every time the method loads (operator can override). A literal (display units) like 5, or an FQDN token like ${gm.safe_speed} that snapshots that value on load.",children:_jsx(InputText,{value:d,onChange:e=>m(e.target.value),placeholder:"e.g. 5 or ${gm.safe_speed}"})}),r&&_jsxs(_Fragment,{children:[_jsx("div",{style:{borderTop:"1px solid #e5e7eb",margin:"0.75rem 0 0.25rem"}}),_jsx(FormRow,{label:"Aggregate",hint:"Cross-test rollup. When set, this result is derived server-side over the non-excluded tests sharing the same sample_id — the analysis script does not supply it.",children:_jsx(Dropdown,{value:h?.fn??"",options:AGG_FNS,onChange:e=>(e=>{if(""===e)return void c({...u,aggregate:void 0});if("count"===e)return void c({...u,aggregate:{fn:"count",from:u.aggregate?.from??"cycle_fields",scope:u.aggregate?.scope??"test"}});const t={fn:e,of:u.aggregate?.of,from:u.aggregate?.from??"cycle_fields",scope:u.aggregate?.scope??"test"};c({...u,aggregate:t})})(e.value)})}),h&&"count"===h.fn&&_jsx(FormRow,{label:"Count",hint:"What is being counted. A test's own cycles is the usual choice it's the number the operator can see in the Cycle Data table.",children:_jsx(Dropdown,{value:b,options:COUNT_CHOICES,onChange:e=>{const t=COUNT_CHOICES.find(t=>t.value===e.value);t&&c({...u,aggregate:{...h,scope:t.scope,from:t.from}})}})}),h&&"count"!==h.fn&&_jsxs(_Fragment,{children:[_jsx(FormRow,{label:"Scope",hint:"How wide the reduction reaches. 'This test only' is the default: a value on a test's own results panel or report describes that test.",children:_jsx(Dropdown,{value:v,options:AGG_SCOPES,onChange:e=>c({...u,aggregate:{...h,scope:e.value}})})}),_jsx(FormRow,{label:"Aggregate from",hint:"Which per-test source the target field is read from.",children:_jsx(Dropdown,{value:f,options:AGG_FROMS,onChange:e=>c({...u,aggregate:{...h,from:e.value}})})}),_jsx(FormRow,{label:"Aggregate of",required:!0,hint:"The field to reduce across tests.",children:_jsx(Dropdown,{value:h.of??null,options:x,onChange:e=>c({...u,aggregate:{...h,of:e.value||void 0}}),editable:!0,placeholder:x.length?"Select a field":`No ${f} defined yet`})})]})]}),_jsx(FormRow,{label:"Description",hint:"Hover tooltip in the form.",children:_jsx(InputTextarea,{rows:2,value:u.description??"",onChange:e=>c({...u,description:e.target.value||void 0})})}),_jsxs("div",{style:{display:"flex",justifyContent:"flex-end",gap:"0.5rem",marginTop:"1rem"},children:[_jsx(Button,{label:"Cancel",className:"p-button-text",onClick:a}),_jsx(Button,{label:"Save",onClick:()=>{const e={...u},a=(e=>{const t=e.trim();if(""!==t){if(t.startsWith("${"))return t;if("true"===t)return!0;if("false"===t)return!1;if(/^-?\d*\.?\d+$/.test(t)){const e=Number(t);if(Number.isFinite(e))return e}return t}})(d);void 0===a?delete e.default:e.default=a;const r=(n=e).name.trim()?/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(n.name)?o.some(e=>e===n.name&&e!==t?.name)?`A field named "${n.name}" already exists in this array.`:"number"==typeof n.min&&"number"==typeof n.max&&n.min>n.max?`Min (${n.min}) cannot be greater than Max (${n.max}).`:n.aggregate&&"count"!==n.aggregate.fn&&!n.aggregate.of?.trim()?`Aggregate "${n.aggregate.fn}" needs a target field (the field to ${n.aggregate.fn} across tests).`:null:"Name must be a valid identifier (letters, digits, underscore; cannot start with a digit).":"Field name is required.";var n;r?p(r):l(e)}})]})]})};
@@ -37,6 +37,17 @@ export interface TestField {
37
37
  }
38
38
  export type AggFn = 'avg' | 'min' | 'max' | 'stddev' | 'count';
39
39
  export type AggFrom = 'cycle_fields' | 'results';
40
+ /**
41
+ * How wide an aggregate reaches, in the levels the operator sees:
42
+ * Project > Test > Cycle.
43
+ *
44
+ * 'test' the open test's own cycles (default) — the number a value on
45
+ * a test's results panel or report is understood to describe.
46
+ * 'project' every test in the project sharing this sample_id.
47
+ *
48
+ * Identical whenever a sample is run once; they diverge on a re-run.
49
+ */
50
+ export type AggScope = 'test' | 'project';
40
51
  export interface AggregateSpec {
41
52
  /** Reduction applied across the gathered values. */
42
53
  fn: AggFn;
@@ -45,6 +56,8 @@ export interface AggregateSpec {
45
56
  of?: string;
46
57
  /** Which per-test source `of` is read from. Defaults to "cycle_fields". */
47
58
  from?: AggFrom;
59
+ /** How wide the reduction reaches. Defaults to "test". */
60
+ scope?: AggScope;
48
61
  }
49
62
  export interface ChartAxis {
50
63
  field?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/tis-editor/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/tis-editor/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adcops/autocore-react",
3
- "version": "3.3.120",
3
+ "version": "3.3.123",
4
4
  "description": "A React component library for industrial user interfaces.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -63,6 +63,10 @@ export interface TestFieldDef {
63
63
  units?: string;
64
64
  required?: boolean;
65
65
  source?: string;
66
+ /** Operator-facing column header. The tables and the CSV exports both
67
+ * prefer it over `name`; a header reading `cycle_count` is the wire
68
+ * key leaking into a screen that says "Cycles" everywhere else. */
69
+ label?: string;
66
70
  /** Optional display-time scale multiplier. `display = raw * scale`,
67
71
  * default 1.0 = no conversion. Cycle Data and Results panels
68
72
  * apply this when formatting numeric cells. Charts plot raw
@@ -78,6 +82,16 @@ export interface TestFieldDef {
78
82
  * measured columns can never be typed over. Mirrors Rust
79
83
  * `TestField::editable`. */
80
84
  editable?: boolean;
85
+ /** results_fields only: server-derived rollup. `scope` decides whether
86
+ * the value describes this test (default) or every test of this sample
87
+ * in the project — a distinction the table has to make visible, since
88
+ * the two sit side by side. */
89
+ aggregate?: {
90
+ fn: string;
91
+ of?: string;
92
+ from?: 'cycle_fields' | 'results';
93
+ scope?: 'test' | 'project';
94
+ };
81
95
  }
82
96
 
83
97
  export interface ChartAxis { field?: string; column?: string; label?: string; }
@@ -434,23 +448,52 @@ export const TestDataView: React.FC<TestDataViewProps> = (props) => {
434
448
  try {
435
449
  const resp: any = await invoke(
436
450
  'tis.sample_summary' as any, MessageType.Request as any,
437
- { project_id: projectId, method_id: methodId, sample_id: sampleId } as any);
451
+ // run_id names the test being viewed: `scope: "test"`
452
+ // fields reduce over it alone, `scope: "project"` fields
453
+ // still span every test of this sample.
454
+ { project_id: projectId, method_id: methodId,
455
+ sample_id: sampleId, run_id: runId } as any);
438
456
  if (!cancelled && resp?.success) setSampleSummary(resp.data?.summary ?? {});
439
457
  } catch (e) {
440
458
  console.error('[TestDataView] sample_summary fetch failed', e);
441
459
  }
442
460
  };
443
461
  fetchSummary();
462
+ // Refetch rather than adopt the payload: the broadcast is computed
463
+ // with no current test (it serves the pooled Sample Summary view),
464
+ // so taking its numbers here would silently swap every test-scoped
465
+ // field for its project-wide value.
444
466
  const onSummary = (payload: any) => {
445
467
  if (payload?.project_id === projectId
446
468
  && payload?.method_id === methodId
447
469
  && payload?.sample_id === sampleId) {
448
- setSampleSummary(payload.summary ?? {});
470
+ void fetchSummary();
471
+ }
472
+ };
473
+ // Every cycle_fields aggregate — avg, min, max, stddev, count —
474
+ // moves the moment a cycle is recorded, but `add_cycle` fires no
475
+ // summary broadcast (`sample_summary_updated` covers a sibling run
476
+ // finishing or being excluded, and `update_results` recomputes the
477
+ // rollup server-side). So the Results panel sat on the rollup
478
+ // computed when the view mounted: the operator ran a cycle, watched
479
+ // the Cycle Data table grow a row, and every derived number stayed
480
+ // put until they left the tab and came back (gen4_ac, 2026-08-12).
481
+ //
482
+ // Refetching from the client rather than pushing from the server on
483
+ // every add_cycle is deliberate: the rollup rescans every run of the
484
+ // method off disk, and that cost should be paid only while someone
485
+ // is actually looking at the numbers.
486
+ const onCycleAdded = (payload: any) => {
487
+ if (payload?.project_id === projectId && payload?.method_id === methodId) {
488
+ void fetchSummary();
449
489
  }
450
490
  };
451
- const id = subscribe('tis.sample_summary_updated', onSummary);
452
- return () => { cancelled = true; unsubscribe(id); };
453
- }, [projectId, methodId, sampleId, invoke, subscribe, unsubscribe]);
491
+ const ids = [
492
+ subscribe('tis.sample_summary_updated', onSummary),
493
+ subscribe('tis.cycle_added', onCycleAdded),
494
+ ];
495
+ return () => { cancelled = true; ids.forEach(unsubscribe); };
496
+ }, [projectId, methodId, sampleId, runId, invoke, subscribe, unsubscribe]);
454
497
 
455
498
  // -----------------------------------------------------------------
456
499
  // Raw-trace data fetch (lazy)
@@ -811,16 +854,9 @@ export const TestDataView: React.FC<TestDataViewProps> = (props) => {
811
854
  // server accepts cycle_config_fields on tis.update_cycle
812
855
  // without an `editable` flag), while measured columns still
813
856
  // have to opt in.
814
- const toColumn = (f: TestFieldDef, editable: boolean): ScienceColumn => ({
815
- field: f.name,
816
- label: f.name,
817
- units: f.units,
818
- editable,
819
- body: (row) => formatCell(row[f.name], f.type, f.scale),
820
- });
821
857
  const cycleColumns: ScienceColumn[] = [
822
- ...(schema.cycle_config_fields ?? []).map(f => toColumn(f, true)),
823
- ...(schema.cycle_fields ?? []).map(f => toColumn(f, !!f.editable)),
858
+ ...(schema.cycle_config_fields ?? []).map(f => toScienceColumn(f, true)),
859
+ ...(schema.cycle_fields ?? []).map(f => toScienceColumn(f, !!f.editable)),
824
860
  ];
825
861
  return (
826
862
  <ScienceTable
@@ -842,19 +878,28 @@ export const TestDataView: React.FC<TestDataViewProps> = (props) => {
842
878
  {(() => {
843
879
  const values = { ...results, ...sampleSummary };
844
880
  const hasValues = Object.keys(values).length > 0;
845
- const resultColumns: ScienceColumn[] = (schema.results_fields ?? []).map(f => ({
846
- field: f.name,
847
- label: f.name,
848
- units: f.units,
849
- body: (row) => formatCell(row[f.name], f.type, f.scale),
850
- }));
881
+ const resultFields = schema.results_fields ?? [];
882
+ const resultColumns: ScienceColumn[] = resultFields.map(f => toScienceColumn(f));
883
+ const hasProjectScoped = resultFields.some(isProjectScoped);
851
884
  return (
852
- <ScienceTable
853
- title="Results"
854
- columns={resultColumns}
855
- rows={hasValues ? [values] : []}
856
- emptyMessage="No results yet."
857
- />
885
+ <>
886
+ <ScienceTable
887
+ title="Results"
888
+ columns={resultColumns}
889
+ rows={hasValues ? [values] : []}
890
+ emptyMessage="No results yet."
891
+ />
892
+ {hasProjectScoped && (
893
+ <div style={{
894
+ fontSize: '0.8rem',
895
+ color: 'var(--text-secondary-color)',
896
+ margin: '0.25rem 0 0.75rem',
897
+ }}>
898
+ * across every test of this sample in the project.
899
+ All other values describe this test.
900
+ </div>
901
+ )}
902
+ </>
858
903
  );
859
904
  })()}
860
905
 
@@ -1435,6 +1480,27 @@ const rightAxisLabel = (v?: ChartView) =>
1435
1480
  * the convention used by AutoCoreTagContext: `display = raw * scale`.
1436
1481
  * Non-numeric values pass through unchanged.
1437
1482
  */
1483
+ /**
1484
+ * One schema field → one <ScienceTable> column. Shared by the Cycle Data
1485
+ * and Results tables so a field is headed identically in both.
1486
+ *
1487
+ * The header is the schema's `label` when it declares one, falling back to
1488
+ * the field name. The server applies the same rule to CSV headers
1489
+ * (`column_header_with_units`), so an export reads like the screen.
1490
+ */
1491
+ const isProjectScoped = (f: TestFieldDef): boolean => f.aggregate?.scope === 'project';
1492
+
1493
+ const toScienceColumn = (f: TestFieldDef, editable = false): ScienceColumn => ({
1494
+ field: f.name,
1495
+ // A project-scoped rollup sits in the same row as this test's own
1496
+ // numbers, so it has to say so. Marked here and explained in a note
1497
+ // under the table — never in a tooltip.
1498
+ label: (f.label && f.label.length > 0 ? f.label : f.name) + (isProjectScoped(f) ? ' *' : ''),
1499
+ units: f.units,
1500
+ editable,
1501
+ body: (row) => formatCell(row[f.name], f.type, f.scale),
1502
+ });
1503
+
1438
1504
  const formatCell = (v: any, type: string, scale?: number): string => {
1439
1505
  if (v === null || v === undefined) return '';
1440
1506
  if (scale && scale !== 1 && typeof v === 'number' && Number.isFinite(v)) {
@@ -7,7 +7,7 @@ import { Dropdown } from 'primereact/dropdown';
7
7
  import { Checkbox } from 'primereact/checkbox';
8
8
  import { InputNumber } from 'primereact/inputnumber';
9
9
  import { FormRow } from '../../forms/FormRow';
10
- import type { TestField, AggFn, AggFrom, AggregateSpec } from '../types';
10
+ import type { TestField, AggFn, AggFrom, AggScope, AggregateSpec } from '../types';
11
11
 
12
12
  const FIELD_TYPES: { label: string; value: string }[] = [
13
13
  { label: 'string', value: 'string' },
@@ -35,12 +35,21 @@ const AGG_FROMS: { label: string; value: AggFrom }[] = [
35
35
  { label: 'result (one value per test)', value: 'results' },
36
36
  ];
37
37
 
38
- /** `count` has no target field, but it still picks an axis: how many cycle
39
- * rows, or how many tests. Labelled in those terms rather than the generic
40
- * ones above, because "count of cycle field" reads like nonsense. */
41
- const COUNT_FROMS: { label: string; value: AggFrom }[] = [
42
- { label: 'cycles (rows across the included tests)', value: 'cycle_fields' },
43
- { label: 'tests (included runs for the sample)', value: 'results' },
38
+ /**
39
+ * `count` folds scope and axis into one choice. Presenting them as two
40
+ * dropdowns makes "this test / how many tests" selectable, which is always
41
+ * 1 so the combination simply isn't offered.
42
+ */
43
+ const COUNT_CHOICES: { label: string; value: string; scope: AggScope; from: AggFrom }[] = [
44
+ { label: 'cycles in this test', value: 'test_cycles', scope: 'test', from: 'cycle_fields' },
45
+ { label: 'cycles for this sample, all tests', value: 'project_cycles', scope: 'project', from: 'cycle_fields' },
46
+ { label: 'tests for this sample', value: 'project_tests', scope: 'project', from: 'results' },
47
+ ];
48
+
49
+ /** How wide a non-count reduction reaches. Operator levels, not table names. */
50
+ const AGG_SCOPES: { label: string; value: AggScope }[] = [
51
+ { label: 'this test only', value: 'test' },
52
+ { label: 'this sample, every test in the project', value: 'project' },
44
53
  ];
45
54
 
46
55
  export interface TestFieldDialogProps {
@@ -117,18 +126,31 @@ export const TestFieldDialog: React.FC<TestFieldDialogProps> = ({
117
126
  // drops of/from (it reduces over the set of included tests, not a field).
118
127
  const setAggFn = (fn: '' | AggFn) => {
119
128
  if (fn === '') { setDraft({ ...draft, aggregate: undefined }); return; }
120
- // count keeps its `from`that's what decides cycles vs tests —
121
- // and drops `of`, which it has no use for.
129
+ // count keeps scope + from — together they decide what is being
130
+ // counted — and drops `of`, which it has no use for.
122
131
  if (fn === 'count') {
123
- setDraft({ ...draft, aggregate: { fn: 'count', from: draft.aggregate?.from ?? 'cycle_fields' } });
132
+ setDraft({ ...draft, aggregate: {
133
+ fn: 'count',
134
+ from: draft.aggregate?.from ?? 'cycle_fields',
135
+ scope: draft.aggregate?.scope ?? 'test',
136
+ } });
124
137
  return;
125
138
  }
126
- const next: AggregateSpec = { fn, of: draft.aggregate?.of, from: draft.aggregate?.from ?? 'cycle_fields' };
139
+ const next: AggregateSpec = {
140
+ fn,
141
+ of: draft.aggregate?.of,
142
+ from: draft.aggregate?.from ?? 'cycle_fields',
143
+ scope: draft.aggregate?.scope ?? 'test',
144
+ };
127
145
  setDraft({ ...draft, aggregate: next });
128
146
  };
129
147
 
130
148
  const agg = draft.aggregate;
131
149
  const aggFrom: AggFrom = agg?.from ?? 'cycle_fields';
150
+ const aggScope: AggScope = agg?.scope ?? 'test';
151
+ const countChoice =
152
+ COUNT_CHOICES.find(c => c.scope === aggScope && c.from === aggFrom)?.value
153
+ ?? 'test_cycles';
132
154
  // `of` candidates depend on the source; exclude this field's own name so
133
155
  // a result can't aggregate itself.
134
156
  const aggTargets = (aggFrom === 'results' ? resultsFieldNames : cycleFieldNames)
@@ -262,18 +284,34 @@ export const TestFieldDialog: React.FC<TestFieldDialogProps> = ({
262
284
  />
263
285
  </FormRow>
264
286
  {agg && agg.fn === 'count' && (
265
- <FormRow label="Count" hint="What is being counted. Cycles is the number of rows behind an avg/min/max over cycle fields; tests is the number of runs for this sample.">
287
+ <FormRow
288
+ label="Count"
289
+ hint="What is being counted. A test's own cycles is the usual choice — it's the number the operator can see in the Cycle Data table."
290
+ >
266
291
  <Dropdown
267
- value={aggFrom}
268
- options={COUNT_FROMS}
269
- onChange={(e) =>
270
- setDraft({ ...draft, aggregate: { ...agg, from: e.value } })
271
- }
292
+ value={countChoice}
293
+ options={COUNT_CHOICES}
294
+ onChange={(e) => {
295
+ const c = COUNT_CHOICES.find(o => o.value === e.value);
296
+ if (c) setDraft({ ...draft, aggregate: { ...agg, scope: c.scope, from: c.from } });
297
+ }}
272
298
  />
273
299
  </FormRow>
274
300
  )}
275
301
  {agg && agg.fn !== 'count' && (
276
302
  <>
303
+ <FormRow
304
+ label="Scope"
305
+ hint="How wide the reduction reaches. 'This test only' is the default: a value on a test's own results panel or report describes that test."
306
+ >
307
+ <Dropdown
308
+ value={aggScope}
309
+ options={AGG_SCOPES}
310
+ onChange={(e) =>
311
+ setDraft({ ...draft, aggregate: { ...agg, scope: e.value } })
312
+ }
313
+ />
314
+ </FormRow>
277
315
  <FormRow label="Aggregate from" hint="Which per-test source the target field is read from.">
278
316
  <Dropdown
279
317
  value={aggFrom}
@@ -45,6 +45,18 @@ export interface TestField {
45
45
  export type AggFn = 'avg' | 'min' | 'max' | 'stddev' | 'count';
46
46
  export type AggFrom = 'cycle_fields' | 'results';
47
47
 
48
+ /**
49
+ * How wide an aggregate reaches, in the levels the operator sees:
50
+ * Project > Test > Cycle.
51
+ *
52
+ * 'test' the open test's own cycles (default) — the number a value on
53
+ * a test's results panel or report is understood to describe.
54
+ * 'project' every test in the project sharing this sample_id.
55
+ *
56
+ * Identical whenever a sample is run once; they diverge on a re-run.
57
+ */
58
+ export type AggScope = 'test' | 'project';
59
+
48
60
  export interface AggregateSpec {
49
61
  /** Reduction applied across the gathered values. */
50
62
  fn: AggFn;
@@ -53,6 +65,8 @@ export interface AggregateSpec {
53
65
  of?: string;
54
66
  /** Which per-test source `of` is read from. Defaults to "cycle_fields". */
55
67
  from?: AggFrom;
68
+ /** How wide the reduction reaches. Defaults to "test". */
69
+ scope?: AggScope;
56
70
  }
57
71
 
58
72
  export interface ChartAxis {