@adcops/autocore-react 3.3.120 → 3.3.124
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.
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/tis/ResultHistoryTable.d.ts.map +1 -1
- package/dist/components/tis/ResultHistoryTable.js +1 -1
- package/dist/components/tis/ScienceTable.d.ts +14 -0
- package/dist/components/tis/ScienceTable.d.ts.map +1 -1
- package/dist/components/tis/ScienceTable.js +1 -1
- package/dist/components/tis/TestDataView.d.ts +31 -0
- package/dist/components/tis/TestDataView.d.ts.map +1 -1
- package/dist/components/tis/TestDataView.js +1 -1
- package/dist/components/tis-editor/editor/ChartViewDialog.d.ts.map +1 -1
- package/dist/components/tis-editor/editor/ChartViewDialog.js +1 -1
- package/dist/components/tis-editor/editor/TestFieldDialog.d.ts.map +1 -1
- package/dist/components/tis-editor/editor/TestFieldDialog.js +1 -1
- package/dist/components/tis-editor/types.d.ts +19 -0
- package/dist/components/tis-editor/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/index.ts +1 -1
- package/src/components/tis/ResultHistoryTable.tsx +7 -0
- package/src/components/tis/ScienceTable.tsx +20 -1
- package/src/components/tis/TestDataView.tsx +146 -28
- package/src/components/tis-editor/editor/ChartViewDialog.tsx +27 -0
- package/src/components/tis-editor/editor/TestFieldDialog.tsx +55 -17
- package/src/components/tis-editor/types.ts +20 -0
|
@@ -22,7 +22,7 @@ export { ResultHistoryTable } from './tis/ResultHistoryTable';
|
|
|
22
22
|
export type { ResultHistoryTableProps } from './tis/ResultHistoryTable';
|
|
23
23
|
export { SampleSummaryPanel } from './tis/SampleSummaryPanel';
|
|
24
24
|
export type { SampleSummaryPanelProps } from './tis/SampleSummaryPanel';
|
|
25
|
-
export { TestDataView } from './tis/TestDataView';
|
|
25
|
+
export { TestDataView, DEFAULT_X_DECIMALS } from './tis/TestDataView';
|
|
26
26
|
export type { TestDataViewProps, ChartAxis, ChartSeries, ChartView, RawDataShape } from './tis/TestDataView';
|
|
27
27
|
export { TestRawDataView } from './tis/TestRawDataView';
|
|
28
28
|
export type { TestRawDataViewProps } from './tis/TestRawDataView';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAQA,OAAO,EACH,WAAW,EACX,UAAU,EACV,MAAM,EACN,aAAa,EACb,WAAW,EACX,eAAe,EACf,UAAU,EACV,SAAS,EACT,iBAAiB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACR,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,mBAAmB,GACtB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,eAAe,EAAE,MAAU,uBAAuB,CAAC;AAC5D,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAElE,OAAO,EAAE,aAAa,EAAE,MAAY,qBAAqB,CAAC;AAC1D,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,OAAO,EAAE,aAAa,EAAE,MAAY,qBAAqB,CAAC;AAC1D,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,OAAO,EAAE,YAAY,EAAE,MAAa,oBAAoB,CAAC;AACzD,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE9E,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC5E,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAElE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAChF,YAAY,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAEvF,OAAO,EAAE,gBAAgB,EAAE,MAAS,wBAAwB,CAAC;AAC7D,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE,OAAO,EAAE,iBAAiB,EAAE,MAAQ,yBAAyB,CAAC;AAC9D,YAAY,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAEvF,OAAO,EAAE,gBAAgB,EAAE,MAAS,wBAAwB,CAAC;AAC7D,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE,OAAO,EAAE,kBAAkB,EAAE,MAAO,0BAA0B,CAAC;AAC/D,YAAY,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAExE,OAAO,EAAE,kBAAkB,EAAE,MAAO,0BAA0B,CAAC;AAC/D,YAAY,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAExE,OAAO,EAAE,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"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"}
|
package/dist/components/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{TisProvider,TisContext,useTis,useTisSchemas,useTisState,useTisSelection,useTisRuns,useTisRun,useTisCycleConfig}from"./tis/TisProvider";export{ProjectSelector}from"./tis/ProjectSelector";export{TestSetupForm}from"./tis/TestSetupForm";export{TestSetupView}from"./tis/TestSetupView";export{TestFieldRow}from"./tis/TestFieldRow";export{CycleConfigForm,cycleConfigSummary}from"./tis/CycleConfigForm";export{CycleConfigDialog,useCycleConfigGate}from"./tis/CycleConfigDialog";export{CycleStatusStrip}from"./tis/CycleStatusStrip";export{ProjectInfoDialog}from"./tis/ProjectInfoDialog";export{TestMethodDialog}from"./tis/TestMethodDialog";export{ResultHistoryTable}from"./tis/ResultHistoryTable";export{SampleSummaryPanel}from"./tis/SampleSummaryPanel";export{TestDataView}from"./tis/TestDataView";export{TestRawDataView}from"./tis/TestRawDataView";export{ProjectManager}from"./tis/ProjectManager";export{AmsProvider,useAms,useAmsSchemas,useAmsAlerts,useAmsAssets,useAmsSelection}from"./ams/AmsProvider";export{AssetRegistryTable}from"./ams/AssetRegistryTable";export{AssetDetailView}from"./ams/AssetDetailView";export{CalibrationEntryDialog}from"./ams/CalibrationEntryDialog";export{SubLocationPicker}from"./ams/SubLocationPicker";export{NetworkProvider,useNetwork}from"./network/NetworkProvider";export{NetworkPanel}from"./network/NetworkPanel";export{StagedChangeBanner}from"./network/StagedChangeBanner";
|
|
1
|
+
export{TisProvider,TisContext,useTis,useTisSchemas,useTisState,useTisSelection,useTisRuns,useTisRun,useTisCycleConfig}from"./tis/TisProvider";export{ProjectSelector}from"./tis/ProjectSelector";export{TestSetupForm}from"./tis/TestSetupForm";export{TestSetupView}from"./tis/TestSetupView";export{TestFieldRow}from"./tis/TestFieldRow";export{CycleConfigForm,cycleConfigSummary}from"./tis/CycleConfigForm";export{CycleConfigDialog,useCycleConfigGate}from"./tis/CycleConfigDialog";export{CycleStatusStrip}from"./tis/CycleStatusStrip";export{ProjectInfoDialog}from"./tis/ProjectInfoDialog";export{TestMethodDialog}from"./tis/TestMethodDialog";export{ResultHistoryTable}from"./tis/ResultHistoryTable";export{SampleSummaryPanel}from"./tis/SampleSummaryPanel";export{TestDataView,DEFAULT_X_DECIMALS}from"./tis/TestDataView";export{TestRawDataView}from"./tis/TestRawDataView";export{ProjectManager}from"./tis/ProjectManager";export{AmsProvider,useAms,useAmsSchemas,useAmsAlerts,useAmsAssets,useAmsSelection}from"./ams/AmsProvider";export{AssetRegistryTable}from"./ams/AssetRegistryTable";export{AssetDetailView}from"./ams/AssetDetailView";export{CalibrationEntryDialog}from"./ams/CalibrationEntryDialog";export{SubLocationPicker}from"./ams/SubLocationPicker";export{NetworkProvider,useNetwork}from"./network/NetworkProvider";export{NetworkPanel}from"./network/NetworkPanel";export{StagedChangeBanner}from"./network/StagedChangeBanner";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResultHistoryTable.d.ts","sourceRoot":"","sources":["../../../src/components/tis/ResultHistoryTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkD,MAAM,OAAO,CAAC;AAWvE,OAAO,0BAA0B,CAAC;AAElC,MAAM,WAAW,uBAAuB;IACpC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAyCD,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,
|
|
1
|
+
{"version":3,"file":"ResultHistoryTable.d.ts","sourceRoot":"","sources":["../../../src/components/tis/ResultHistoryTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkD,MAAM,OAAO,CAAC;AAWvE,OAAO,0BAA0B,CAAC;AAElC,MAAM,WAAW,uBAAuB;IACpC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAyCD,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CA2gBhE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React,{useState,useEffect,useContext,useRef}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Button}from"primereact/button";import{Badge}from"primereact/badge";import{Menu}from"primereact/menu";import{confirmDialog}from"primereact/confirmdialog";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";import{SampleSummaryPanel}from"./SampleSummaryPanel";import"./ResultHistoryTable.css";const downloadCsvBlob=(e,t)=>{const o=new Blob([t],{type:"text/csv;charset=utf-8;"}),i=URL.createObjectURL(o),s=document.createElement("a");s.href=i,s.download=e,document.body.appendChild(s),s.click(),s.remove(),URL.revokeObjectURL(i)},downloadViaUrl=(e,t)=>{const o=document.createElement("a");o.href=e,o.download=t,document.body.appendChild(o),o.click(),o.remove()};export const ResultHistoryTable=e=>{const t=useTis(),o=e.projectId??t.selection.projectId,i=e.methodId,[s,r]=useState([]),[a,n]=useState(!1),[l,d]=useState(null),[c,p]=useState(null),[u,m]=useState(null),f=useRef(null),[_,
|
|
1
|
+
import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React,{useState,useEffect,useContext,useRef}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Button}from"primereact/button";import{Badge}from"primereact/badge";import{Menu}from"primereact/menu";import{confirmDialog}from"primereact/confirmdialog";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";import{SampleSummaryPanel}from"./SampleSummaryPanel";import"./ResultHistoryTable.css";const downloadCsvBlob=(e,t)=>{const o=new Blob([t],{type:"text/csv;charset=utf-8;"}),i=URL.createObjectURL(o),s=document.createElement("a");s.href=i,s.download=e,document.body.appendChild(s),s.click(),s.remove(),URL.revokeObjectURL(i)},downloadViaUrl=(e,t)=>{const o=document.createElement("a");o.href=e,o.download=t,document.body.appendChild(o),o.click(),o.remove()};export const ResultHistoryTable=e=>{const t=useTis(),o=e.projectId??t.selection.projectId,i=e.methodId,[s,r]=useState([]),[a,n]=useState(!1),[l,d]=useState(null),[c,p]=useState(null),[u,m]=useState(null),f=useRef(null),[_,y]=useState(null),[h,b]=useState(null),{invoke:g}=useContext(EventEmitterContext),x=async()=>{if(o){n(!0);try{const e={project_id:o};i&&(e.method_id=i);const t=await g("tis.list_tests",MessageType.Request,e);t.success&&t.data&&t.data.tests&&r(t.data.tests)}catch(e){}n(!1)}else r([])};useEffect(()=>{x()},[o,i,t.state.activeRunId,t.state.active]);const v=async(e,t)=>{const s=e?.run_id,r=e?.method_id??i;if(!s||!r||!o)return;const a="report"===t?"tis.export_test_csv":"tis.export_test_data_csv",n="report"===t?"test report":"test data";d({runId:s,kind:t});try{const e=await g(a,MessageType.Request,{project_id:o,method_id:r,run_id:s});if(!e?.success)return void alert(`Failed to build ${n} for ${s}`+(e?.error_message?`: ${e.error_message}`:""));const i="string"==typeof e.data?.filename&&e.data.filename?e.data.filename:`${o}_${r}_${s}_${t}.csv`;if("report"===t){const t="string"==typeof e.data?.csv?e.data.csv:"";if(!t)return void alert(`${n} for ${s} is empty.`);downloadCsvBlob(i,t)}else{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 ${n}.`);downloadViaUrl(t,i)}}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{d(null)}},w=async(e,t)=>{const s=e?.run_id,r=e?.method_id??i;if(!s||!r||!o)return;const a="complete"===t?"tis.finish_test":"abandon"===t?"tis.reset_test":"tis.resume_test";b(s);try{const e=await g(a,MessageType.Request,{project_id:o,method_id:r,run_id:s});if(!e?.success)return void alert(`Could not ${t} ${s}`+(e?.error_message?`:\n\n${e.error_message}`:""));await x()}catch(e){alert(`${t} failed: ${e instanceof Error?e.message:String(e)}`)}finally{b(null)}},j=async e=>{if(!o)return;const t="report"===e?"tis.export_project_csv":"tis.export_project_zip",i="report"===e?"project report":"project archive";p(e);try{const s=await g(t,MessageType.Request,{project_id:o});if(!s?.success)return void alert(`Failed to build ${i}`+(s?.error_message?`: ${s.error_message}`:""));const r="string"==typeof s.data?.download_url?s.data.download_url:"";if(!r)return void alert(`Server did not return a download URL for ${i}.`);const a="report"===e?`${o}_project_report.csv`:`${o}_project_archive.zip`,n="string"==typeof s.data?.filename&&s.data.filename?s.data.filename:a;downloadViaUrl(r,n)}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{p(null)}},C=e=>{const t="string"==typeof e?.sample_id?e.sample_id:"";if(t)return t;const o=e?.config;return o&&"object"==typeof o&&"string"==typeof o.sample_id?o.sample_id:""};return _jsxs("div",{style:{width:"100%",maxWidth:"100%",overflow:"hidden",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:o?`Test History: ${o}${i?` / ${i}`:""}`:"Test History (no project selected)"}),_jsxs("div",{style:{display:"flex",gap:"0.4rem",alignItems:"center"},children:[_jsx(Button,{icon:"report"===c?"pi pi-spin pi-spinner":"pi pi-file",label:"Download Report",size:"small",outlined:!0,disabled:!o||null!==c,onClick:()=>j("report"),tooltip:"Download a CSV report of every test in this project",tooltipOptions:{position:"bottom"}}),_jsx(Button,{icon:"archive"===c?"pi pi-spin pi-spinner":"pi pi-box",label:"Download Archive",size:"small",outlined:!0,disabled:!o||null!==c,onClick:()=>j("archive"),tooltip:"Download a ZIP of the entire project directory (all tests, raw data, configs)",tooltipOptions:{position:"bottom"}}),_jsx(Button,{icon:"pi pi-refresh",label:"Refresh",size:"small",onClick:x,disabled:a})]})]}),_jsx(SampleSummaryPanel,{projectId:o,methodId:i}),_jsxs(DataTable,{value:s,loading:a,paginator:!0,rows:10,emptyMessage:"No tests found.",scrollable:!0,scrollHeight:"flex",tableStyle:{minWidth:0},style:{width:"100%"},rowClassName:e=>!0===e?.excluded?"tis-row-excluded":"",selectionMode:"single",onSelectionChange:e=>{const o=e.value;o?.run_id&&t.setSelection({projectId:o.project_id??null,methodId:o.method_id??null,runId:o.run_id})},children:[_jsx(Column,{header:"Sample ID",sortable:!0,body:C,sortFunction:e=>{const t=[...e.data];return t.sort((t,o)=>C(t).localeCompare(C(o))*(e.order??1)),t},style:{minWidth:"8rem"}}),_jsx(Column,{field:"start_time",header:"Date/Time",sortable:!0,body:e=>{return(t=e.start_time)?new Date(t).toLocaleString():"";var t},style:{minWidth:"12rem"}}),_jsx(Column,{field:"method_id",header:"Test Method",sortable:!0,style:{minWidth:"10rem"}}),_jsx(Column,{header:"Status",field:"status",sortable:!0,body:e=>{const t=(e=>{if(!0===e?.finalized)return{label:"Abandoned",severity:"danger"};const t="string"==typeof e?.status?e.status:"completed";if("completed"===t&&!0===e?.auto_closed)return{label:"Interrupted",severity:"warning"};switch(t){case"in_progress":return{label:"In Progress",severity:"info"};case"paused":return{label:"Paused",severity:"warning"};case"completed":return{label:"Completed",severity:"success"};default:return{label:t,severity:void 0}}})(e);return _jsx(Badge,{value:t.label,severity:t.severity})},style:{minWidth:"6rem"}}),_jsx(Column,{header:"Incl.",align:"center",alignHeader:"center",style:{width:"4.5rem"},body:e=>{const t=!0===e?.excluded,s=u===e.run_id;return _jsx(Button,{icon:s?"pi pi-spin pi-spinner":t?"pi pi-eye-slash":"pi pi-check",rounded:!0,text:!0,size:"small",severity:t?"warning":"success",disabled:null!==u,onClick:()=>(async e=>{const t=e?.run_id,s=e?.method_id??i;if(!t||!s||!o)return;const r=!0!==e?.excluded;m(t);try{const e=await g("tis.set_test_excluded",MessageType.Request,{project_id:o,method_id:s,run_id:t,excluded:r});if(!e?.success)return void alert(`Failed to ${r?"exclude":"include"} ${t}`+(e?.error_message?`: ${e.error_message}`:""));await x()}catch(e){alert(`Exclude toggle failed: ${e instanceof Error?e.message:String(e)}`)}finally{m(null)}})(e),"aria-label":t?"Excluded — click to include":"Included — click to exclude",tooltip:t?"Excluded from cross-test results — click to include":"Included in cross-test results — click to exclude",tooltipOptions:{position:"left"}})}}),_jsx(Column,{header:"Download",style:{width:"14rem"},body:e=>{const t=l?.runId===e.run_id&&"data"===l?.kind,o=l?.runId===e.run_id&&"report"===l?.kind,i=null!==l;return _jsxs("div",{style:{display:"flex",gap:"0.4rem"},children:[_jsx(Button,{icon:t?"pi pi-spin pi-spinner":"pi pi-download",label:"Raw",size:"small",outlined:!0,disabled:i,onClick:()=>v(e,"data"),tooltip:"Download raw + filtered trace data as one CSV",tooltipOptions:{position:"left"}}),_jsx(Button,{icon:o?"pi pi-spin pi-spinner":"pi pi-file",label:"Results",size:"small",outlined:!0,disabled:i,onClick:()=>v(e,"report"),tooltip:"Download a CSV report (metadata + cycles + results) for this test",tooltipOptions:{position:"left"}})]})}}),_jsx(Column,{header:"",style:{width:"3rem"},body:e=>_jsx(Button,{icon:h===e.run_id?"pi pi-spin pi-spinner":"pi pi-ellipsis-v",text:!0,rounded:!0,size:"small",disabled:null!==h,"aria-label":"Test actions",tooltip:"Complete / abandon / reopen this test",tooltipOptions:{position:"left"},onClick:t=>{y(e),f.current?.toggle(t)}})})]}),_jsx(Menu,{model:_?(e=>{const t=e?.status,o=!0===e?.finalized,i=[];return"in_progress"===t||"paused"===t?(i.push({label:"Complete Test",icon:"pi pi-check-circle",command:()=>w(e,"complete")}),i.push({label:"Abandon Test",icon:"pi pi-ban",command:()=>confirmDialog({header:"Abandon Test",message:`Mark ${e?.sample_id||e?.run_id} as abandoned? Recorded data is kept, but the test can no longer be reopened.`,icon:"pi pi-exclamation-triangle",acceptClassName:"p-button-danger",acceptLabel:"Abandon",accept:()=>w(e,"abandon")})})):"completed"!==t||o?i.push({label:o?"Abandoned — cannot reopen":"No actions available",icon:"pi pi-info-circle",disabled:!0}):i.push({label:"Reopen Test",icon:"pi pi-replay",command:()=>confirmDialog({header:"Reopen Test",message:`Reopen ${e?.sample_id||e?.run_id} so new cycles are added to it? Cycle numbering continues from where it stopped. This changes where data is recorded — it does not start the machine.`,icon:"pi pi-replay",acceptLabel:"Reopen",accept:()=>w(e,"reopen")})}),i})(_):[],popup:!0,ref:f})]})};
|
|
@@ -31,6 +31,20 @@ export interface ScienceTableProps {
|
|
|
31
31
|
showIndex?: boolean;
|
|
32
32
|
/** Header for the index column. Default "#". */
|
|
33
33
|
indexHeader?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Which way the row index counts.
|
|
36
|
+
*
|
|
37
|
+
* `'ascending'` (default) numbers top-to-bottom, 1..n — right for a table
|
|
38
|
+
* whose first row is its first record.
|
|
39
|
+
*
|
|
40
|
+
* `'descending'` numbers n..1, so the TOP row carries the highest number.
|
|
41
|
+
* Use it whenever `rows` is newest-first: the number then reads as "this is
|
|
42
|
+
* cycle 47 of 47" instead of restarting at 1 on every new arrival, which is
|
|
43
|
+
* what an operator watching a running test expects. Getting this wrong is
|
|
44
|
+
* actively misleading — an ascending index over newest-first rows labels the
|
|
45
|
+
* newest cycle "1" and the oldest one "47".
|
|
46
|
+
*/
|
|
47
|
+
indexOrder?: 'ascending' | 'descending';
|
|
34
48
|
emptyMessage?: string;
|
|
35
49
|
/** Enable virtual scrolling; pair with `scrollHeight` for large runs. */
|
|
36
50
|
scrollable?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScienceTable.d.ts","sourceRoot":"","sources":["../../../src/components/tis/ScienceTable.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,oBAAoB,CAAC;AAE5B,MAAM,WAAW,aAAa;IAC1B,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,+DAA+D;IAC/D,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,KAAK,CAAC,SAAS,CAAC;IACrC,iDAAiD;IACjD,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAC9B,oEAAoE;IACpE,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,6EAA6E;IAC7E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9E;AASD,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,
|
|
1
|
+
{"version":3,"file":"ScienceTable.d.ts","sourceRoot":"","sources":["../../../src/components/tis/ScienceTable.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,oBAAoB,CAAC;AAE5B,MAAM,WAAW,aAAa;IAC1B,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,+DAA+D;IAC/D,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,KAAK,CAAC,SAAS,CAAC;IACrC,iDAAiD;IACjD,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAC9B,oEAAoE;IACpE,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,6EAA6E;IAC7E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EAAE,WAAW,GAAG,YAAY,CAAC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9E;AASD,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAmGpD,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{InputText}from"primereact/inputtext";import"./ScienceTable.css";const HeaderCell=({column:e})=>_jsxs("div",{className:"ac-sci-th",children:[_jsx("span",{className:"ac-sci-th-name",children:e.label}),e.units?_jsxs("span",{className:"ac-sci-th-unit",children:["[",e.units,"]"]}):null]});export const ScienceTable=({title:e,columns:a,rows:l,showIndex:s=!0,indexHeader:t="#",
|
|
1
|
+
import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{InputText}from"primereact/inputtext";import"./ScienceTable.css";const HeaderCell=({column:e})=>_jsxs("div",{className:"ac-sci-th",children:[_jsx("span",{className:"ac-sci-th-name",children:e.label}),e.units?_jsxs("span",{className:"ac-sci-th-unit",children:["[",e.units,"]"]}):null]});export const ScienceTable=({title:e,columns:a,rows:l,showIndex:s=!0,indexHeader:t="#",indexOrder:i="ascending",emptyMessage:c="No data.",scrollable:n=!1,scrollHeight:r,virtualItemSize:o=38,onCellEdit:d})=>{const m=n&&!!r,u=!!d&&a.some(e=>e.editable);return _jsxs("div",{className:"ac-science-table-wrap",children:[null!=e&&_jsx("div",{className:"ac-science-table-title",children:e}),_jsx("div",{className:"ac-science-table-scroll",children:_jsxs(DataTable,{className:"ac-science-table",value:l,scrollable:n,scrollHeight:r,virtualScrollerOptions:m?{itemSize:o}:void 0,emptyMessage:c,editMode:u?"cell":void 0,children:[s?_jsx(Column,{header:t,headerClassName:"ac-sci-index",bodyClassName:"ac-sci-index",align:"center",alignHeader:"center",style:{width:"3.5rem",maxWidth:"3.5rem"},body:(e,a)=>"descending"===i?l.length-a.rowIndex:a.rowIndex+1}):null,a.map(e=>{const a=!!d&&!!e.editable;return _jsx(Column,{field:e.field,header:_jsx(HeaderCell,{column:e}),align:e.align??"center",alignHeader:"center",style:e.style,bodyClassName:a?"ac-sci-cell ac-sci-cell-editable":"ac-sci-cell",body:e.body?a=>e.body(a):void 0,editor:a?e=>_jsx(InputText,{value:e.value??"",autoFocus:!0,style:{width:"100%"},onChange:a=>e.editorCallback?.(a.target.value),onKeyDown:e=>e.stopPropagation()}):void 0,onCellEditComplete:a?a=>{const l=a.rowData?.[e.field];let s=a.newValue;if("number"==typeof l&&""!==s&&null!=s){const e=Number(s);Number.isNaN(e)||(s=e)}s!==l&&d(a.rowData,e.field,s)}:void 0},e.field)})]})})]})};export default ScienceTable;
|
|
@@ -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;
|
|
@@ -84,6 +98,17 @@ export interface ChartView {
|
|
|
84
98
|
* count. Ignored when `smooth` is false.
|
|
85
99
|
*/
|
|
86
100
|
smoothWindow?: number;
|
|
101
|
+
/**
|
|
102
|
+
* Maximum decimal places shown on X-axis tick labels. Default
|
|
103
|
+
* [`DEFAULT_X_DECIMALS`].
|
|
104
|
+
*
|
|
105
|
+
* Without a cap the labels print the stored double verbatim, so a value
|
|
106
|
+
* that is only *nearly* round — 0.30000000000000004, the ordinary result of
|
|
107
|
+
* accumulating a 0.1 step — renders as a 17-character tick and the axis
|
|
108
|
+
* becomes unreadable. This is a display cap only: the underlying data,
|
|
109
|
+
* table, CSV export and every calculation still see the full value.
|
|
110
|
+
*/
|
|
111
|
+
x_decimals?: number;
|
|
87
112
|
/**
|
|
88
113
|
* Optional shaded X-range bands drawn over the plot, declared per view
|
|
89
114
|
* in project.json (static). These are drawn on every cycle.
|
|
@@ -159,4 +184,10 @@ export interface TestDataViewProps {
|
|
|
159
184
|
chartHeight?: string;
|
|
160
185
|
}
|
|
161
186
|
export declare const TestDataView: React.FC<TestDataViewProps>;
|
|
187
|
+
/**
|
|
188
|
+
* Decimal places on X-axis tick labels when a view doesn't say otherwise.
|
|
189
|
+
* Two is enough to separate adjacent ticks on every axis this renders
|
|
190
|
+
* (time in seconds, position in mm, load in N) without printing float noise.
|
|
191
|
+
*/
|
|
192
|
+
export declare const DEFAULT_X_DECIMALS = 2;
|
|
162
193
|
//# sourceMappingURL=TestDataView.d.ts.map
|
|
@@ -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;
|
|
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,o=e.schema??(a?t.schemas[a]:void 0),{throttleMs:l=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,o)=>{const l=e?.cycle_index;if(r&&a&&n&&l)try{const e=await c("tis.update_cycle",MessageType.Request,{project_id:r,method_id:a,run_id:n,cycle_index:l,fields:{[t]:o}});if(!1===e?.success)return;h(e=>e.map(e=>e?.cycle_index===l?{...e,[t]:o}:e))}catch(e){}},[c,r,a,n]),S=useRef(null),[T,R]=useState(null),[A,L]=useState(null),[M,E]=useState(!1),[D,I]=useState(null),[O,N]=useState(null),[k,F]=useState(!1),[H,P]=useState([]),[z,V]=useState(null),B=useMemo(()=>{const e=[];for(const[t,r]of Object.entries(o?.views??{}))e.push({name:t,view:r});return e},[o]),[$,G]=useState(B.length>0?B[0].name:null);useEffect(()=>{if(0===B.length)return;null!==$&&B.some(e=>e.name===$)||G(B[0].name)},[B,$]);const W=B.find(e=>e.name===$)?.view,q="raw_trace"===W?.type,U=useRef([]),J=useRef(null),X=useRef(null),Y=()=>{X.current||(X.current=setTimeout(()=>{if(X.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)},l))};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 o=await c("tis.read_cycles",MessageType.Request,{project_id:r,method_id:a,run_id:n,offset:0,limit:200,order:"desc"});!e&&o?.success&&h(o.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),Y())}),o=d("tis.results_updated",t=>{e(t)&&(J.current=t.results??{},Y())});return()=>{u(t),u(o),X.current&&(clearTimeout(X.current),X.current=null)}},[r,a,n,l]);const Z="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||!Z)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:Z,run_id:n});!e&&t?.success&&x(t.data?.summary??{})}catch(e){}};t();const o=[d("tis.sample_summary_updated",e=>{e?.project_id===r&&e?.method_id===a&&e?.sample_id===Z&&t()}),d("tis.cycle_added",e=>{e?.project_id===r&&e?.method_id===a&&t()})];return()=>{e=!0,o.forEach(u)}},[r,a,Z,n,c,d,u]);const K=useRawCycleData({projectId:r,methodId:a,runId:n,blobName:o?.raw_data?.blob_name??"trace",source:W?.source??"raw",enabled:q}),Q=useMemo(()=>{if(!W)return null;if("cycle_scatter"===W.type){const e=W.x.field;if(!e)return null;const t=[...y].reverse(),r=W.x_decimals??DEFAULT_X_DECIMALS;return{labels:t.map(t=>formatAxisTick(t[e],r)),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(!K.raw)return null;const e=W.x.column;if(!e)return null;const t=K.raw[e]??[],r=!1!==W.smooth;return{datasets:W.y.map((e,a)=>{const n=K.raw[e.column]??[],o=r?smoothTraceForDisplay(t,n,W.smoothWindow):n.map((e,r)=>({x:t[r],y:e}));return{label:e.label??e.column,data:o,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,K.raw]),ee=W?.y.some(e=>"right"===e.y_axis)??!1,te=useMemo(()=>{if(!q)return[];const e=K.envelope,t=e?.regions??e?.context?.regions;return Array.isArray(t)?t:[]},[q,K.envelope]),re=useMemo(()=>[...W?.regions??[],...te],[W,te]),ae=useMemo(()=>{const e="raw_trace"===W?.type,t=W?.x_decimals??DEFAULT_X_DECIMALS,r={callback(r){const a=e?r:this?.getLabelForValue?.(r)??r;return formatAxisTick(a,t)}};return{responsive:!0,maintainAspectRatio:!1,parsing:!e&&void 0,scales:{x:e?{type:"linear",ticks:r,title:{display:!!W?.x.label,text:W?.x.label}}:{ticks:r,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=o?.raw_data?.blob_name??"trace",oe=useRef(""),le=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(oe.current===t)return;oe.current=t;const o={project_id:r,method_id:a,run_id:n,name:ne};null!=e&&(o.cycle_index=e),E(!0),L(null),R(null);try{const e=await c("tis.read_raw",MessageType.Request,o);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{E(!1)}F(!0),N(null),I(null);try{const e=await c("tis.read_filtered",MessageType.Request,o);e?.success?I(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(()=>{oe.current="",P([]),V(null)},[r,a,n,ne]);return useEffect(()=>{b&&null!=z&&se(z)},[b,z,se]),r&&a&&n&&o?_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:!!o.raw_data,onViewRaw:async()=>{j(!0);let e=H;0===e.length&&(e=await le(),P(e));const t=e.length>0?e[e.length-1]:null,r=z??t;z!==r&&V(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:B.map(e=>({label:e.view.title??e.name,value:e.name})),onChange:e=>G(e.value),placeholder:0===B.length?"No view defined":"Select a view",disabled:0===B.length}),_jsx("h3",{style:{margin:0},children:W?.title??""}),q&&K.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:K.selectedCycle,options:K.cycles.map(e=>({label:`Cycle ${e}`,value:e})),onChange:e=>K.setSelectedCycle(Number(e.value)),style:{minWidth:"8rem"}}),_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:["of ",K.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:!Q,tooltip:"Reset chart zoom",tooltipOptions:{position:"left"},"aria-label":"Reset chart zoom"})]}),_jsxs("div",{style:{height:i,position:"relative"},children:[q&&K.loading&&_jsx(ChartOverlay,{children:"Loading raw data…"}),q&&K.error&&_jsx(ChartOverlay,{children:K.error}),Q&&_jsx(Line,{ref:S,data:Q,options:ae})]})]}),(()=>{const e=y.length>CYCLE_VIRTUAL_THRESHOLD,t=[...(o.cycle_config_fields??[]).map(e=>toScienceColumn(e,!0)),...(o.cycle_fields??[]).map(e=>toScienceColumn(e,!!e.editable))];return _jsx(ScienceTable,{title:`Cycle Data (${y.length})`,columns:t,rows:y,indexOrder:"descending",scrollable:e,scrollHeight:e?s:void 0,emptyMessage:"No cycles yet.",onCellEdit:C})})(),(()=>{const e={...f,..._},t=Object.keys(e).length>0,r=o.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."})]})})(),o.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:V}),_jsx(RawEnvelopeHeader,{envelope:T}),_jsxs(TabView,{style:{height:"100%"},children:[_jsx(TabPanel,{header:"Raw Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(T),loading:M,error:A,rawData:o.raw_data})}),_jsx(TabPanel,{header:"Filtered Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(D),loading:k,error:O,rawData:o.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:o,onViewRaw:l,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()]})]})]}),o&&_jsx(Button,{icon:"pi pi-table",label:"View Raw Data",onClick:l,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 o=Object.keys(a.columns??{}),l=Object.keys(e).filter(t=>Array.isArray(e[t])),s=[];for(const t of o)Array.isArray(e[t])&&s.push(t);for(const e of l)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 o=0;o<2*t;o++)r[a][o]-=n*r[e][o]}}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 o=a+1,l=Array.from({length:o},()=>new Array(o).fill(0));for(let e=0;e<o;e++)for(let t=0;t<o;t++){let a=0;for(let o=0;o<r;o++)a+=n[o][e]*n[o][t];l[e][t]=a}const s=invertMatrix(l);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<o;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},o=new Array(r);for(let a=0;a<r;a++){const l=Math.min(t,a,r-1-a);if(l<1){o[a]=e[a];continue}const s=n(l);let i=0;for(let t=-l;t<=l;t++)i+=s[t+l]*e[a+t];o[a]=i}return o}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))),o=savitzkyGolay(t.slice(0,a),n),l=Math.max(1,Math.ceil(a/SMOOTH_TARGET_POINTS)),s=[];for(let t=0;t<a;t+=l)s.push({x:e[t],y:o[t]});return(a-1)%l!=0&&s.push({x:e[a-1],y:o[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});export const DEFAULT_X_DECIMALS=2;const formatAxisTick=(e,t)=>{const r="number"==typeof e?e:Number(e);return Number.isFinite(r)?Number.parseFloat(r.toFixed(t)).toString():null==e?"":String(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":"ChartViewDialog.d.ts","sourceRoot":"","sources":["../../../../src/components/tis-editor/editor/ChartViewDialog.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ChartViewDialog.d.ts","sourceRoot":"","sources":["../../../../src/components/tis-editor/editor/ChartViewDialog.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAA0B,SAAS,EAAE,MAAM,UAAU,CAAC;AAiBlE,MAAM,WAAW,oBAAoB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,CAAA;KAAE,GAAG,IAAI,CAAC;IACpD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IAClD,iEAAiE;IACjE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,iEAAiE;IACjE,UAAU,EAAE,MAAM,EAAE,CAAC;CACxB;AAQD,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA0J1D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as _jsx,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{Dropdown}from"primereact/dropdown";import{FormRow}from"../../forms/FormRow";const VIEW_TYPES=[{label:"Cycle scatter",value:"cycle_scatter"},{label:"Raw trace",value:"raw_trace"}],Y_AXES=[{label:"Left",value:"left"},{label:"Right",value:"right"}],blank=()=>({title:"",type:"cycle_scatter",x:{field:"",label:""},y:[]});export const ChartViewDialog=({visible:e,initial:
|
|
1
|
+
import{jsx as _jsx,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{Dropdown}from"primereact/dropdown";import{InputNumber}from"primereact/inputnumber";import{FormRow}from"../../forms/FormRow";import{DEFAULT_X_DECIMALS}from"../../tis/TestDataView";const MAX_X_DECIMALS=8,VIEW_TYPES=[{label:"Cycle scatter",value:"cycle_scatter"},{label:"Raw trace",value:"raw_trace"}],Y_AXES=[{label:"Left",value:"left"},{label:"Right",value:"right"}],blank=()=>({title:"",type:"cycle_scatter",x:{field:"",label:""},y:[]});export const ChartViewDialog=({visible:e,initial:l,onCancel:t,onSave:a,knownKeys:i,siblingIds:n})=>{const[s,r]=useState(""),[o,u]=useState(blank()),[m,c]=useState(null);useEffect(()=>{e&&(r(l?.viewId??""),u(l?JSON.parse(JSON.stringify(l.view)):blank()),c(null))},[e,l]);const d="raw_trace"===o.type,p=d?"column":"field",x=e=>{u({...o,x:{...o.x,...e}})},_=(e,l)=>{const t=[...o.y];t[e]={...t[e],...l},u({...o,y:t})};return _jsxs(Dialog,{header:l?`Edit view: ${l.viewId}`:"New chart view",visible:e,onHide:t,style:{width:"42rem"},children:[m&&_jsx("div",{style:{color:"#dc2626",marginBottom:"0.5rem"},children:m}),_jsx(FormRow,{label:"View ID",required:!0,hint:"Stable key (e.g. cof_scatter). Cannot collide with other views.",children:_jsx(InputText,{value:s,onChange:e=>r(e.target.value)})}),_jsx(FormRow,{label:"Title",children:_jsx(InputText,{value:o.title??"",onChange:e=>u({...o,title:e.target.value})})}),_jsx(FormRow,{label:"Type",required:!0,children:_jsx(Dropdown,{value:o.type,options:VIEW_TYPES,onChange:e=>u({...o,type:e.value})})}),_jsx(FormRow,{label:d?"X column":"X field",required:!0,children:_jsx(InputText,{value:o.x[p]??"",onChange:e=>x({[p]:e.target.value}),list:"known-keys"})}),_jsx(FormRow,{label:"X label",children:_jsx(InputText,{value:o.x.label??"",onChange:e=>x({label:e.target.value})})}),_jsx(FormRow,{label:"X decimals",hint:`Decimal places on X-axis tick labels. Blank uses the default (${DEFAULT_X_DECIMALS}). Display only — stored data keeps full precision.`,children:_jsx(InputNumber,{value:o.x_decimals??null,onValueChange:e=>{const l={...o};null==e.value?delete l.x_decimals:l.x_decimals=e.value,u(l)},min:0,max:8,placeholder:String(DEFAULT_X_DECIMALS),showButtons:!0})}),_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",margin:"1rem 0 0.5rem"},children:[_jsx("strong",{children:"Y series"}),_jsx(Button,{label:"Add series",icon:"pi pi-plus",size:"small",onClick:()=>{u({...o,y:[...o.y,{[p]:"",label:"",y_axis:"left"}]})}})]}),0===o.y.length&&_jsx("small",{children:"No series — add at least one."}),o.y.map((e,l)=>_jsxs("div",{style:{display:"grid",gridTemplateColumns:"1fr 1fr 6rem 2rem",gap:"0.5rem",marginBottom:"0.25rem",alignItems:"center"},children:[_jsx(InputText,{placeholder:d?"column":"field",value:e[p]??"",onChange:e=>_(l,{[p]:e.target.value}),list:"known-keys"}),_jsx(InputText,{placeholder:"label",value:e.label??"",onChange:e=>_(l,{label:e.target.value})}),_jsx(Dropdown,{value:e.y_axis??"left",options:Y_AXES,onChange:e=>_(l,{y_axis:e.value})}),_jsx(Button,{icon:"pi pi-trash",className:"p-button-text p-button-danger p-button-sm",onClick:()=>(e=>{u({...o,y:o.y.filter((l,t)=>t!==e)})})(l),"aria-label":"Remove series"})]},l)),_jsx("datalist",{id:"known-keys",children:i.map(e=>_jsx("option",{value:e},e))}),_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=s.trim()?n.includes(s)&&s!==l?.viewId?`A view named "${s}" already exists.`:null:"View ID is required.";e?c(e):a(s,o)}})]})]})};
|
|
@@ -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,
|
|
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"}],
|
|
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;
|
|
@@ -63,6 +76,12 @@ export interface ChartView {
|
|
|
63
76
|
type: ChartViewType | string;
|
|
64
77
|
x: ChartAxis;
|
|
65
78
|
y: ChartSeries[];
|
|
79
|
+
/**
|
|
80
|
+
* Max decimal places on X-axis tick labels (display only; default 2).
|
|
81
|
+
* See `ChartView.x_decimals` in TestDataView for why an uncapped axis
|
|
82
|
+
* prints 17-digit ticks.
|
|
83
|
+
*/
|
|
84
|
+
x_decimals?: number;
|
|
66
85
|
}
|
|
67
86
|
export type RawColumnSource = 'time' | 'derived' | string;
|
|
68
87
|
export interface RawColumn {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/tis-editor/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,SAAS,GACf,QAAQ,GACR,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAC7B,KAAK,GAAG,KAAK,GACb,MAAM,CAAC;AAEb,MAAM,WAAW,SAAS;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;6DAEyD;IACzD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;2EAEuE;IACvE,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B;;yDAEqD;IACrD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;wBAGoB;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,MAAM,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;AAC/D,MAAM,MAAM,OAAO,GAAG,cAAc,GAAG,SAAS,CAAC;AAEjD,MAAM,WAAW,aAAa;IAC1B,oDAAoD;IACpD,EAAE,EAAE,KAAK,CAAC;IACV;mEAC+D;IAC/D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,2EAA2E;IAC3E,IAAI,CAAC,EAAE,OAAO,CAAC;
|
|
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
package/src/components/index.ts
CHANGED
|
@@ -62,7 +62,7 @@ export type { ResultHistoryTableProps } from './tis/ResultHistoryTable';
|
|
|
62
62
|
export { SampleSummaryPanel } from './tis/SampleSummaryPanel';
|
|
63
63
|
export type { SampleSummaryPanelProps } from './tis/SampleSummaryPanel';
|
|
64
64
|
|
|
65
|
-
export { TestDataView }
|
|
65
|
+
export { TestDataView, DEFAULT_X_DECIMALS } from './tis/TestDataView';
|
|
66
66
|
export type { TestDataViewProps, ChartAxis, ChartSeries, ChartView, RawDataShape } from './tis/TestDataView';
|
|
67
67
|
|
|
68
68
|
export { TestRawDataView } from './tis/TestRawDataView';
|
|
@@ -377,6 +377,13 @@ export const ResultHistoryTable: React.FC<ResultHistoryTableProps> = (props) =>
|
|
|
377
377
|
const statusOf = (rowData: any): { label: string; severity: 'success' | 'info' | 'warning' | 'danger' | undefined } => {
|
|
378
378
|
if (rowData?.finalized === true) return { label: 'Abandoned', severity: 'danger' };
|
|
379
379
|
const status = typeof rowData?.status === 'string' ? rowData.status : 'completed';
|
|
380
|
+
// A run TIS closed at startup because the previous process stopped
|
|
381
|
+
// mid-test. It is closed (not "In Progress"), but the operator never
|
|
382
|
+
// finished it, so calling it "Completed" would overstate what happened.
|
|
383
|
+
// Still reopenable — `finalized` is deliberately not set by that sweep.
|
|
384
|
+
if (status === 'completed' && rowData?.auto_closed === true) {
|
|
385
|
+
return { label: 'Interrupted', severity: 'warning' };
|
|
386
|
+
}
|
|
380
387
|
switch (status) {
|
|
381
388
|
case 'in_progress': return { label: 'In Progress', severity: 'info' };
|
|
382
389
|
case 'paused': return { label: 'Paused', severity: 'warning' };
|
|
@@ -52,6 +52,20 @@ export interface ScienceTableProps {
|
|
|
52
52
|
showIndex?: boolean;
|
|
53
53
|
/** Header for the index column. Default "#". */
|
|
54
54
|
indexHeader?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Which way the row index counts.
|
|
57
|
+
*
|
|
58
|
+
* `'ascending'` (default) numbers top-to-bottom, 1..n — right for a table
|
|
59
|
+
* whose first row is its first record.
|
|
60
|
+
*
|
|
61
|
+
* `'descending'` numbers n..1, so the TOP row carries the highest number.
|
|
62
|
+
* Use it whenever `rows` is newest-first: the number then reads as "this is
|
|
63
|
+
* cycle 47 of 47" instead of restarting at 1 on every new arrival, which is
|
|
64
|
+
* what an operator watching a running test expects. Getting this wrong is
|
|
65
|
+
* actively misleading — an ascending index over newest-first rows labels the
|
|
66
|
+
* newest cycle "1" and the oldest one "47".
|
|
67
|
+
*/
|
|
68
|
+
indexOrder?: 'ascending' | 'descending';
|
|
55
69
|
emptyMessage?: string;
|
|
56
70
|
/** Enable virtual scrolling; pair with `scrollHeight` for large runs. */
|
|
57
71
|
scrollable?: boolean;
|
|
@@ -80,6 +94,7 @@ export const ScienceTable: React.FC<ScienceTableProps> = ({
|
|
|
80
94
|
rows,
|
|
81
95
|
showIndex = true,
|
|
82
96
|
indexHeader = '#',
|
|
97
|
+
indexOrder = 'ascending',
|
|
83
98
|
emptyMessage = 'No data.',
|
|
84
99
|
scrollable = false,
|
|
85
100
|
scrollHeight,
|
|
@@ -111,7 +126,11 @@ export const ScienceTable: React.FC<ScienceTableProps> = ({
|
|
|
111
126
|
align="center"
|
|
112
127
|
alignHeader="center"
|
|
113
128
|
style={{ width: '3.5rem', maxWidth: '3.5rem' }}
|
|
114
|
-
body={(_data: any, options: ColumnBodyOptions) =>
|
|
129
|
+
body={(_data: any, options: ColumnBodyOptions) =>
|
|
130
|
+
indexOrder === 'descending'
|
|
131
|
+
? rows.length - options.rowIndex
|
|
132
|
+
: options.rowIndex + 1
|
|
133
|
+
}
|
|
115
134
|
/>
|
|
116
135
|
) : null}
|
|
117
136
|
{columns.map((c) => {
|
|
@@ -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; }
|
|
@@ -131,6 +145,17 @@ export interface ChartView {
|
|
|
131
145
|
* count. Ignored when `smooth` is false.
|
|
132
146
|
*/
|
|
133
147
|
smoothWindow?: number;
|
|
148
|
+
/**
|
|
149
|
+
* Maximum decimal places shown on X-axis tick labels. Default
|
|
150
|
+
* [`DEFAULT_X_DECIMALS`].
|
|
151
|
+
*
|
|
152
|
+
* Without a cap the labels print the stored double verbatim, so a value
|
|
153
|
+
* that is only *nearly* round — 0.30000000000000004, the ordinary result of
|
|
154
|
+
* accumulating a 0.1 step — renders as a 17-character tick and the axis
|
|
155
|
+
* becomes unreadable. This is a display cap only: the underlying data,
|
|
156
|
+
* table, CSV export and every calculation still see the full value.
|
|
157
|
+
*/
|
|
158
|
+
x_decimals?: number;
|
|
134
159
|
/**
|
|
135
160
|
* Optional shaded X-range bands drawn over the plot, declared per view
|
|
136
161
|
* in project.json (static). These are drawn on every cycle.
|
|
@@ -434,23 +459,52 @@ export const TestDataView: React.FC<TestDataViewProps> = (props) => {
|
|
|
434
459
|
try {
|
|
435
460
|
const resp: any = await invoke(
|
|
436
461
|
'tis.sample_summary' as any, MessageType.Request as any,
|
|
437
|
-
|
|
462
|
+
// run_id names the test being viewed: `scope: "test"`
|
|
463
|
+
// fields reduce over it alone, `scope: "project"` fields
|
|
464
|
+
// still span every test of this sample.
|
|
465
|
+
{ project_id: projectId, method_id: methodId,
|
|
466
|
+
sample_id: sampleId, run_id: runId } as any);
|
|
438
467
|
if (!cancelled && resp?.success) setSampleSummary(resp.data?.summary ?? {});
|
|
439
468
|
} catch (e) {
|
|
440
469
|
console.error('[TestDataView] sample_summary fetch failed', e);
|
|
441
470
|
}
|
|
442
471
|
};
|
|
443
472
|
fetchSummary();
|
|
473
|
+
// Refetch rather than adopt the payload: the broadcast is computed
|
|
474
|
+
// with no current test (it serves the pooled Sample Summary view),
|
|
475
|
+
// so taking its numbers here would silently swap every test-scoped
|
|
476
|
+
// field for its project-wide value.
|
|
444
477
|
const onSummary = (payload: any) => {
|
|
445
478
|
if (payload?.project_id === projectId
|
|
446
479
|
&& payload?.method_id === methodId
|
|
447
480
|
&& payload?.sample_id === sampleId) {
|
|
448
|
-
|
|
481
|
+
void fetchSummary();
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
// Every cycle_fields aggregate — avg, min, max, stddev, count —
|
|
485
|
+
// moves the moment a cycle is recorded, but `add_cycle` fires no
|
|
486
|
+
// summary broadcast (`sample_summary_updated` covers a sibling run
|
|
487
|
+
// finishing or being excluded, and `update_results` recomputes the
|
|
488
|
+
// rollup server-side). So the Results panel sat on the rollup
|
|
489
|
+
// computed when the view mounted: the operator ran a cycle, watched
|
|
490
|
+
// the Cycle Data table grow a row, and every derived number stayed
|
|
491
|
+
// put until they left the tab and came back (gen4_ac, 2026-08-12).
|
|
492
|
+
//
|
|
493
|
+
// Refetching from the client rather than pushing from the server on
|
|
494
|
+
// every add_cycle is deliberate: the rollup rescans every run of the
|
|
495
|
+
// method off disk, and that cost should be paid only while someone
|
|
496
|
+
// is actually looking at the numbers.
|
|
497
|
+
const onCycleAdded = (payload: any) => {
|
|
498
|
+
if (payload?.project_id === projectId && payload?.method_id === methodId) {
|
|
499
|
+
void fetchSummary();
|
|
449
500
|
}
|
|
450
501
|
};
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
502
|
+
const ids = [
|
|
503
|
+
subscribe('tis.sample_summary_updated', onSummary),
|
|
504
|
+
subscribe('tis.cycle_added', onCycleAdded),
|
|
505
|
+
];
|
|
506
|
+
return () => { cancelled = true; ids.forEach(unsubscribe); };
|
|
507
|
+
}, [projectId, methodId, sampleId, runId, invoke, subscribe, unsubscribe]);
|
|
454
508
|
|
|
455
509
|
// -----------------------------------------------------------------
|
|
456
510
|
// Raw-trace data fetch (lazy)
|
|
@@ -480,7 +534,11 @@ export const TestDataView: React.FC<TestDataViewProps> = (props) => {
|
|
|
480
534
|
const xField = selectedViewDef.x.field;
|
|
481
535
|
if (!xField) return null;
|
|
482
536
|
const asc = [...cycles].reverse(); // state is newest-first; charts want oldest-first
|
|
483
|
-
|
|
537
|
+
// cycle_scatter uses a CATEGORY x axis, so chart.js prints each
|
|
538
|
+
// label verbatim — an unrounded double lands on the axis with all
|
|
539
|
+
// 17 of its digits. Round here, at the point the label is made.
|
|
540
|
+
const decimals = selectedViewDef.x_decimals ?? DEFAULT_X_DECIMALS;
|
|
541
|
+
const xs = asc.map(c => formatAxisTick(c[xField], decimals));
|
|
484
542
|
const datasets = selectedViewDef.y.map((s, idx) => ({
|
|
485
543
|
label: s.label ?? s.field,
|
|
486
544
|
data: asc.map(c => c[s.field!]),
|
|
@@ -550,6 +608,16 @@ export const TestDataView: React.FC<TestDataViewProps> = (props) => {
|
|
|
550
608
|
|
|
551
609
|
const chartOptions = useMemo(() => {
|
|
552
610
|
const isTrace = selectedViewDef?.type === 'raw_trace';
|
|
611
|
+
const xDecimals = selectedViewDef?.x_decimals ?? DEFAULT_X_DECIMALS;
|
|
612
|
+
// A linear axis picks its own tick values, and zooming in drives them
|
|
613
|
+
// to arbitrary precision — so cap them here too, not just on the
|
|
614
|
+
// category labels built in `chartData`.
|
|
615
|
+
const xTicks = {
|
|
616
|
+
callback(this: any, value: any) {
|
|
617
|
+
const raw = isTrace ? value : this?.getLabelForValue?.(value) ?? value;
|
|
618
|
+
return formatAxisTick(raw, xDecimals);
|
|
619
|
+
},
|
|
620
|
+
};
|
|
553
621
|
return {
|
|
554
622
|
responsive: true,
|
|
555
623
|
maintainAspectRatio: false,
|
|
@@ -561,8 +629,10 @@ export const TestDataView: React.FC<TestDataViewProps> = (props) => {
|
|
|
561
629
|
scales: {
|
|
562
630
|
x: isTrace
|
|
563
631
|
? { type: 'linear' as const,
|
|
632
|
+
ticks: xTicks,
|
|
564
633
|
title: { display: !!selectedViewDef?.x.label, text: selectedViewDef?.x.label } }
|
|
565
|
-
: {
|
|
634
|
+
: { ticks: xTicks,
|
|
635
|
+
title: { display: !!selectedViewDef?.x.label, text: selectedViewDef?.x.label } },
|
|
566
636
|
y: { position: 'left' as const,
|
|
567
637
|
title: { display: true, text: leftAxisLabel(selectedViewDef) } },
|
|
568
638
|
...(usesRightAxis ? {
|
|
@@ -811,22 +881,20 @@ export const TestDataView: React.FC<TestDataViewProps> = (props) => {
|
|
|
811
881
|
// server accepts cycle_config_fields on tis.update_cycle
|
|
812
882
|
// without an `editable` flag), while measured columns still
|
|
813
883
|
// 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
884
|
const cycleColumns: ScienceColumn[] = [
|
|
822
|
-
...(schema.cycle_config_fields ?? []).map(f =>
|
|
823
|
-
...(schema.cycle_fields ?? []).map(f =>
|
|
885
|
+
...(schema.cycle_config_fields ?? []).map(f => toScienceColumn(f, true)),
|
|
886
|
+
...(schema.cycle_fields ?? []).map(f => toScienceColumn(f, !!f.editable)),
|
|
824
887
|
];
|
|
825
888
|
return (
|
|
826
889
|
<ScienceTable
|
|
827
890
|
title={`Cycle Data (${cycles.length})`}
|
|
828
891
|
columns={cycleColumns}
|
|
829
892
|
rows={cycles}
|
|
893
|
+
// `cycles` is newest-first, so the index has to count
|
|
894
|
+
// down: the top row is the latest cycle and carries the
|
|
895
|
+
// highest number. Ascending here would label the newest
|
|
896
|
+
// cycle "1" and renumber every row on each arrival.
|
|
897
|
+
indexOrder="descending"
|
|
830
898
|
scrollable={useVirtual}
|
|
831
899
|
scrollHeight={useVirtual ? cycleTableHeight : undefined}
|
|
832
900
|
emptyMessage="No cycles yet."
|
|
@@ -842,19 +910,28 @@ export const TestDataView: React.FC<TestDataViewProps> = (props) => {
|
|
|
842
910
|
{(() => {
|
|
843
911
|
const values = { ...results, ...sampleSummary };
|
|
844
912
|
const hasValues = Object.keys(values).length > 0;
|
|
845
|
-
const
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
units: f.units,
|
|
849
|
-
body: (row) => formatCell(row[f.name], f.type, f.scale),
|
|
850
|
-
}));
|
|
913
|
+
const resultFields = schema.results_fields ?? [];
|
|
914
|
+
const resultColumns: ScienceColumn[] = resultFields.map(f => toScienceColumn(f));
|
|
915
|
+
const hasProjectScoped = resultFields.some(isProjectScoped);
|
|
851
916
|
return (
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
917
|
+
<>
|
|
918
|
+
<ScienceTable
|
|
919
|
+
title="Results"
|
|
920
|
+
columns={resultColumns}
|
|
921
|
+
rows={hasValues ? [values] : []}
|
|
922
|
+
emptyMessage="No results yet."
|
|
923
|
+
/>
|
|
924
|
+
{hasProjectScoped && (
|
|
925
|
+
<div style={{
|
|
926
|
+
fontSize: '0.8rem',
|
|
927
|
+
color: 'var(--text-secondary-color)',
|
|
928
|
+
margin: '0.25rem 0 0.75rem',
|
|
929
|
+
}}>
|
|
930
|
+
* across every test of this sample in the project.
|
|
931
|
+
All other values describe this test.
|
|
932
|
+
</div>
|
|
933
|
+
)}
|
|
934
|
+
</>
|
|
858
935
|
);
|
|
859
936
|
})()}
|
|
860
937
|
|
|
@@ -1421,6 +1498,26 @@ const ChartOverlay: React.FC<{ children: React.ReactNode }> = ({ children }) =>
|
|
|
1421
1498
|
// Axis labels work for both scatter (s.field) and raw_trace (s.column)
|
|
1422
1499
|
// series. Whichever the view declared, that's what's used as a label
|
|
1423
1500
|
// fallback when the explicit `label` is absent.
|
|
1501
|
+
/**
|
|
1502
|
+
* Decimal places on X-axis tick labels when a view doesn't say otherwise.
|
|
1503
|
+
* Two is enough to separate adjacent ticks on every axis this renders
|
|
1504
|
+
* (time in seconds, position in mm, load in N) without printing float noise.
|
|
1505
|
+
*/
|
|
1506
|
+
export const DEFAULT_X_DECIMALS = 2;
|
|
1507
|
+
|
|
1508
|
+
/**
|
|
1509
|
+
* Cap a tick label's decimals without padding it.
|
|
1510
|
+
*
|
|
1511
|
+
* `toFixed` alone would turn a clean 0, 1, 2 axis into "0.00", "1.00", "2.00";
|
|
1512
|
+
* re-parsing drops the trailing zeros, so this caps precision rather than
|
|
1513
|
+
* fixing it. Non-numeric labels (a category axis over strings) pass through.
|
|
1514
|
+
*/
|
|
1515
|
+
const formatAxisTick = (v: unknown, decimals: number): string => {
|
|
1516
|
+
const n = typeof v === 'number' ? v : Number(v);
|
|
1517
|
+
if (!Number.isFinite(n)) return v == null ? '' : String(v);
|
|
1518
|
+
return Number.parseFloat(n.toFixed(decimals)).toString();
|
|
1519
|
+
};
|
|
1520
|
+
|
|
1424
1521
|
const seriesLabel = (s: ChartSeries) => s.label ?? s.field ?? s.column ?? '';
|
|
1425
1522
|
const leftAxisLabel = (v?: ChartView) =>
|
|
1426
1523
|
v?.y.filter(s => s.y_axis !== 'right').map(seriesLabel).join(' / ') ?? '';
|
|
@@ -1435,6 +1532,27 @@ const rightAxisLabel = (v?: ChartView) =>
|
|
|
1435
1532
|
* the convention used by AutoCoreTagContext: `display = raw * scale`.
|
|
1436
1533
|
* Non-numeric values pass through unchanged.
|
|
1437
1534
|
*/
|
|
1535
|
+
/**
|
|
1536
|
+
* One schema field → one <ScienceTable> column. Shared by the Cycle Data
|
|
1537
|
+
* and Results tables so a field is headed identically in both.
|
|
1538
|
+
*
|
|
1539
|
+
* The header is the schema's `label` when it declares one, falling back to
|
|
1540
|
+
* the field name. The server applies the same rule to CSV headers
|
|
1541
|
+
* (`column_header_with_units`), so an export reads like the screen.
|
|
1542
|
+
*/
|
|
1543
|
+
const isProjectScoped = (f: TestFieldDef): boolean => f.aggregate?.scope === 'project';
|
|
1544
|
+
|
|
1545
|
+
const toScienceColumn = (f: TestFieldDef, editable = false): ScienceColumn => ({
|
|
1546
|
+
field: f.name,
|
|
1547
|
+
// A project-scoped rollup sits in the same row as this test's own
|
|
1548
|
+
// numbers, so it has to say so. Marked here and explained in a note
|
|
1549
|
+
// under the table — never in a tooltip.
|
|
1550
|
+
label: (f.label && f.label.length > 0 ? f.label : f.name) + (isProjectScoped(f) ? ' *' : ''),
|
|
1551
|
+
units: f.units,
|
|
1552
|
+
editable,
|
|
1553
|
+
body: (row) => formatCell(row[f.name], f.type, f.scale),
|
|
1554
|
+
});
|
|
1555
|
+
|
|
1438
1556
|
const formatCell = (v: any, type: string, scale?: number): string => {
|
|
1439
1557
|
if (v === null || v === undefined) return '';
|
|
1440
1558
|
if (scale && scale !== 1 && typeof v === 'number' && Number.isFinite(v)) {
|
|
@@ -3,9 +3,16 @@ import { Dialog } from 'primereact/dialog';
|
|
|
3
3
|
import { Button } from 'primereact/button';
|
|
4
4
|
import { InputText } from 'primereact/inputtext';
|
|
5
5
|
import { Dropdown } from 'primereact/dropdown';
|
|
6
|
+
import { InputNumber } from 'primereact/inputnumber';
|
|
6
7
|
import { FormRow } from '../../forms/FormRow';
|
|
8
|
+
import { DEFAULT_X_DECIMALS } from '../../tis/TestDataView';
|
|
7
9
|
import type { ChartAxis, ChartSeries, ChartView } from '../types';
|
|
8
10
|
|
|
11
|
+
/** Upper bound on the editor's X-decimals spinner. Past ~6 the tick labels
|
|
12
|
+
* are wider than the gap between them, which is the problem this setting
|
|
13
|
+
* exists to solve. */
|
|
14
|
+
const MAX_X_DECIMALS = 8;
|
|
15
|
+
|
|
9
16
|
const VIEW_TYPES = [
|
|
10
17
|
{ label: 'Cycle scatter', value: 'cycle_scatter' },
|
|
11
18
|
{ label: 'Raw trace', value: 'raw_trace' },
|
|
@@ -115,6 +122,26 @@ export const ChartViewDialog: React.FC<ChartViewDialogProps> = ({
|
|
|
115
122
|
onChange={(e) => setAxis({ label: e.target.value })}
|
|
116
123
|
/>
|
|
117
124
|
</FormRow>
|
|
125
|
+
<FormRow
|
|
126
|
+
label="X decimals"
|
|
127
|
+
hint={`Decimal places on X-axis tick labels. Blank uses the default (${DEFAULT_X_DECIMALS}). Display only — stored data keeps full precision.`}
|
|
128
|
+
>
|
|
129
|
+
<InputNumber
|
|
130
|
+
value={draft.x_decimals ?? null}
|
|
131
|
+
onValueChange={(e) => {
|
|
132
|
+
// Blank clears the override so the view falls back to
|
|
133
|
+
// the default, rather than pinning it to 0 decimals.
|
|
134
|
+
const next = { ...draft };
|
|
135
|
+
if (e.value == null) delete next.x_decimals;
|
|
136
|
+
else next.x_decimals = e.value;
|
|
137
|
+
setDraft(next);
|
|
138
|
+
}}
|
|
139
|
+
min={0}
|
|
140
|
+
max={MAX_X_DECIMALS}
|
|
141
|
+
placeholder={String(DEFAULT_X_DECIMALS)}
|
|
142
|
+
showButtons
|
|
143
|
+
/>
|
|
144
|
+
</FormRow>
|
|
118
145
|
|
|
119
146
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', margin: '1rem 0 0.5rem' }}>
|
|
120
147
|
<strong>Y series</strong>
|
|
@@ -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
|
-
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
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: {
|
|
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 = {
|
|
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
|
|
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={
|
|
268
|
-
options={
|
|
269
|
-
onChange={(e) =>
|
|
270
|
-
|
|
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 {
|
|
@@ -75,6 +89,12 @@ export interface ChartView {
|
|
|
75
89
|
type: ChartViewType | string;
|
|
76
90
|
x: ChartAxis;
|
|
77
91
|
y: ChartSeries[];
|
|
92
|
+
/**
|
|
93
|
+
* Max decimal places on X-axis tick labels (display only; default 2).
|
|
94
|
+
* See `ChartView.x_decimals` in TestDataView for why an uncapped axis
|
|
95
|
+
* prints 17-digit ticks.
|
|
96
|
+
*/
|
|
97
|
+
x_decimals?: number;
|
|
78
98
|
}
|
|
79
99
|
|
|
80
100
|
export type RawColumnSource = 'time' | 'derived' | string; // also `ni.<daq>.channels.<name>`
|