@adcops/autocore-react 3.3.106 → 3.3.109

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.
Files changed (34) hide show
  1. package/dist/components/index.d.ts +2 -0
  2. package/dist/components/index.d.ts.map +1 -1
  3. package/dist/components/index.js +1 -1
  4. package/dist/components/tis/ResultHistoryTable.css +12 -0
  5. package/dist/components/tis/ResultHistoryTable.d.ts +1 -0
  6. package/dist/components/tis/ResultHistoryTable.d.ts.map +1 -1
  7. package/dist/components/tis/ResultHistoryTable.js +1 -1
  8. package/dist/components/tis/SampleSummaryPanel.d.ts +23 -0
  9. package/dist/components/tis/SampleSummaryPanel.d.ts.map +1 -0
  10. package/dist/components/tis/SampleSummaryPanel.js +1 -0
  11. package/dist/components/tis/TestDataView.d.ts.map +1 -1
  12. package/dist/components/tis/TestDataView.js +1 -1
  13. package/dist/components/tis/TestSetupForm.d.ts.map +1 -1
  14. package/dist/components/tis/TestSetupForm.js +1 -1
  15. package/dist/components/tis-editor/editor/FieldArrayEditor.d.ts.map +1 -1
  16. package/dist/components/tis-editor/editor/FieldArrayEditor.js +1 -1
  17. package/dist/components/tis-editor/editor/TestFieldDialog.d.ts +6 -0
  18. package/dist/components/tis-editor/editor/TestFieldDialog.d.ts.map +1 -1
  19. package/dist/components/tis-editor/editor/TestFieldDialog.js +1 -1
  20. package/dist/components/tis-editor/types.d.ts +15 -0
  21. package/dist/components/tis-editor/types.d.ts.map +1 -1
  22. package/dist/components/tis-editor/validation.d.ts.map +1 -1
  23. package/dist/components/tis-editor/validation.js +1 -1
  24. package/package.json +1 -1
  25. package/src/components/index.ts +3 -0
  26. package/src/components/tis/ResultHistoryTable.css +12 -0
  27. package/src/components/tis/ResultHistoryTable.tsx +61 -0
  28. package/src/components/tis/SampleSummaryPanel.tsx +171 -0
  29. package/src/components/tis/TestDataView.tsx +47 -1
  30. package/src/components/tis/TestSetupForm.tsx +12 -0
  31. package/src/components/tis-editor/editor/FieldArrayEditor.tsx +18 -1
  32. package/src/components/tis-editor/editor/TestFieldDialog.tsx +82 -1
  33. package/src/components/tis-editor/types.ts +17 -0
  34. package/src/components/tis-editor/validation.ts +35 -0
@@ -12,6 +12,8 @@ export { TestMethodDialog } from './tis/TestMethodDialog';
12
12
  export type { TestMethodDialogProps } from './tis/TestMethodDialog';
13
13
  export { ResultHistoryTable } from './tis/ResultHistoryTable';
14
14
  export type { ResultHistoryTableProps } from './tis/ResultHistoryTable';
15
+ export { SampleSummaryPanel } from './tis/SampleSummaryPanel';
16
+ export type { SampleSummaryPanelProps } from './tis/SampleSummaryPanel';
15
17
  export { TestDataView } from './tis/TestDataView';
16
18
  export type { TestDataViewProps, ChartAxis, ChartSeries, ChartView, RawDataShape } from './tis/TestDataView';
17
19
  export { TestRawDataView } 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,GACZ,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACR,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,eAAe,GAClB,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,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,YAAY,EAAE,MAAa,oBAAoB,CAAC;AACzD,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE7G,OAAO,EAAE,eAAe,EAAE,MAAU,uBAAuB,CAAC;AAC5D,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAElE,OAAO,EAAE,cAAc,EAAE,MAAW,sBAAsB,CAAC;AAC3D,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAMhE,OAAO,EACH,WAAW,EACX,MAAM,EACN,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,eAAe,GAClB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACR,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,aAAa,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAS,0BAA0B,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAY,uBAAuB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,YAAY,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAU,yBAAyB,CAAC;AAShE,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACxE,YAAY,EACR,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,MAAM,EACN,YAAY,GACf,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,YAAY,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC"}
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,GACZ,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACR,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,eAAe,GAClB,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,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,MAAa,oBAAoB,CAAC;AACzD,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE7G,OAAO,EAAE,eAAe,EAAE,MAAU,uBAAuB,CAAC;AAC5D,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAElE,OAAO,EAAE,cAAc,EAAE,MAAW,sBAAsB,CAAC;AAC3D,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAMhE,OAAO,EACH,WAAW,EACX,MAAM,EACN,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,eAAe,GAClB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACR,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,aAAa,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAS,0BAA0B,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAY,uBAAuB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,YAAY,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAU,yBAAyB,CAAC;AAShE,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACxE,YAAY,EACR,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,MAAM,EACN,YAAY,GACf,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,YAAY,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC"}
@@ -1 +1 @@
1
- export{TisProvider,TisContext,useTis,useTisSchemas,useTisState,useTisSelection,useTisRuns,useTisRun}from"./tis/TisProvider";export{ProjectSelector}from"./tis/ProjectSelector";export{TestSetupForm}from"./tis/TestSetupForm";export{TestSetupView}from"./tis/TestSetupView";export{ProjectInfoDialog}from"./tis/ProjectInfoDialog";export{TestMethodDialog}from"./tis/TestMethodDialog";export{ResultHistoryTable}from"./tis/ResultHistoryTable";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}from"./tis/TisProvider";export{ProjectSelector}from"./tis/ProjectSelector";export{TestSetupForm}from"./tis/TestSetupForm";export{TestSetupView}from"./tis/TestSetupView";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";
@@ -0,0 +1,12 @@
1
+ /* Runs excluded from cross-test aggregation are kept visible but dimmed and
2
+ struck through, so an operator can see at a glance which rows don't feed
3
+ the avg/min/max/stddev/count results for their sample group. */
4
+ .tis-row-excluded > td {
5
+ opacity: 0.55;
6
+ text-decoration: line-through;
7
+ }
8
+ /* Keep the action buttons (Include / Download) legible and clickable. */
9
+ .tis-row-excluded > td .p-button {
10
+ opacity: 1;
11
+ text-decoration: none;
12
+ }
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import './ResultHistoryTable.css';
2
3
  export interface ResultHistoryTableProps {
3
4
  /**
4
5
  * Override the project scope. When omitted, the table reads from
@@ -1 +1 @@
1
- {"version":3,"file":"ResultHistoryTable.d.ts","sourceRoot":"","sources":["../../../src/components/tis/ResultHistoryTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAS/D,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,CAoThE,CAAC"}
1
+ {"version":3,"file":"ResultHistoryTable.d.ts","sourceRoot":"","sources":["../../../src/components/tis/ResultHistoryTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAS/D,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,CA+WhE,CAAC"}
@@ -1 +1 @@
1
- import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React,{useState,useEffect,useContext}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Button}from"primereact/button";import{Tag}from"primereact/tag";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";const downloadCsvBlob=(e,t)=>{const o=new Blob([t],{type:"text/csv;charset=utf-8;"}),r=URL.createObjectURL(o),s=document.createElement("a");s.href=r,s.download=e,document.body.appendChild(s),s.click(),s.remove(),URL.revokeObjectURL(r)},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,r=e.methodId,[s,i]=useState([]),[a,n]=useState(!1),[l,d]=useState(null),[c,p]=useState(null),{invoke:u}=useContext(EventEmitterContext),m=async()=>{if(o){n(!0);try{const e={project_id:o};r&&(e.method_id=r);const t=await u("tis.list_tests",MessageType.Request,e);t.success&&t.data&&t.data.tests&&i(t.data.tests)}catch(e){}n(!1)}else i([])};useEffect(()=>{m()},[o,r,t.state.activeRunId,t.state.active]);const f=async(e,t)=>{const s=e?.run_id,i=e?.method_id??r;if(!s||!i||!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 u(a,MessageType.Request,{project_id:o,method_id:i,run_id:s});if(!e?.success)return void alert(`Failed to build ${n} for ${s}`+(e?.error_message?`: ${e.error_message}`:""));const r="string"==typeof e.data?.filename&&e.data.filename?e.data.filename:`${o}_${i}_${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(r,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,r)}}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{d(null)}},_=async e=>{if(!o)return;const t="report"===e?"tis.export_project_csv":"tis.export_project_zip",r="report"===e?"project report":"project archive";p(e);try{const s=await u(t,MessageType.Request,{project_id:o});if(!s?.success)return void alert(`Failed to build ${r}`+(s?.error_message?`: ${s.error_message}`:""));const i="string"==typeof s.data?.download_url?s.data.download_url:"";if(!i)return void alert(`Server did not return a download URL for ${r}.`);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(i,n)}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{p(null)}},y=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}${r?` / ${r}`:""}`:"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:()=>_("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:()=>_("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:m,disabled:a})]})]}),_jsxs(DataTable,{value:s,loading:a,paginator:!0,rows:10,emptyMessage:"No tests found.",scrollable:!0,scrollHeight:"flex",tableStyle:{minWidth:0},style:{width:"100%"},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:y,sortFunction:e=>{const t=[...e.data];return t.sort((t,o)=>y(t).localeCompare(y(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,{field:"run_id",header:"Run ID",sortable:!0,style:{minWidth:"12rem"}}),_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";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(Tag,{value:t.label,severity:t.severity})},style:{minWidth:"8rem"}}),_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,r=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:r,onClick:()=>f(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:r,onClick:()=>f(e,"report"),tooltip:"Download a CSV report (metadata + cycles + results) for this test",tooltipOptions:{position:"left"}})]})}})]})]})};
1
+ import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React,{useState,useEffect,useContext}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Button}from"primereact/button";import{Tag}from"primereact/tag";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;"}),r=URL.createObjectURL(o),s=document.createElement("a");s.href=r,s.download=e,document.body.appendChild(s),s.click(),s.remove(),URL.revokeObjectURL(r)},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,r=e.methodId,[s,i]=useState([]),[a,l]=useState(!1),[n,d]=useState(null),[c,p]=useState(null),[u,m]=useState(null),{invoke:f}=useContext(EventEmitterContext),_=async()=>{if(o){l(!0);try{const e={project_id:o};r&&(e.method_id=r);const t=await f("tis.list_tests",MessageType.Request,e);t.success&&t.data&&t.data.tests&&i(t.data.tests)}catch(e){}l(!1)}else i([])};useEffect(()=>{_()},[o,r,t.state.activeRunId,t.state.active]);const y=async(e,t)=>{const s=e?.run_id,i=e?.method_id??r;if(!s||!i||!o)return;const a="report"===t?"tis.export_test_csv":"tis.export_test_data_csv",l="report"===t?"test report":"test data";d({runId:s,kind:t});try{const e=await f(a,MessageType.Request,{project_id:o,method_id:i,run_id:s});if(!e?.success)return void alert(`Failed to build ${l} for ${s}`+(e?.error_message?`: ${e.error_message}`:""));const r="string"==typeof e.data?.filename&&e.data.filename?e.data.filename:`${o}_${i}_${s}_${t}.csv`;if("report"===t){const t="string"==typeof e.data?.csv?e.data.csv:"";if(!t)return void alert(`${l} for ${s} is empty.`);downloadCsvBlob(r,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 ${l}.`);downloadViaUrl(t,r)}}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{d(null)}},h=async e=>{if(!o)return;const t="report"===e?"tis.export_project_csv":"tis.export_project_zip",r="report"===e?"project report":"project archive";p(e);try{const s=await f(t,MessageType.Request,{project_id:o});if(!s?.success)return void alert(`Failed to build ${r}`+(s?.error_message?`: ${s.error_message}`:""));const i="string"==typeof s.data?.download_url?s.data.download_url:"";if(!i)return void alert(`Server did not return a download URL for ${r}.`);const a="report"===e?`${o}_project_report.csv`:`${o}_project_archive.zip`,l="string"==typeof s.data?.filename&&s.data.filename?s.data.filename:a;downloadViaUrl(i,l)}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{p(null)}},x=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}${r?` / ${r}`:""}`:"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:()=>h("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:()=>h("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:_,disabled:a})]})]}),_jsx(SampleSummaryPanel,{projectId:o,methodId:r}),_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:x,sortFunction:e=>{const t=[...e.data];return t.sort((t,o)=>x(t).localeCompare(x(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,{field:"run_id",header:"Run ID",sortable:!0,style:{minWidth:"12rem"}}),_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";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(Tag,{value:t.label,severity:t.severity})},style:{minWidth:"8rem"}}),_jsx(Column,{header:"Include",style:{width:"8rem"},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",label:t?"Excluded":"Included",size:"small",outlined:!0,severity:t?"warning":"success",disabled:null!==u,onClick:()=>(async e=>{const t=e?.run_id,s=e?.method_id??r;if(!t||!s||!o)return;const i=!0!==e?.excluded;m(t);try{const e=await f("tis.set_test_excluded",MessageType.Request,{project_id:o,method_id:s,run_id:t,excluded:i});if(!e?.success)return void alert(`Failed to ${i?"exclude":"include"} ${t}`+(e?.error_message?`: ${e.error_message}`:""));await _()}catch(e){alert(`Exclude toggle failed: ${e instanceof Error?e.message:String(e)}`)}finally{m(null)}})(e),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=n?.runId===e.run_id&&"data"===n?.kind,o=n?.runId===e.run_id&&"report"===n?.kind,r=null!==n;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:r,onClick:()=>y(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:r,onClick:()=>y(e,"report"),tooltip:"Download a CSV report (metadata + cycles + results) for this test",tooltipOptions:{position:"left"}})]})}})]})]})};
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ /**
3
+ * Per-method cross-test rollup, one row per sample_id.
4
+ *
5
+ * Distinct from the per-run results overlay in <TestDataView>: this panel
6
+ * answers "across every run of sample X, what's the avg/min/max/stddev/count?"
7
+ * — the question operators ask when a sample is run repeatedly. Backed by
8
+ * `tis.sample_summaries`; refreshes on the server's `tis.sample_summary_updated`
9
+ * broadcast (a sibling finishing or an exclusion toggle).
10
+ *
11
+ * Renders nothing unless some method in the project declares aggregate
12
+ * results_fields. When `methodId` is omitted and more than one method has
13
+ * aggregates, a small method picker is shown.
14
+ */
15
+ export interface SampleSummaryPanelProps {
16
+ /** Defaults to the active project from <TisProvider>. */
17
+ projectId?: string;
18
+ /** Lock the panel to one method. When omitted, the user picks among
19
+ * methods that declare aggregate results_fields. */
20
+ methodId?: string;
21
+ }
22
+ export declare const SampleSummaryPanel: React.FC<SampleSummaryPanelProps>;
23
+ //# sourceMappingURL=SampleSummaryPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SampleSummaryPanel.d.ts","sourceRoot":"","sources":["../../../src/components/tis/SampleSummaryPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAQ/D;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,uBAAuB;IACpC,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;yDACqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAeD,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAgIhE,CAAC"}
@@ -0,0 +1 @@
1
+ import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React,{useState,useEffect,useContext}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Dropdown}from"primereact/dropdown";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";export const SampleSummaryPanel=e=>{const t=useTis(),{invoke:s,subscribe:r,unsubscribe:a}=useContext(EventEmitterContext),i=e.projectId??t.selection.projectId??void 0,m=React.useMemo(()=>Object.entries(t.schemas).filter(([,e])=>(e?.results_fields??[]).some(e=>e?.aggregate)).map(([e])=>e),[t.schemas]),[o,n]=useState(void 0),l=e.methodId??o??m[0];useEffect(()=>{e.methodId||o&&m.includes(o)||n(m[0])},[e.methodId,o,m]);const[d,u]=useState([]),[c,p]=useState(!1),h=React.useMemo(()=>{const e=l?t.schemas[l]:void 0;return(e?.results_fields??[]).filter(e=>e?.aggregate).map(e=>({name:e.name,header:(e.label||e.name)+(e.units?` [${e.units}]`:""),scale:"number"==typeof e.scale?e.scale:void 0}))},[t.schemas,l]);if(useEffect(()=>{if(!i||!l)return void u([]);let e=!1;const t=async()=>{p(!0);try{const t=await s("tis.sample_summaries",MessageType.Request,{project_id:i,method_id:l});!e&&t?.success&&u(t.data?.summaries??[])}catch(e){}finally{e||p(!1)}};t();const m=r("tis.sample_summary_updated",e=>{e?.project_id===i&&e?.method_id===l&&t()});return()=>{e=!0,a(m)}},[i,l,t.state.activeRunId,t.state.active]),0===m.length)return null;return _jsxs("div",{style:{width:"100%",maxWidth:"100%",overflow:"hidden",boxSizing:"border-box",marginBottom:"1rem"},children:[_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"0.5rem",gap:"0.5rem",flexWrap:"wrap"},children:[_jsx("h3",{style:{margin:0},children:"Sample Summary"}),!e.methodId&&m.length>1&&_jsx(Dropdown,{value:l,options:m.map(e=>({label:e,value:e})),onChange:e=>n(e.value),style:{minWidth:"12rem"}})]}),_jsxs(DataTable,{value:d,loading:c,dataKey:"sample_id",emptyMessage:"No samples yet.",scrollable:!0,tableStyle:{minWidth:0},style:{width:"100%"},size:"small",stripedRows:!0,children:[_jsx(Column,{field:"sample_id",header:"Sample ID",sortable:!0,style:{minWidth:"8rem"}}),h.map(e=>_jsx(Column,{header:e.header,bodyStyle:{fontVariantNumeric:"tabular-nums",textAlign:"right"},style:{minWidth:"7rem"},body:t=>((e,t)=>{if(null==e)return"—";if("number"==typeof e){if(!Number.isFinite(e))return String(e);const s="number"==typeof t&&0!==t?e*t:e;return Number.parseFloat(s.toPrecision(6)).toString()}return String(e)})(t.summary?.[e.name],e.scale)},e.name)),_jsx(Column,{header:"# Tests",bodyStyle:{textAlign:"right"},style:{width:"6rem"},body:e=>e.included_count}),_jsx(Column,{header:"Excluded",bodyStyle:{textAlign:"right"},style:{width:"6rem"},body:e=>e.excluded_count>0?e.excluded_count:"—"})]})]})};
@@ -1 +1 @@
1
- {"version":3,"file":"TestDataView.d.ts","sourceRoot":"","sources":["../../../src/components/tis/TestDataView.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TestDataView.d.ts","sourceRoot":"","sources":["../../../src/components/tis/TestDataView.tsx"],"names":[],"mappings":"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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";ChartJS.register(CategoryScale,LinearScale,PointElement,LineElement,Title,Tooltip,Legend,zoomPlugin,annotationPlugin);export const TestDataView=e=>{const t=useTis(),r=e.projectId??t.selection.projectId,a=e.methodId??t.selection.methodId,n=e.runId??t.selection.runId,l=e.schema??(a?t.schemas[a]:void 0),{throttleMs:o=100,cycleTableHeight:s="400px",chartHeight:i="320px"}=e,{invoke:c,subscribe:d,unsubscribe:u}=useContext(EventEmitterContext),[m,p]=useState(null),[y,h]=useState([]),[f,g]=useState({}),[x,_]=useState(!1),[b,j]=useState(!1),v=useRef(null),[w,C]=useState(null),[S,R]=useState(null),[T,M]=useState(!1),[L,I]=useState(null),[O,A]=useState(null),[D,E]=useState(!1),[N,k]=useState([]),[F,H]=useState(null),z=useMemo(()=>{const e=[];for(const[t,r]of Object.entries(l?.views??{}))e.push({name:t,view:r});return e},[l]),[P,$]=useState(z.length>0?z[0].name:null);useEffect(()=>{if(0===z.length)return;null!==P&&z.some(e=>e.name===P)||$(z[0].name)},[z,P]);const B=z.find(e=>e.name===P)?.view,V="raw_trace"===B?.type,G=useRef([]),W=useRef(null),q=useRef(null),U=()=>{q.current||(q.current=setTimeout(()=>{if(q.current=null,G.current.length>0){const e=G.current;G.current=[],h(t=>[...e.slice().reverse(),...t])}W.current&&(g(W.current),W.current=null)},o))};useEffect(()=>{if(!r||!a||!n)return p(null),h([]),void g({});let e=!1;return(async()=>{try{const t=await c("tis.read_test",MessageType.Request,{project_id:r,method_id:a,run_id:n});!e&&t?.success&&(p(t.data),g(t.data.results??{}));const l=await c("tis.read_cycles",MessageType.Request,{project_id:r,method_id:a,run_id:n,offset:0,limit:200,order:"desc"});!e&&l?.success&&h(l.data.cycles??[])}catch(e){}})(),()=>{e=!0}},[r,a,n,c]),useEffect(()=>{const e=e=>e?.project_id===r&&e?.method_id===a&&e?.run_id===n,t=d("tis.cycle_added",t=>{e(t)&&t.cycle&&(G.current.push(t.cycle),U())}),l=d("tis.results_updated",t=>{e(t)&&(W.current=t.results??{},U())});return()=>{u(t),u(l),q.current&&(clearTimeout(q.current),q.current=null)}},[r,a,n,o]);const J=useRawCycleData({projectId:r,methodId:a,runId:n,blobName:l?.raw_data?.blob_name??"trace",source:B?.source??"raw",enabled:V}),Y=useMemo(()=>{if(!B)return null;if("cycle_scatter"===B.type){const e=B.x.field;if(!e)return null;const t=[...y].reverse();return{labels:t.map(t=>t[e]),datasets:B.y.map((e,r)=>({label:e.label??e.field,data:t.map(t=>t[e.field]),yAxisID:"right"===e.y_axis?"y1":"y",borderColor:palette(r),backgroundColor:palette(r),tension:.1,pointRadius:2}))}}if("raw_trace"===B.type){if(!J.raw)return null;const e=B.x.column;if(!e)return null;const t=J.raw[e]??[],r=!1!==B.smooth;return{datasets:B.y.map((e,a)=>{const n=J.raw[e.column]??[],l=r?smoothTraceForDisplay(t,n,B.smoothWindow):n.map((e,r)=>({x:t[r],y:e}));return{label:e.label??e.column,data:l,yAxisID:"right"===e.y_axis?"y1":"y",borderColor:palette(a),backgroundColor:palette(a),pointRadius:0,borderWidth:1.5,showLine:!0,...r?{cubicInterpolationMode:"monotone"}:{}}})}}return null},[B,y,J.raw]),Z=B?.y.some(e=>"right"===e.y_axis)??!1,K=useMemo(()=>{if(!V)return[];const e=J.envelope,t=e?.regions??e?.context?.regions;return Array.isArray(t)?t:[]},[V,J.envelope]),Q=useMemo(()=>[...B?.regions??[],...K],[B,K]),X=useMemo(()=>{const e="raw_trace"===B?.type;return{responsive:!0,maintainAspectRatio:!1,parsing:!e&&void 0,scales:{x:e?{type:"linear",title:{display:!!B?.x.label,text:B?.x.label}}:{title:{display:!!B?.x.label,text:B?.x.label}},y:{position:"left",title:{display:!0,text:leftAxisLabel(B)}},...Z?{y1:{position:"right",grid:{drawOnChartArea:!1},title:{display:!0,text:rightAxisLabel(B)}}}:{}},plugins:{legend:{display:!0},zoom:{pan:{enabled:!0,mode:"xy"},zoom:{wheel:{enabled:!0},pinch:{enabled:!0},mode:"xy"}},annotation:{annotations:buildRegionAnnotations(Q)}}}},[B,Z,Q]),ee=l?.raw_data?.blob_name??"trace",te=useRef(""),re=useCallback(async()=>{if(!r||!a||!n)return[];try{const e=await c("tis.list_raw",MessageType.Request,{project_id:r,method_id:a,run_id:n});if(!e?.success)return[];const t=e.data?.cycles??[];return t.filter(e=>e?.name===ee&&"number"==typeof e?.cycle_index).map(e=>e.cycle_index).sort((e,t)=>e-t)}catch{return[]}},[r,a,n,ee,c]),ae=useCallback(async e=>{if(!r||!a||!n)return;const t=`${r}|${a}|${n}|${ee}|${e??"latest"}`;if(te.current===t)return;te.current=t;const l={project_id:r,method_id:a,run_id:n,name:ee};null!=e&&(l.cycle_index=e),M(!0),R(null),C(null);try{const e=await c("tis.read_raw",MessageType.Request,l);e?.success?C(e.data??{}):R(e?.error_message??"No raw data on disk for this run.")}catch(e){R(String(e?.message??e))}finally{M(!1)}E(!0),A(null),I(null);try{const e=await c("tis.read_filtered",MessageType.Request,l);e?.success?I(e.data??{}):A(e?.error_message??"No filtered data on disk for this run.")}catch(e){A(String(e?.message??e))}finally{E(!1)}},[r,a,n,ee,c]);useEffect(()=>{te.current="",k([]),H(null)},[r,a,n,ee]);return useEffect(()=>{x&&null!=F&&ae(F)},[x,F,ae]),r&&a&&n&&l?_jsxs("div",{className:"vblock",style:{display:"flex",flexDirection:"column",gap:"1rem"},children:[_jsx(Header,{meta:m,config:m?.config,runId:n,projectId:r,methodId:a,canViewRaw:!!l.raw_data,onViewRaw:async()=>{_(!0);let e=N;0===e.length&&(e=await re(),k(e));const t=e.length>0?e[e.length-1]:null,r=F??t;F!==r&&H(r),await ae(r)},onShowConfig:()=>j(!0)}),_jsxs("div",{className:"p-card",style:{padding:"1rem"},children:[_jsxs("div",{className:"flex",style:{gap:"1rem",alignItems:"center",marginBottom:"0.5rem",flexWrap:"wrap"},children:[_jsx(Dropdown,{value:P,options:z.map(e=>({label:e.view.title??e.name,value:e.name})),onChange:e=>$(e.value),placeholder:0===z.length?"No view defined":"Select a view",disabled:0===z.length}),_jsx("h3",{style:{margin:0},children:B?.title??""}),V&&J.cycles.length>1&&_jsxs(_Fragment,{children:[_jsx("label",{htmlFor:"chart-cycle-picker",style:{color:"var(--text-secondary-color)"},children:"Cycle:"}),_jsx(Dropdown,{inputId:"chart-cycle-picker",value:J.selectedCycle,options:J.cycles.map(e=>({label:`Cycle ${e}`,value:e})),onChange:e=>J.setSelectedCycle(Number(e.value)),style:{minWidth:"8rem"}}),_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:["of ",J.cycles.length]})]}),_jsx("div",{style:{flex:1}}),_jsx(Button,{icon:"pi pi-th-large",outlined:!0,rounded:!0,size:"small",onClick:()=>v.current?.resetZoom?.(),disabled:!Y,tooltip:"Reset chart zoom",tooltipOptions:{position:"left"},"aria-label":"Reset chart zoom"})]}),_jsxs("div",{style:{height:i,position:"relative"},children:[V&&J.loading&&_jsx(ChartOverlay,{children:"Loading raw data…"}),V&&J.error&&_jsx(ChartOverlay,{children:J.error}),Y&&_jsx(Line,{ref:v,data:Y,options:X})]})]}),_jsxs("div",{className:"p-card",style:{padding:"1rem"},children:[_jsxs("h3",{style:{marginTop:0},children:["Cycle Data (",y.length,")"]}),(()=>{const e=y.length>CYCLE_VIRTUAL_THRESHOLD;return _jsx(DataTable,{value:y,scrollable:e,scrollHeight:e?s:void 0,virtualScrollerOptions:e?{itemSize:38}:void 0,emptyMessage:"No cycles yet.",children:l.cycle_fields.map(e=>_jsx(Column,{field:e.name,header:e.units?`${e.name} (${e.units})`:e.name,body:t=>formatCell(t[e.name],e.type,e.scale)},e.name))})})()]}),_jsxs("div",{className:"p-card",style:{padding:"1rem"},children:[_jsx("h3",{style:{marginTop:0},children:"Results"}),_jsx(ResultsGrid,{schema:l.results_fields,values:f})]}),l.raw_data&&_jsxs(Dialog,{visible:x,onHide:()=>_(!1),header:`Run Data — ${n}`,style:{width:"90vw",height:"80vh"},maximizable:!0,children:[_jsx(CyclePickerBar,{cycles:N,selected:F,onChange:H}),_jsx(RawEnvelopeHeader,{envelope:w}),_jsxs(TabView,{style:{height:"100%"},children:[_jsx(TabPanel,{header:"Raw Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(w),loading:T,error:S,rawData:l.raw_data})}),_jsx(TabPanel,{header:"Filtered Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(L),loading:D,error:O,rawData:l.raw_data,emptyMessage:"Filtered data is written by post-processing — none on disk for this run yet."})})]})]}),_jsx(Dialog,{visible:b,onHide:()=>j(!1),header:"Test Configuration",style:{width:"min(640px, 90vw)"},modal:!0,children:_jsx(ConfigList,{config:m?.config})})]}):_jsx("div",{className:"p-card",style:{padding:"1rem",color:"var(--text-secondary-color)"},children:"No test selected. Pick a row from the History tab or start a run."})};const Header=({meta:e,config:t,runId:r,projectId:a,methodId:n,canViewRaw:l,onViewRaw:o,onShowConfig:s})=>{const i="string"==typeof e?.sample_id&&e.sample_id||"string"==typeof e?.config?.sample_id&&e.config.sample_id||"",c=t&&"object"==typeof t&&Object.entries(t).some(([e])=>"sample_id"!==e);return _jsx("div",{className:"p-card",style:{padding:"1rem"},children:_jsxs("div",{className:"flex",style:{justifyContent:"space-between",alignItems:"flex-start",gap:"1rem"},children:[_jsxs("div",{children:[_jsxs("h2",{style:{margin:0,display:"flex",alignItems:"center",gap:"0.5rem"},children:[i||r,c&&_jsx(Button,{icon:"pi pi-info-circle",type:"button",rounded:!0,text:!0,onClick:s,tooltip:"Show test configuration",tooltipOptions:{position:"top"},style:{width:"2rem",height:"2rem",padding:0},"aria-label":"Show test configuration"})]}),_jsxs("div",{style:{color:"var(--text-secondary-color)",fontSize:"0.85em"},children:["project: ",a," · method: ",n," · run: ",r,e?.start_time&&_jsxs(_Fragment,{children:[" · started: ",new Date(e.start_time).toLocaleString()]})]})]}),l&&_jsx(Button,{icon:"pi pi-table",label:"View Raw Data",onClick:o,outlined:!0})]})})},ConfigList=({config:e})=>{const t=e&&"object"==typeof e?Object.entries(e).filter(([e])=>"sample_id"!==e):[];return 0===t.length?_jsx("div",{style:{color:"var(--text-secondary-color)"},children:"No configuration recorded for this run."}):_jsx("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr",gap:"0.5rem 1rem",fontSize:"0.95em"},children:t.map(([e,t])=>_jsxs(React.Fragment,{children:[_jsx("div",{style:{color:"var(--text-secondary-color)"},children:e}),_jsx("div",{children:formatCell(t,"string")})]},e))})},unwrapEnvelope=e=>e&&"object"==typeof e&&"data"in e&&e.data&&"object"==typeof e.data&&Object.values(e.data).some(e=>Array.isArray(e))?e.data:e,CyclePickerBar=({cycles:e,selected:t,onChange:r})=>e.length<=1?null:_jsxs("div",{style:{display:"flex",alignItems:"center",gap:"0.5rem",padding:"0.25rem 0.5rem 0.5rem"},children:[_jsx("label",{htmlFor:"raw-cycle-picker",style:{color:"var(--text-secondary-color)"},children:"Cycle:"}),_jsx(Dropdown,{inputId:"raw-cycle-picker",value:t,options:e.map(e=>({label:`Cycle ${e}`,value:e})),onChange:e=>r(Number(e.value)),style:{minWidth:"8rem"}}),_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:["(",e.length," cycles recorded)"]})]}),RawEnvelopeHeader=({envelope:e})=>{if(!e||"object"!=typeof e)return null;const t=e.cycle_index,r=e.cycle_fields,a=e.context;if(null==t&&!r&&!a)return null;const n=e=>{if(!e||"object"!=typeof e)return null;const t=Object.entries(e).filter(([,e])=>null!==e&&"object"!=typeof e);return 0===t.length?null:t.map(([e,t])=>_jsxs("span",{style:{marginRight:"1rem"},children:[_jsxs("span",{style:{color:"var(--text-secondary-color)"},children:[e,": "]}),_jsx("span",{children:String(t)})]},e))};return _jsxs("div",{style:{padding:"0.5rem",borderBottom:"1px solid var(--surface-border)",fontSize:"0.9rem"},children:[null!=t&&_jsx("div",{children:_jsxs("strong",{children:["Cycle ",t]})}),r&&_jsx("div",{style:{marginTop:"0.25rem"},children:n(r)}),a&&_jsx("div",{style:{marginTop:"0.25rem"},children:n(a)})]})},DataBlobTable=({blob:e,loading:t,error:r,rawData:a,emptyMessage:n})=>{if(t)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:"Loading…"});if(r)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:n??r});if(!e||"object"!=typeof e)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:"No data."});const l=Object.keys(a.columns??{}),o=Object.keys(e).filter(t=>Array.isArray(e[t])),s=[];for(const t of l)Array.isArray(e[t])&&s.push(t);for(const e of o)s.includes(e)||s.push(e);if(0===s.length)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:n??"No columnar data in this blob."});const i=s.reduce((t,r)=>Math.min(t,e[r].length),Number.POSITIVE_INFINITY),c=Number.isFinite(i)?i:0,d=Array.from({length:c},(t,r)=>{const a={__i:r};for(const t of s)a[t]=e[t][r];return a}),u=e=>{const t=a.units?.[e];return t?`${e} [${t}]`:e};return _jsxs(DataTable,{value:d,scrollable:!0,scrollHeight:"60vh",virtualScrollerOptions:{itemSize:32},emptyMessage:n??"No data.",size:"small",stripedRows:!0,children:[_jsx(Column,{field:"__i",header:"#",style:{width:"5rem",textAlign:"right"},bodyStyle:{fontVariantNumeric:"tabular-nums",textAlign:"right"}}),s.map(e=>_jsx(Column,{field:e,header:u(e),style:{minWidth:"8rem"},bodyStyle:{fontVariantNumeric:"tabular-nums",textAlign:"right"},body:t=>formatNumeric(t[e])},e))]})},formatNumeric=e=>null==e?"":"number"==typeof e&&Number.isFinite(e)?Number.parseFloat(e.toPrecision(6)).toString():String(e),ResultsGrid=({schema:e,values:t})=>t&&0!==Object.keys(t).length?_jsx("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(220px, 1fr))",gap:"0.5rem 1rem"},children:e.map(e=>_jsxs("div",{children:[_jsxs("div",{style:{fontSize:"0.8em",color:"var(--text-secondary-color)"},children:[e.name,e.units?` (${e.units})`:""]}),_jsx("div",{children:formatCell(t[e.name],e.type,e.scale)})]},e.name))}):_jsx("div",{style:{color:"var(--text-secondary-color)"},children:"No results yet."}),CYCLE_VIRTUAL_THRESHOLD=30,CHART_COLORS=["#4ea8de","#f59e0b","#22c55e","#a855f7","#ef4444","#14b8a6","#eab308","#ec4899"],palette=e=>CHART_COLORS[e%CHART_COLORS.length],SG_ORDER=3,SMOOTH_TARGET_POINTS=800;function invertMatrix(e){const t=e.length,r=e.map((e,r)=>[...e,...Array.from({length:t},(e,t)=>r===t?1:0)]);for(let e=0;e<t;e++){let a=e;for(let n=e+1;n<t;n++)Math.abs(r[n][e])>Math.abs(r[a][e])&&(a=n);if(Math.abs(r[a][e])<1e-12)return null;[r[e],r[a]]=[r[a],r[e]];const n=r[e][e];for(let a=0;a<2*t;a++)r[e][a]/=n;for(let a=0;a<t;a++){if(a===e)continue;const n=r[a][e];if(0!==n)for(let l=0;l<2*t;l++)r[a][l]-=n*r[e][l]}}return r.map(e=>e.slice(t))}function sgWeights(e,t){const r=2*e+1,a=Math.min(t,r-1),n=[];for(let t=-e;t<=e;t++){const e=[];let r=1;for(let n=0;n<=a;n++)e.push(r),r*=t;n.push(e)}const l=a+1,o=Array.from({length:l},()=>new Array(l).fill(0));for(let e=0;e<l;e++)for(let t=0;t<l;t++){let a=0;for(let l=0;l<r;l++)a+=n[l][e]*n[l][t];o[e][t]=a}const s=invertMatrix(o);if(!s)return new Array(r).fill(1/r);const i=new Array(r);for(let e=0;e<r;e++){let t=0;for(let r=0;r<l;r++)t+=s[0][r]*n[e][r];i[e]=t}return i}function savitzkyGolay(e,t){const r=e.length;if(0===r||t<1)return e.slice();const a=new Map,n=e=>{let t=a.get(e);return t||(t=sgWeights(e,3),a.set(e,t)),t},l=new Array(r);for(let a=0;a<r;a++){const o=Math.min(t,a,r-1-a);if(o<1){l[a]=e[a];continue}const s=n(o);let i=0;for(let t=-o;t<=o;t++)i+=s[t+o]*e[a+t];l[a]=i}return l}function smoothTraceForDisplay(e,t,r){const a=Math.min(e.length,t.length);if(0===a)return[];const n=r&&r>1?Math.max(1,Math.floor((Math.min(r,a)-1)/2)):Math.max(2,Math.min(40,Math.round(a/250))),l=savitzkyGolay(t.slice(0,a),n),o=Math.max(1,Math.ceil(a/SMOOTH_TARGET_POINTS)),s=[];for(let t=0;t<a;t+=o)s.push({x:e[t],y:l[t]});return(a-1)%o!=0&&s.push({x:e[a-1],y:l[a-1]}),s}const DEFAULT_REGION_FILL="rgba(78, 168, 222, 0.15)",REGION_LABEL_COLOR="rgba(226, 232, 240, 0.85)";function buildRegionAnnotations(e){if(!e||0===e.length)return{};const t={};return e.forEach((e,r)=>{const a=!!e.borderColor;t[`region-${r}`]={type:"box",xScaleID:"x",xMin:e.xMin,xMax:e.xMax,backgroundColor:e.color??DEFAULT_REGION_FILL,borderColor:a?e.borderColor:"transparent",borderWidth:a?1:0,drawTime:"beforeDatasetsDraw",...e.label?{label:{display:!0,content:e.label,position:{x:"center",y:"start"},color:REGION_LABEL_COLOR,font:{size:11}}}:{}}}),t}const ChartOverlay=({children:e})=>_jsx("div",{style:{position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",color:"var(--text-secondary-color)",pointerEvents:"none"},children:e}),seriesLabel=e=>e.label??e.field??e.column??"",leftAxisLabel=e=>e?.y.filter(e=>"right"!==e.y_axis).map(seriesLabel).join(" / ")??"",rightAxisLabel=e=>e?.y.filter(e=>"right"===e.y_axis).map(seriesLabel).join(" / ")??"",formatCell=(e,t,r)=>null==e?"":(r&&1!==r&&"number"==typeof e&&Number.isFinite(e)&&(e*=r),"f32"===t||"f64"===t?"number"==typeof e?e.toFixed(4):String(e):"object"==typeof e?JSON.stringify(e):String(e));
1
+ import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import React,{useCallback,useContext,useEffect,useMemo,useRef,useState}from"react";import{Button}from"primereact/button";import{Column}from"primereact/column";import{DataTable}from"primereact/datatable";import{Dialog}from"primereact/dialog";import{Dropdown}from"primereact/dropdown";import{TabView,TabPanel}from"primereact/tabview";import{Chart as ChartJS,CategoryScale,LinearScale,PointElement,LineElement,Title,Tooltip,Legend}from"chart.js";import zoomPlugin from"chartjs-plugin-zoom";import annotationPlugin from"chartjs-plugin-annotation";import{Line}from"react-chartjs-2";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";import{useRawCycleData}from"./useRawCycleData";ChartJS.register(CategoryScale,LinearScale,PointElement,LineElement,Title,Tooltip,Legend,zoomPlugin,annotationPlugin);export const TestDataView=e=>{const t=useTis(),r=e.projectId??t.selection.projectId,a=e.methodId??t.selection.methodId,n=e.runId??t.selection.runId,l=e.schema??(a?t.schemas[a]:void 0),{throttleMs:s=100,cycleTableHeight:o="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=useRef(null),[S,R]=useState(null),[T,M]=useState(null),[L,I]=useState(!1),[O,E]=useState(null),[A,D]=useState(null),[N,k]=useState(!1),[F,H]=useState([]),[z,P]=useState(null),$=useMemo(()=>{const e=[];for(const[t,r]of Object.entries(l?.views??{}))e.push({name:t,view:r});return e},[l]),[B,V]=useState($.length>0?$[0].name:null);useEffect(()=>{if(0===$.length)return;null!==B&&$.some(e=>e.name===B)||V($[0].name)},[$,B]);const G=$.find(e=>e.name===B)?.view,W="raw_trace"===G?.type,q=useRef([]),U=useRef(null),J=useRef(null),Y=()=>{J.current||(J.current=setTimeout(()=>{if(J.current=null,q.current.length>0){const e=q.current;q.current=[],h(t=>[...e.slice().reverse(),...t])}U.current&&(g(U.current),U.current=null)},s))};useEffect(()=>{if(!r||!a||!n)return p(null),h([]),void g({});let e=!1;return(async()=>{try{const t=await c("tis.read_test",MessageType.Request,{project_id:r,method_id:a,run_id:n});!e&&t?.success&&(p(t.data),g(t.data.results??{}));const l=await c("tis.read_cycles",MessageType.Request,{project_id:r,method_id:a,run_id:n,offset:0,limit:200,order:"desc"});!e&&l?.success&&h(l.data.cycles??[])}catch(e){}})(),()=>{e=!0}},[r,a,n,c]),useEffect(()=>{const e=e=>e?.project_id===r&&e?.method_id===a&&e?.run_id===n,t=d("tis.cycle_added",t=>{e(t)&&t.cycle&&(q.current.push(t.cycle),Y())}),l=d("tis.results_updated",t=>{e(t)&&(U.current=t.results??{},Y())});return()=>{u(t),u(l),J.current&&(clearTimeout(J.current),J.current=null)}},[r,a,n,s]);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;(async()=>{try{const t=await c("tis.sample_summary",MessageType.Request,{project_id:r,method_id:a,sample_id:Z});!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===Z&&x(e.summary??{})});return()=>{e=!0,u(t)}},[r,a,Z,c,d,u]);const K=useRawCycleData({projectId:r,methodId:a,runId:n,blobName:l?.raw_data?.blob_name??"trace",source:G?.source??"raw",enabled:W}),Q=useMemo(()=>{if(!G)return null;if("cycle_scatter"===G.type){const e=G.x.field;if(!e)return null;const t=[...y].reverse();return{labels:t.map(t=>t[e]),datasets:G.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"===G.type){if(!K.raw)return null;const e=G.x.column;if(!e)return null;const t=K.raw[e]??[],r=!1!==G.smooth;return{datasets:G.y.map((e,a)=>{const n=K.raw[e.column]??[],l=r?smoothTraceForDisplay(t,n,G.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},[G,y,K.raw]),X=G?.y.some(e=>"right"===e.y_axis)??!1,ee=useMemo(()=>{if(!W)return[];const e=K.envelope,t=e?.regions??e?.context?.regions;return Array.isArray(t)?t:[]},[W,K.envelope]),te=useMemo(()=>[...G?.regions??[],...ee],[G,ee]),re=useMemo(()=>{const e="raw_trace"===G?.type;return{responsive:!0,maintainAspectRatio:!1,parsing:!e&&void 0,scales:{x:e?{type:"linear",title:{display:!!G?.x.label,text:G?.x.label}}:{title:{display:!!G?.x.label,text:G?.x.label}},y:{position:"left",title:{display:!0,text:leftAxisLabel(G)}},...X?{y1:{position:"right",grid:{drawOnChartArea:!1},title:{display:!0,text:rightAxisLabel(G)}}}:{}},plugins:{legend:{display:!0},zoom:{pan:{enabled:!0,mode:"xy"},zoom:{wheel:{enabled:!0},pinch:{enabled:!0},mode:"xy"}},annotation:{annotations:buildRegionAnnotations(te)}}}},[G,X,te]),ae=l?.raw_data?.blob_name??"trace",ne=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===ae&&"number"==typeof e?.cycle_index).map(e=>e.cycle_index).sort((e,t)=>e-t)}catch{return[]}},[r,a,n,ae,c]),se=useCallback(async e=>{if(!r||!a||!n)return;const t=`${r}|${a}|${n}|${ae}|${e??"latest"}`;if(ne.current===t)return;ne.current=t;const l={project_id:r,method_id:a,run_id:n,name:ae};null!=e&&(l.cycle_index=e),I(!0),M(null),R(null);try{const e=await c("tis.read_raw",MessageType.Request,l);e?.success?R(e.data??{}):M(e?.error_message??"No raw data on disk for this run.")}catch(e){M(String(e?.message??e))}finally{I(!1)}k(!0),D(null),E(null);try{const e=await c("tis.read_filtered",MessageType.Request,l);e?.success?E(e.data??{}):D(e?.error_message??"No filtered data on disk for this run.")}catch(e){D(String(e?.message??e))}finally{k(!1)}},[r,a,n,ae,c]);useEffect(()=>{ne.current="",H([]),P(null)},[r,a,n,ae]);return useEffect(()=>{b&&null!=z&&se(z)},[b,z,se]),r&&a&&n&&l?_jsxs("div",{className:"vblock",style:{display:"flex",flexDirection:"column",gap:"1rem"},children:[_jsx(Header,{meta:m,config:m?.config,runId:n,projectId:r,methodId:a,canViewRaw:!!l.raw_data,onViewRaw:async()=>{j(!0);let e=F;0===e.length&&(e=await le(),H(e));const t=e.length>0?e[e.length-1]:null,r=z??t;z!==r&&P(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:B,options:$.map(e=>({label:e.view.title??e.name,value:e.name})),onChange:e=>V(e.value),placeholder:0===$.length?"No view defined":"Select a view",disabled:0===$.length}),_jsx("h3",{style:{margin:0},children:G?.title??""}),W&&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:()=>C.current?.resetZoom?.(),disabled:!Q,tooltip:"Reset chart zoom",tooltipOptions:{position:"left"},"aria-label":"Reset chart zoom"})]}),_jsxs("div",{style:{height:i,position:"relative"},children:[W&&K.loading&&_jsx(ChartOverlay,{children:"Loading raw data…"}),W&&K.error&&_jsx(ChartOverlay,{children:K.error}),Q&&_jsx(Line,{ref:C,data:Q,options:re})]})]}),_jsxs("div",{className:"p-card",style:{padding:"1rem"},children:[_jsxs("h3",{style:{marginTop:0},children:["Cycle Data (",y.length,")"]}),(()=>{const e=y.length>CYCLE_VIRTUAL_THRESHOLD;return _jsx(DataTable,{value:y,scrollable:e,scrollHeight:e?o:void 0,virtualScrollerOptions:e?{itemSize:38}:void 0,emptyMessage:"No cycles yet.",children:l.cycle_fields.map(e=>_jsx(Column,{field:e.name,header:e.units?`${e.name} (${e.units})`:e.name,body:t=>formatCell(t[e.name],e.type,e.scale)},e.name))})})()]}),_jsxs("div",{className:"p-card",style:{padding:"1rem"},children:[_jsx("h3",{style:{marginTop:0},children:"Results"}),_jsx(ResultsGrid,{schema:l.results_fields,values:{...f,..._}})]}),l.raw_data&&_jsxs(Dialog,{visible:b,onHide:()=>j(!1),header:`Run Data — ${n}`,style:{width:"90vw",height:"80vh"},maximizable:!0,children:[_jsx(CyclePickerBar,{cycles:F,selected:z,onChange:P}),_jsx(RawEnvelopeHeader,{envelope:S}),_jsxs(TabView,{style:{height:"100%"},children:[_jsx(TabPanel,{header:"Raw Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(S),loading:L,error:T,rawData:l.raw_data})}),_jsx(TabPanel,{header:"Filtered Data",children:_jsx(DataBlobTable,{blob:unwrapEnvelope(O),loading:N,error:A,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:s,onShowConfig:o})=>{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:o,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:s,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??{}),s=Object.keys(e).filter(t=>Array.isArray(e[t])),o=[];for(const t of l)Array.isArray(e[t])&&o.push(t);for(const e of s)o.includes(e)||o.push(e);if(0===o.length)return _jsx("div",{style:{padding:"1rem",color:"var(--text-secondary-color)"},children:n??"No columnar data in this blob."});const i=o.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 o)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"}}),o.map(e=>_jsx(Column,{field:e,header:u(e),style:{minWidth:"8rem"},bodyStyle:{fontVariantNumeric:"tabular-nums",textAlign:"right"},body:t=>formatNumeric(t[e])},e))]})},formatNumeric=e=>null==e?"":"number"==typeof e&&Number.isFinite(e)?Number.parseFloat(e.toPrecision(6)).toString():String(e),ResultsGrid=({schema:e,values:t})=>t&&0!==Object.keys(t).length?_jsx("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(220px, 1fr))",gap:"0.5rem 1rem"},children:e.map(e=>_jsxs("div",{children:[_jsxs("div",{style:{fontSize:"0.8em",color:"var(--text-secondary-color)"},children:[e.name,e.units?` (${e.units})`:""]}),_jsx("div",{children:formatCell(t[e.name],e.type,e.scale)})]},e.name))}):_jsx("div",{style:{color:"var(--text-secondary-color)"},children:"No results yet."}),CYCLE_VIRTUAL_THRESHOLD=30,CHART_COLORS=["#4ea8de","#f59e0b","#22c55e","#a855f7","#ef4444","#14b8a6","#eab308","#ec4899"],palette=e=>CHART_COLORS[e%CHART_COLORS.length],SG_ORDER=3,SMOOTH_TARGET_POINTS=800;function invertMatrix(e){const t=e.length,r=e.map((e,r)=>[...e,...Array.from({length:t},(e,t)=>r===t?1:0)]);for(let e=0;e<t;e++){let a=e;for(let n=e+1;n<t;n++)Math.abs(r[n][e])>Math.abs(r[a][e])&&(a=n);if(Math.abs(r[a][e])<1e-12)return null;[r[e],r[a]]=[r[a],r[e]];const n=r[e][e];for(let a=0;a<2*t;a++)r[e][a]/=n;for(let a=0;a<t;a++){if(a===e)continue;const n=r[a][e];if(0!==n)for(let l=0;l<2*t;l++)r[a][l]-=n*r[e][l]}}return r.map(e=>e.slice(t))}function sgWeights(e,t){const r=2*e+1,a=Math.min(t,r-1),n=[];for(let t=-e;t<=e;t++){const e=[];let r=1;for(let n=0;n<=a;n++)e.push(r),r*=t;n.push(e)}const l=a+1,s=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];s[e][t]=a}const o=invertMatrix(s);if(!o)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+=o[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 s=Math.min(t,a,r-1-a);if(s<1){l[a]=e[a];continue}const o=n(s);let i=0;for(let t=-s;t<=s;t++)i+=o[t+s]*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),s=Math.max(1,Math.ceil(a/SMOOTH_TARGET_POINTS)),o=[];for(let t=0;t<a;t+=s)o.push({x:e[t],y:l[t]});return(a-1)%s!=0&&o.push({x:e[a-1],y:l[a-1]}),o}const DEFAULT_REGION_FILL="rgba(78, 168, 222, 0.15)",REGION_LABEL_COLOR="rgba(226, 232, 240, 0.85)";function buildRegionAnnotations(e){if(!e||0===e.length)return{};const t={};return e.forEach((e,r)=>{const a=!!e.borderColor;t[`region-${r}`]={type:"box",xScaleID:"x",xMin:e.xMin,xMax:e.xMax,backgroundColor:e.color??DEFAULT_REGION_FILL,borderColor:a?e.borderColor:"transparent",borderWidth:a?1:0,drawTime:"beforeDatasetsDraw",...e.label?{label:{display:!0,content:e.label,position:{x:"center",y:"start"},color:REGION_LABEL_COLOR,font:{size:11}}}:{}}}),t}const ChartOverlay=({children:e})=>_jsx("div",{style:{position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",color:"var(--text-secondary-color)",pointerEvents:"none"},children:e}),seriesLabel=e=>e.label??e.field??e.column??"",leftAxisLabel=e=>e?.y.filter(e=>"right"!==e.y_axis).map(seriesLabel).join(" / ")??"",rightAxisLabel=e=>e?.y.filter(e=>"right"===e.y_axis).map(seriesLabel).join(" / ")??"",formatCell=(e,t,r)=>null==e?"":(r&&1!==r&&"number"==typeof e&&Number.isFinite(e)&&(e*=r),"f32"===t||"f64"===t?"number"==typeof e?e.toFixed(4):String(e):"object"==typeof e?JSON.stringify(e):String(e));
@@ -1 +1 @@
1
- {"version":3,"file":"TestSetupForm.d.ts","sourceRoot":"","sources":["../../../src/components/tis/TestSetupForm.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAexE;;;;;GAKG;AACH,UAAU,WAAW;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,aAAa,GAAG,aAAa,CAAC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IACzB,iEAAiE;IACjE,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;0EACsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;iEAO6D;IAC7D,OAAO,CAAC,EAAE,GAAG,CAAC;IACd;;;;;;;;;;8DAU0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;+EAE2E;IAC3E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;6CAMyC;IACzC,OAAO,CAAC,EAAE,KAAK,CACT,MAAM,GACN,MAAM,GACN,OAAO,GACP;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;KAAE,CACzD,CAAC;CACL;AAED,MAAM,WAAW,UAAU;IACvB,cAAc,EAAE,YAAY,EAAE,CAAC;IAC/B,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,cAAc,EAAE,YAAY,EAAE,CAAC;IAC/B;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,WAAW,EAAE,CAAC;IAC3B;0CACsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;2CACuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;gEAG4D;IAC5D,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf;;sDAEkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,iBAAiB,EAAE,CAAC;CACxC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAC9B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IAC/B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;CAChE;AAuKD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAynBtD,CAAC"}
1
+ {"version":3,"file":"TestSetupForm.d.ts","sourceRoot":"","sources":["../../../src/components/tis/TestSetupForm.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAexE;;;;;GAKG;AACH,UAAU,WAAW;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,aAAa,GAAG,aAAa,CAAC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IACzB,iEAAiE;IACjE,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;0EACsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;iEAO6D;IAC7D,OAAO,CAAC,EAAE,GAAG,CAAC;IACd;;;;;;;;;;8DAU0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;+EAE2E;IAC3E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;6CAMyC;IACzC,OAAO,CAAC,EAAE,KAAK,CACT,MAAM,GACN,MAAM,GACN,OAAO,GACP;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;KAAE,CACzD,CAAC;CACL;AAED,MAAM,WAAW,UAAU;IACvB,cAAc,EAAE,YAAY,EAAE,CAAC;IAC/B,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,cAAc,EAAE,YAAY,EAAE,CAAC;IAC/B;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,WAAW,EAAE,CAAC;IAC3B;0CACsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;2CACuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;gEAG4D;IAC5D,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf;;sDAEkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,iBAAiB,EAAE,CAAC;CACxC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAC9B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IAC/B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;CAChE;AAuKD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAqoBtD,CAAC"}
@@ -1 +1 @@
1
- import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import React,{useState,useEffect,useContext,useMemo}from"react";import{Button}from"primereact/button";import{InputText}from"primereact/inputtext";import{Dropdown}from"primereact/dropdown";import{Dialog}from"primereact/dialog";import{EventEmitterContext}from"../../core/EventEmitterContext";import{AutoCoreTagContext}from"../../core/AutoCoreTagContext";import{MessageType}from"../../hub/CommandMessage";import{ValueInput}from"../ValueInput";import{TextInput}from"../TextInput";import{useTis}from"./TisProvider";import{useAmsAssets,useAmsRoles}from"../ams/AmsProvider";import{TestMethodDialog}from"./TestMethodDialog";import{ConfigurationDialog,configLabelOf}from"./ConfigurationDialog";const labelOf=e=>{const t=e.label&&e.label.length>0?e.label:e.name;return e.units?`${t} [${e.units}]`:t},rawToDisplay=(e,t)=>t&&1!==t&&"number"==typeof e&&Number.isFinite(e)?e*t:e,displayToRaw=(e,t)=>t&&1!==t&&"number"==typeof e&&Number.isFinite(e)?e/t:e,hasDescription=e=>"string"==typeof e.description&&e.description.length>0,DEFAULT_TOKEN_RE=/^\$\{\s*([^}]+?)\s*\}$/,resolveDefaultRaw=(e,t,s)=>{const i=e.default;if("string"==typeof i){const e=i.match(DEFAULT_TOKEN_RE);if(e){const i=t(e[1]),a=i?s[i.tagName]:void 0;return null==a?{raw:void 0,ok:!1}:{raw:a,ok:!0}}}return{raw:displayToRaw(i,e.scale),ok:!0}},rangeIssue=(e,t)=>{if(void 0===t||""===t||null===t)return null;if(null==e.min&&null==e.max)return null;const s=Number(rawToDisplay(Number(t),e.scale));return Number.isFinite(s)?null!=e.min&&s<e.min?`must be ≥ ${e.min}`:null!=e.max&&s>e.max?`must be ≤ ${e.max}`:null:null},normalizeOptions=e=>(e??[]).map(e=>null!==e&&"object"==typeof e?{label:String(e.label??e.value),value:e.value}:{label:String(e),value:e}),methodLabelOf=(e,t)=>t?.label&&t.label.length>0?t.label:e,AssetIdPicker=({assetType:e,value:t,onChange:s,invalid:i})=>{const a=useAmsAssets(),n=useAmsRoles(),o=useMemo(()=>a.filter(t=>t.asset_type===e&&"active"===t.status).map(e=>{const t=n[e.asset_type]?.find(t=>t.location===e.location)?.label,s=[e.asset_id];return t?s.push(`— ${t}`):e.location&&s.push(`— ${e.location}`),e.serial&&s.push(`(s/n ${e.serial})`),{label:s.join(" "),value:e.asset_id}}),[a,n,e]);return _jsx(Dropdown,{value:t,options:o,onChange:e=>s(e.value??""),placeholder:0===o.length?`No active ${e} assets registered — add one in Settings → Assets`:`Select ${e}…`,className:"ac-dropdown-clearable"+(i?" p-invalid":""),filter:!0,showClear:!0,disabled:0===o.length})};export const TestSetupForm=({schema:e,defaultMethodId:t,onMethodChange:s,onValidationChange:i})=>{const a=useTis(),{invoke:n,write:o}=useContext(EventEmitterContext),{rawValues:r,findTagByFqdn:l}=useContext(AutoCoreTagContext),c=useMemo(()=>Object.keys(a.schemas),[a.schemas]),d=a.selection.projectId,m=""!==d.trim()&&a.projectKnown(d.trim()),[u,p]=useState(a.selection.methodId||t||a.defaultMethodId||""),[f,h]=useState(a.selection.sampleId||""),g=a.stagedConfig,x=e=>{const t="function"==typeof e?e(a.stagedConfig):e;t!==a.stagedConfig&&a.setStagedConfig(t)},_=e??(u?a.schemas[u]:void 0),j=(e,t)=>{for(const[s,i]of Object.entries(e.defaults??{})){if("sample_id"===s)continue;const e=_?.config_fields.find(e=>e.name===s),a=displayToRaw(i,e?.scale);t[s]=a,e?.source&&Promise.resolve().then(()=>o(e.source,a)).catch(e=>{})}return t};useEffect(()=>{a.selection.methodId!==u&&u&&a.setSelection({methodId:u}),s&&s(u)},[u]),useEffect(()=>{a.selection.sampleId!==f&&a.setSelection({sampleId:f})},[f]),useEffect(()=>{a.state.stagedSampleId&&a.state.stagedSampleId!==f&&h(a.state.stagedSampleId)},[a.state.stagedSampleId]),useEffect(()=>{a.selection.methodId&&a.selection.methodId!==u&&p(a.selection.methodId)},[a.selection.methodId]);const[b,y]=useState(!1),[v,N]=useState(!1),[C,I]=useState(!1),T=_?.configurations??[],S=T.find(e=>e.name===a.configurationName),[w,k]=useState({open:!1,title:"",body:null});useEffect(()=>{if(!_||!u)return;if(a.defaultsAppliedForMethod===u)return;a.markDefaultsAppliedForMethod(u);const e=_.configurations&&_.configurations.length>0?_.configurations[0]:void 0;x(t=>{let s=t;for(const e of _.config_fields){if("sample_id"===e.name)continue;if(void 0===e.default||null===e.default)continue;const{raw:i,ok:a}=resolveDefaultRaw(e,l,r);a&&(s===t&&(s={...t}),s[e.name]=i,e.source&&Promise.resolve().then(()=>o(e.source,i)).catch(e=>{}))}return e&&(s===t&&(s={...t}),s=j(e,s)),s}),a.setConfigurationName(e?e.name:"")},[_,u,o,r,l,a.defaultsAppliedForMethod,a.markDefaultsAppliedForMethod,a.setConfigurationName]),useEffect(()=>{_&&x(e=>{let t=e;for(const s of _.config_fields){if("sample_id"===s.name)continue;if(!s.source)continue;const i=l(s.source);if(!i)continue;const a=r[i.tagName];null!=a&&(t[s.name]!==a&&(t===e&&(t={...e}),t[s.name]=a))}return t})},[_,r,l]),useEffect(()=>{if(!_)return void y(!1);let e=!0;m||(e=!1),u.trim()||(e=!1),f.trim()||(e=!1),e&&!a.projectFieldsLoaded&&(e=!1);for(const t of _.config_fields){if("sample_id"===t.name)continue;const s=g[t.name],i=void 0===s||""===s||null===s;if(t.required&&i){e=!1;break}if(rangeIssue(t,s)){e=!1;break}}if(y(e),i&&i(e,g),e){const{sample_id:e,...t}=g??{},s={...a.projectFields,...t};n("tis.stage_test",MessageType.Request,{project_id:d,method_id:u,sample_id:f,config:s}).catch(e=>{})}},[g,_,d,u,f,m,a.projectFields,a.projectFieldsLoaded,i,n]);const A=async(e,t)=>{const s=displayToRaw(t,e.scale);if(x({...g,[e.name]:s}),e.source)try{await o(e.source,s)}catch(e){}};if(!_)return _jsx("div",{className:"ac-form-grid",style:{padding:"1.25rem"},children:_jsx("h3",{className:"ac-form-section",children:a.schemasLoaded?"No Test Method Selected":"Loading test methods…"})});if(!m)return _jsxs("div",{style:{padding:"1.25rem",maxWidth:"600px"},children:[_jsx("h3",{className:"ac-form-section",children:"No project selected"}),_jsxs("p",{style:{color:"var(--text-secondary-color)",marginTop:"0.5rem"},children:["Pick a project on the ",_jsx("strong",{children:"Project"})," tab first",""!==d.trim()&&` (or click + there to create "${d.trim()}")`,"."]})]});const D={padding:"1.25rem",gridTemplateColumns:"auto 1fr 1.75rem 1.75rem"},F=b&&!a.state.lastStartError;return _jsxs("div",{className:"ac-test-setup-form",children:[_jsxs("div",{className:"ac-form-grid ac-test-setup-form__head",style:D,children:[_jsxs("h3",{className:"ac-form-section",style:{display:"flex",alignItems:"center",gap:"10px"},children:["Test Setup",_jsx(Button,{icon:F?"pi pi-check-circle":"pi pi-exclamation-circle",severity:F?"success":"danger",text:!0,rounded:!0,"aria-label":"Test Setup status",onClick:()=>{const e=(()=>{const e=[];if(m||e.push("No project is selected. Pick or create one on the Project tab."),a.projectFieldsLoaded||e.push("Project fields are still loading."),u.trim()||e.push("No test method selected."),f.trim()||e.push("Sample ID is required."),_)for(const t of _.config_fields){if("sample_id"===t.name)continue;const s=g[t.name],i=void 0===s||""===s||null===s;if(t.required&&i){e.push(`Required field "${labelOf(t)}" is empty.`);continue}const a=rangeIssue(t,s);a&&e.push(`"${labelOf(t)}" ${a}.`)}return e})(),t=a.state.lastStartError?.trim()??"";let s;s=0!==e.length||t?_jsxs("div",{children:[e.length>0&&_jsxs(_Fragment,{children:[_jsx("p",{style:{margin:"0 0 0.5rem 0"},children:"The form is incomplete:"}),_jsx("ul",{style:{margin:"0 0 1rem 1.25rem"},children:e.map((e,t)=>_jsx("li",{children:e},t))})]}),t&&_jsxs(_Fragment,{children:[_jsx("p",{style:{margin:"0 0 0.25rem 0",fontWeight:600},children:"Last start_test error from the server:"}),_jsx("pre",{style:{margin:0,padding:"0.5rem",background:"rgba(0,0,0,0.25)",borderRadius:"4px",whiteSpace:"pre-wrap",fontSize:"0.875rem"},children:t})]})]}):_jsx("p",{style:{margin:0},children:"All required fields are complete. The test is staged and ready to start."}),k({open:!0,title:"Test Setup Status",body:s})}}),_jsxs("span",{style:{fontSize:"0.85em",color:"var(--text-secondary-color)",fontWeight:"normal",marginLeft:"0.25rem"},children:["project: ",_jsx("strong",{children:d})]})]}),_jsx("span",{className:"ac-form-label",children:"Sample ID"}),_jsx(TextInput,{label:void 0,value:f,onValueChanged:e=>{h(e)},className:f.trim()?"":"p-invalid"}),_jsx("span",{"aria-hidden":"true"}),_jsx("span",{style:{color:f.trim()?"var(--green-500)":"var(--red-500)",display:"flex",alignItems:"center"},children:_jsx("i",{className:f.trim()?"pi pi-check":"pi pi-times"})}),c.length>0&&_jsxs(_Fragment,{children:[_jsx("span",{className:"ac-form-label",children:"Test Method"}),_jsxs("div",{className:"p-inputgroup",style:{flex:1},children:[_jsx(InputText,{value:methodLabelOf(u,_),readOnly:!0,style:{flex:1},tabIndex:-1}),_jsx(Button,{icon:"pi pi-folder",type:"button",onClick:()=>N(!0),tooltip:c.length>1?"Change test method":"View test method details",tooltipOptions:{position:"top"}})]}),_jsx("span",{"aria-hidden":"true"}),_jsx("span",{style:{color:u?"var(--green-500)":"var(--red-500)",display:"flex",alignItems:"center"},children:_jsx("i",{className:u?"pi pi-check":"pi pi-times"})})]}),T.length>0&&_jsxs(_Fragment,{children:[_jsx("span",{className:"ac-form-label",children:"Configuration"}),_jsxs("div",{className:"p-inputgroup",style:{flex:1},children:[_jsx(InputText,{value:S?configLabelOf(S):"",placeholder:"No configuration selected",readOnly:!0,style:{flex:1},tabIndex:-1}),_jsx(Button,{icon:"pi pi-folder",type:"button",onClick:()=>I(!0),tooltip:"Change configuration",tooltipOptions:{position:"top"}})]}),_jsx("span",{"aria-hidden":"true"}),_jsx("span",{style:{color:S?"var(--green-500)":"var(--text-secondary-color)",display:"flex",alignItems:"center"},children:_jsx("i",{className:S?"pi pi-check":"pi pi-minus"})})]})]}),_jsx("div",{className:"ac-test-setup-form__body",children:_jsxs("div",{className:"ac-form-grid",style:D,children:[_jsx("h3",{className:"ac-form-section",children:"Test Configuration"}),_.config_fields.map(e=>{if("sample_id"===e.name)return null;const t=(e=>{const t=g[e.name],s=void 0===t||""===t||null===t;return!(e.required&&s||rangeIssue(e,t))})(e),s=(e=>{const t=_?.asset_refs??[],s=a.projectAssetRefs??[],i=`config.${e.name}`,n=t.find(e=>"by_id_field"===e.select&&e.from===i);if(n)return n.asset_type;const o=s.find(e=>"by_id_field"===e.select&&e.from===i);return o?o.asset_type:null})(e),i=!s&&Array.isArray(e.options)&&e.options.length>0?normalizeOptions(e.options):null,n=!s&&!i&&"string"!==e.type&&"bool"!==e.type;return _jsxs(React.Fragment,{children:[_jsx("span",{className:"ac-form-label",children:labelOf(e)}),s?_jsx(AssetIdPicker,{assetType:s,value:null!=g[e.name]?String(g[e.name]):"",onChange:t=>A(e,t),invalid:!t}):i?_jsx(Dropdown,{value:g[e.name]??null,options:i,onChange:t=>A(e,t.value),placeholder:`Select ${e.label??e.name}…`,className:"ac-dropdown-clearable"+(t?"":" p-invalid"),showClear:!e.required}):n?_jsx(ValueInput,{label:void 0,value:null!=g[e.name]?Number(rawToDisplay(Number(g[e.name]),e.scale)):null,min:e.min,max:e.max,onValueChanged:t=>A(e,t),className:t?"":"p-invalid"}):_jsx(TextInput,{label:void 0,value:null!=g[e.name]?String(g[e.name]):"",onValueChanged:t=>A(e,t),className:t?"":"p-invalid"}),hasDescription(e)?_jsx(Button,{icon:"pi pi-info-circle",text:!0,rounded:!0,"aria-label":`About ${labelOf(e)}`,onClick:()=>k({open:!0,title:labelOf(e),body:_jsx("p",{style:{margin:0,whiteSpace:"pre-wrap"},children:e.description})})}):_jsx("span",{"aria-hidden":"true"}),_jsx("span",{style:{color:t?"var(--green-500)":"var(--red-500)",display:"flex",alignItems:"center"},children:_jsx("i",{className:t?"pi pi-check":"pi pi-times"})})]},e.name)})]})}),_jsx(TestMethodDialog,{visible:v,onHide:()=>N(!1),currentMethodId:u,onSelected:e=>p(e)}),_jsx(ConfigurationDialog,{visible:C,onHide:()=>I(!1),configurations:T,currentConfigName:a.configurationName,onSelected:e=>{const t=T.find(t=>t.name===e);t&&(x(e=>j(t,{...e})),a.setConfigurationName(e))}}),_jsx(Dialog,{header:w.title,visible:w.open,onHide:()=>k({open:!1,title:"",body:null}),style:{width:"32rem",maxWidth:"90vw"},modal:!0,dismissableMask:!0,children:w.body})]})};
1
+ import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import React,{useState,useEffect,useContext,useMemo}from"react";import{Button}from"primereact/button";import{InputText}from"primereact/inputtext";import{Dropdown}from"primereact/dropdown";import{Dialog}from"primereact/dialog";import{EventEmitterContext}from"../../core/EventEmitterContext";import{AutoCoreTagContext}from"../../core/AutoCoreTagContext";import{MessageType}from"../../hub/CommandMessage";import{ValueInput}from"../ValueInput";import{TextInput}from"../TextInput";import{useTis}from"./TisProvider";import{useAmsAssets,useAmsRoles}from"../ams/AmsProvider";import{TestMethodDialog}from"./TestMethodDialog";import{ConfigurationDialog,configLabelOf}from"./ConfigurationDialog";const labelOf=e=>{const t=e.label&&e.label.length>0?e.label:e.name;return e.units?`${t} [${e.units}]`:t},rawToDisplay=(e,t)=>t&&1!==t&&"number"==typeof e&&Number.isFinite(e)?e*t:e,displayToRaw=(e,t)=>t&&1!==t&&"number"==typeof e&&Number.isFinite(e)?e/t:e,hasDescription=e=>"string"==typeof e.description&&e.description.length>0,DEFAULT_TOKEN_RE=/^\$\{\s*([^}]+?)\s*\}$/,resolveDefaultRaw=(e,t,s)=>{const i=e.default;if("string"==typeof i){const e=i.match(DEFAULT_TOKEN_RE);if(e){const i=t(e[1]),a=i?s[i.tagName]:void 0;return null==a?{raw:void 0,ok:!1}:{raw:a,ok:!0}}}return{raw:displayToRaw(i,e.scale),ok:!0}},rangeIssue=(e,t)=>{if(void 0===t||""===t||null===t)return null;if(null==e.min&&null==e.max)return null;const s=Number(rawToDisplay(Number(t),e.scale));return Number.isFinite(s)?null!=e.min&&s<e.min?`must be ≥ ${e.min}`:null!=e.max&&s>e.max?`must be ≤ ${e.max}`:null:null},normalizeOptions=e=>(e??[]).map(e=>null!==e&&"object"==typeof e?{label:String(e.label??e.value),value:e.value}:{label:String(e),value:e}),methodLabelOf=(e,t)=>t?.label&&t.label.length>0?t.label:e,AssetIdPicker=({assetType:e,value:t,onChange:s,invalid:i})=>{const a=useAmsAssets(),n=useAmsRoles(),o=useMemo(()=>a.filter(t=>t.asset_type===e&&"active"===t.status).map(e=>{const t=n[e.asset_type]?.find(t=>t.location===e.location)?.label,s=[e.asset_id];return t?s.push(`— ${t}`):e.location&&s.push(`— ${e.location}`),e.serial&&s.push(`(s/n ${e.serial})`),{label:s.join(" "),value:e.asset_id}}),[a,n,e]);return _jsx(Dropdown,{value:t,options:o,onChange:e=>s(e.value??""),placeholder:0===o.length?`No active ${e} assets registered — add one in Settings → Assets`:`Select ${e}…`,className:"ac-dropdown-clearable"+(i?" p-invalid":""),filter:!0,showClear:!0,disabled:0===o.length})};export const TestSetupForm=({schema:e,defaultMethodId:t,onMethodChange:s,onValidationChange:i})=>{const a=useTis(),{invoke:n,write:o}=useContext(EventEmitterContext),{rawValues:r,findTagByFqdn:l}=useContext(AutoCoreTagContext),c=useMemo(()=>Object.keys(a.schemas),[a.schemas]),d=a.selection.projectId,m=""!==d.trim()&&a.projectKnown(d.trim()),[u,p]=useState(a.selection.methodId||t||a.defaultMethodId||""),[f,h]=useState(a.selection.sampleId||""),g=a.stagedConfig,x=e=>{const t="function"==typeof e?e(a.stagedConfig):e;t!==a.stagedConfig&&a.setStagedConfig(t)},_=e??(u?a.schemas[u]:void 0),j=(e,t)=>{for(const[s,i]of Object.entries(e.defaults??{})){if("sample_id"===s)continue;const e=_?.config_fields.find(e=>e.name===s),a=displayToRaw(i,e?.scale);t[s]=a,e?.source&&Promise.resolve().then(()=>o(e.source,a)).catch(e=>{})}return t};useEffect(()=>{a.selection.methodId!==u&&u&&a.setSelection({methodId:u}),s&&s(u)},[u]),useEffect(()=>{a.selection.sampleId!==f&&a.setSelection({sampleId:f})},[f]),useEffect(()=>{a.state.stagedSampleId&&a.state.stagedSampleId!==f&&h(a.state.stagedSampleId)},[a.state.stagedSampleId]),useEffect(()=>{a.selection.methodId&&a.selection.methodId!==u&&p(a.selection.methodId)},[a.selection.methodId]);const[b,v]=useState(!1),[y,N]=useState(!1),[C,I]=useState(!1),T=_?.configurations??[],S=T.find(e=>e.name===a.configurationName),[w,k]=useState({open:!1,title:"",body:null});useEffect(()=>{if(!_||!u)return;if(a.defaultsAppliedForMethod===u)return;a.markDefaultsAppliedForMethod(u);const e=_.configurations&&_.configurations.length>0?_.configurations[0]:void 0;x(t=>{let s=t;for(const e of _.config_fields){if("sample_id"===e.name)continue;if(void 0===e.default||null===e.default)continue;if(e.source){const t=l(e.source),s=t?r[t.tagName]:void 0;if(null!=s)continue}const{raw:i,ok:a}=resolveDefaultRaw(e,l,r);a&&(s===t&&(s={...t}),s[e.name]=i,e.source&&Promise.resolve().then(()=>o(e.source,i)).catch(e=>{}))}return e&&(s===t&&(s={...t}),s=j(e,s)),s}),a.setConfigurationName(e?e.name:"")},[_,u,o,r,l,a.defaultsAppliedForMethod,a.markDefaultsAppliedForMethod,a.setConfigurationName]),useEffect(()=>{_&&x(e=>{let t=e;for(const s of _.config_fields){if("sample_id"===s.name)continue;if(!s.source)continue;const i=l(s.source);if(!i)continue;const a=r[i.tagName];null!=a&&(t[s.name]!==a&&(t===e&&(t={...e}),t[s.name]=a))}return t})},[_,r,l]),useEffect(()=>{if(!_)return void v(!1);let e=!0;m||(e=!1),u.trim()||(e=!1),f.trim()||(e=!1),e&&!a.projectFieldsLoaded&&(e=!1);for(const t of _.config_fields){if("sample_id"===t.name)continue;const s=g[t.name],i=void 0===s||""===s||null===s;if(t.required&&i){e=!1;break}if(rangeIssue(t,s)){e=!1;break}}if(v(e),i&&i(e,g),e){const{sample_id:e,...t}=g??{},s={...a.projectFields,...t};n("tis.stage_test",MessageType.Request,{project_id:d,method_id:u,sample_id:f,config:s}).catch(e=>{})}},[g,_,d,u,f,m,a.projectFields,a.projectFieldsLoaded,i,n]);const A=async(e,t)=>{const s=displayToRaw(t,e.scale);if(x({...g,[e.name]:s}),e.source)try{await o(e.source,s)}catch(e){}};if(!_)return _jsx("div",{className:"ac-form-grid",style:{padding:"1.25rem"},children:_jsx("h3",{className:"ac-form-section",children:a.schemasLoaded?"No Test Method Selected":"Loading test methods…"})});if(!m)return _jsxs("div",{style:{padding:"1.25rem",maxWidth:"600px"},children:[_jsx("h3",{className:"ac-form-section",children:"No project selected"}),_jsxs("p",{style:{color:"var(--text-secondary-color)",marginTop:"0.5rem"},children:["Pick a project on the ",_jsx("strong",{children:"Project"})," tab first",""!==d.trim()&&` (or click + there to create "${d.trim()}")`,"."]})]});const D={padding:"1.25rem",gridTemplateColumns:"auto 1fr 1.75rem 1.75rem"},F=b&&!a.state.lastStartError;return _jsxs("div",{className:"ac-test-setup-form",children:[_jsxs("div",{className:"ac-form-grid ac-test-setup-form__head",style:D,children:[_jsxs("h3",{className:"ac-form-section",style:{display:"flex",alignItems:"center",gap:"10px"},children:["Test Setup",_jsx(Button,{icon:F?"pi pi-check-circle":"pi pi-exclamation-circle",severity:F?"success":"danger",text:!0,rounded:!0,"aria-label":"Test Setup status",onClick:()=>{const e=(()=>{const e=[];if(m||e.push("No project is selected. Pick or create one on the Project tab."),a.projectFieldsLoaded||e.push("Project fields are still loading."),u.trim()||e.push("No test method selected."),f.trim()||e.push("Sample ID is required."),_)for(const t of _.config_fields){if("sample_id"===t.name)continue;const s=g[t.name],i=void 0===s||""===s||null===s;if(t.required&&i){e.push(`Required field "${labelOf(t)}" is empty.`);continue}const a=rangeIssue(t,s);a&&e.push(`"${labelOf(t)}" ${a}.`)}return e})(),t=a.state.lastStartError?.trim()??"";let s;s=0!==e.length||t?_jsxs("div",{children:[e.length>0&&_jsxs(_Fragment,{children:[_jsx("p",{style:{margin:"0 0 0.5rem 0"},children:"The form is incomplete:"}),_jsx("ul",{style:{margin:"0 0 1rem 1.25rem"},children:e.map((e,t)=>_jsx("li",{children:e},t))})]}),t&&_jsxs(_Fragment,{children:[_jsx("p",{style:{margin:"0 0 0.25rem 0",fontWeight:600},children:"Last start_test error from the server:"}),_jsx("pre",{style:{margin:0,padding:"0.5rem",background:"rgba(0,0,0,0.25)",borderRadius:"4px",whiteSpace:"pre-wrap",fontSize:"0.875rem"},children:t})]})]}):_jsx("p",{style:{margin:0},children:"All required fields are complete. The test is staged and ready to start."}),k({open:!0,title:"Test Setup Status",body:s})}}),_jsxs("span",{style:{fontSize:"0.85em",color:"var(--text-secondary-color)",fontWeight:"normal",marginLeft:"0.25rem"},children:["project: ",_jsx("strong",{children:d})]})]}),_jsx("span",{className:"ac-form-label",children:"Sample ID"}),_jsx(TextInput,{label:void 0,value:f,onValueChanged:e=>{h(e)},className:f.trim()?"":"p-invalid"}),_jsx("span",{"aria-hidden":"true"}),_jsx("span",{style:{color:f.trim()?"var(--green-500)":"var(--red-500)",display:"flex",alignItems:"center"},children:_jsx("i",{className:f.trim()?"pi pi-check":"pi pi-times"})}),c.length>0&&_jsxs(_Fragment,{children:[_jsx("span",{className:"ac-form-label",children:"Test Method"}),_jsxs("div",{className:"p-inputgroup",style:{flex:1},children:[_jsx(InputText,{value:methodLabelOf(u,_),readOnly:!0,style:{flex:1},tabIndex:-1}),_jsx(Button,{icon:"pi pi-folder",type:"button",onClick:()=>N(!0),tooltip:c.length>1?"Change test method":"View test method details",tooltipOptions:{position:"top"}})]}),_jsx("span",{"aria-hidden":"true"}),_jsx("span",{style:{color:u?"var(--green-500)":"var(--red-500)",display:"flex",alignItems:"center"},children:_jsx("i",{className:u?"pi pi-check":"pi pi-times"})})]}),T.length>0&&_jsxs(_Fragment,{children:[_jsx("span",{className:"ac-form-label",children:"Configuration"}),_jsxs("div",{className:"p-inputgroup",style:{flex:1},children:[_jsx(InputText,{value:S?configLabelOf(S):"",placeholder:"No configuration selected",readOnly:!0,style:{flex:1},tabIndex:-1}),_jsx(Button,{icon:"pi pi-folder",type:"button",onClick:()=>I(!0),tooltip:"Change configuration",tooltipOptions:{position:"top"}})]}),_jsx("span",{"aria-hidden":"true"}),_jsx("span",{style:{color:S?"var(--green-500)":"var(--text-secondary-color)",display:"flex",alignItems:"center"},children:_jsx("i",{className:S?"pi pi-check":"pi pi-minus"})})]})]}),_jsx("div",{className:"ac-test-setup-form__body",children:_jsxs("div",{className:"ac-form-grid",style:D,children:[_jsx("h3",{className:"ac-form-section",children:"Test Configuration"}),_.config_fields.map(e=>{if("sample_id"===e.name)return null;const t=(e=>{const t=g[e.name],s=void 0===t||""===t||null===t;return!(e.required&&s||rangeIssue(e,t))})(e),s=(e=>{const t=_?.asset_refs??[],s=a.projectAssetRefs??[],i=`config.${e.name}`,n=t.find(e=>"by_id_field"===e.select&&e.from===i);if(n)return n.asset_type;const o=s.find(e=>"by_id_field"===e.select&&e.from===i);return o?o.asset_type:null})(e),i=!s&&Array.isArray(e.options)&&e.options.length>0?normalizeOptions(e.options):null,n=!s&&!i&&"string"!==e.type&&"bool"!==e.type;return _jsxs(React.Fragment,{children:[_jsx("span",{className:"ac-form-label",children:labelOf(e)}),s?_jsx(AssetIdPicker,{assetType:s,value:null!=g[e.name]?String(g[e.name]):"",onChange:t=>A(e,t),invalid:!t}):i?_jsx(Dropdown,{value:g[e.name]??null,options:i,onChange:t=>A(e,t.value),placeholder:`Select ${e.label??e.name}…`,className:"ac-dropdown-clearable"+(t?"":" p-invalid"),showClear:!e.required}):n?_jsx(ValueInput,{label:void 0,value:null!=g[e.name]?Number(rawToDisplay(Number(g[e.name]),e.scale)):null,min:e.min,max:e.max,onValueChanged:t=>A(e,t),className:t?"":"p-invalid"}):_jsx(TextInput,{label:void 0,value:null!=g[e.name]?String(g[e.name]):"",onValueChanged:t=>A(e,t),className:t?"":"p-invalid"}),hasDescription(e)?_jsx(Button,{icon:"pi pi-info-circle",text:!0,rounded:!0,"aria-label":`About ${labelOf(e)}`,onClick:()=>k({open:!0,title:labelOf(e),body:_jsx("p",{style:{margin:0,whiteSpace:"pre-wrap"},children:e.description})})}):_jsx("span",{"aria-hidden":"true"}),_jsx("span",{style:{color:t?"var(--green-500)":"var(--red-500)",display:"flex",alignItems:"center"},children:_jsx("i",{className:t?"pi pi-check":"pi pi-times"})})]},e.name)})]})}),_jsx(TestMethodDialog,{visible:y,onHide:()=>N(!1),currentMethodId:u,onSelected:e=>p(e)}),_jsx(ConfigurationDialog,{visible:C,onHide:()=>I(!1),configurations:T,currentConfigName:a.configurationName,onSelected:e=>{const t=T.find(t=>t.name===e);t&&(x(e=>j(t,{...e})),a.setConfigurationName(e))}}),_jsx(Dialog,{header:w.title,visible:w.open,onHide:()=>k({open:!1,title:"",body:null}),style:{width:"32rem",maxWidth:"90vw"},modal:!0,dismissableMask:!0,children:w.body})]})};
@@ -1 +1 @@
1
- {"version":3,"file":"FieldArrayEditor.d.ts","sourceRoot":"","sources":["../../../../src/components/tis-editor/editor/FieldArrayEditor.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAa,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAgBrE,MAAM,WAAW,qBAAqB;IAClC,QAAQ,EAAE,aAAa,CAAC;IACxB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACxC;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAsG5D,CAAC"}
1
+ {"version":3,"file":"FieldArrayEditor.d.ts","sourceRoot":"","sources":["../../../../src/components/tis-editor/editor/FieldArrayEditor.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAa,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAuBrE,MAAM,WAAW,qBAAqB;IAClC,QAAQ,EAAE,aAAa,CAAC;IACxB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACxC;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAgH5D,CAAC"}
@@ -1 +1 @@
1
- import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import{useState}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Button}from"primereact/button";import{FormSection}from"../../forms/FormSection";import{TestFieldDialog}from"./TestFieldDialog";const TITLES={project_fields:"Project Fields",config_fields:"Config Fields",cycle_fields:"Cycle Fields",results_fields:"Results Fields"},DESCRIPTIONS={project_fields:"System-level fields (operator, station). Filled by the HMI but not cycled.",config_fields:"Operator-input config (speeds, loads). Snapshotted into test.json on start.",cycle_fields:"Per-cycle capture. One row appended to cycles.jsonl per cycle.",results_fields:"Post-test summary (min/max/avg, pass/fail). Written once at finish."};export const FieldArrayEditor=({arrayKey:e,method:t,onChange:i})=>{const o=t[e]??[],[l,s]=useState(!1),[n,r]=useState(null),a=(l,s)=>{const n=l+s;if(n<0||n>=o.length)return;const r=[...o];[r[l],r[n]]=[r[n],r[l]],i({...t,[e]:r})};return _jsxs(_Fragment,{children:[_jsx(FormSection,{title:TITLES[e],description:DESCRIPTIONS[e],actions:_jsx(Button,{label:"Add field",icon:"pi pi-plus",size:"small",onClick:()=>{r(null),s(!0)}}),children:_jsxs(DataTable,{value:o,dataKey:"name",emptyMessage:"No fields defined.",children:[_jsx(Column,{field:"name",header:"Name"}),_jsx(Column,{field:"type",header:"Type",style:{width:"6rem"}}),_jsx(Column,{field:"units",header:"Units",style:{width:"6rem"}}),_jsx(Column,{header:"Req",body:e=>e.required?"✓":"",style:{width:"4rem"}}),_jsx(Column,{field:"label",header:"Label"}),_jsx(Column,{header:"",body:(l,n)=>_jsxs("div",{style:{display:"flex",gap:"0.25rem"},children:[_jsx(Button,{icon:"pi pi-arrow-up",className:"p-button-text p-button-sm",disabled:0===n.rowIndex,onClick:()=>a(n.rowIndex,-1),"aria-label":"Move up"}),_jsx(Button,{icon:"pi pi-arrow-down",className:"p-button-text p-button-sm",disabled:n.rowIndex===o.length-1,onClick:()=>a(n.rowIndex,1),"aria-label":"Move down"}),_jsx(Button,{icon:"pi pi-pencil",className:"p-button-text p-button-sm",onClick:()=>{return e=n.rowIndex,r(e),void s(!0);var e},"aria-label":"Edit"}),_jsx(Button,{icon:"pi pi-trash",className:"p-button-text p-button-danger p-button-sm",onClick:()=>(l=>{const s=o[l];if(!s)return;if(!window.confirm(`Remove field "${s.name}"?`))return;const n=o.filter((e,t)=>t!==l);i({...t,[e]:n})})(n.rowIndex),"aria-label":"Remove"})]}),style:{width:"10rem"}})]})}),_jsx(TestFieldDialog,{visible:l,initial:null!==n?o[n]??null:null,siblingNames:o.map(e=>e.name),onCancel:()=>s(!1),onSave:l=>{const r=[...o];null===n?r.push(l):r[n]=l,i({...t,[e]:r}),s(!1)}})]})};
1
+ import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import{useState}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Button}from"primereact/button";import{FormSection}from"../../forms/FormSection";import{TestFieldDialog}from"./TestFieldDialog";const TITLES={project_fields:"Project Fields",config_fields:"Config Fields",cycle_fields:"Cycle Fields",results_fields:"Results Fields"},DESCRIPTIONS={project_fields:"System-level fields (operator, station). Filled by the HMI but not cycled.",config_fields:"Operator-input config (speeds, loads). Snapshotted into test.json on start.",cycle_fields:"Per-cycle capture. One row appended to cycles.jsonl per cycle.",results_fields:"Post-test summary (min/max/avg, pass/fail). Written once at finish. A field can also be a cross-test Aggregate (avg/min/max/stddev/count over tests sharing a sample_id)."},aggregateLabel=e=>{const t=e.aggregate;return t?"count"===t.fn?"count":`${t.fn}(${t.of??"?"})`:""};export const FieldArrayEditor=({arrayKey:e,method:t,onChange:s})=>{const l=t[e]??[],[i,o]=useState(!1),[n,a]=useState(null),r=(i,o)=>{const n=i+o;if(n<0||n>=l.length)return;const a=[...l];[a[i],a[n]]=[a[n],a[i]],s({...t,[e]:a})};return _jsxs(_Fragment,{children:[_jsx(FormSection,{title:TITLES[e],description:DESCRIPTIONS[e],actions:_jsx(Button,{label:"Add field",icon:"pi pi-plus",size:"small",onClick:()=>{a(null),o(!0)}}),children:_jsxs(DataTable,{value:l,dataKey:"name",emptyMessage:"No fields defined.",children:[_jsx(Column,{field:"name",header:"Name"}),_jsx(Column,{field:"type",header:"Type",style:{width:"6rem"}}),_jsx(Column,{field:"units",header:"Units",style:{width:"6rem"}}),_jsx(Column,{header:"Req",body:e=>e.required?"✓":"",style:{width:"4rem"}}),_jsx(Column,{field:"label",header:"Label"}),"results_fields"===e&&_jsx(Column,{header:"Aggregate",body:e=>aggregateLabel(e),style:{width:"8rem"}}),_jsx(Column,{header:"",body:(i,n)=>_jsxs("div",{style:{display:"flex",gap:"0.25rem"},children:[_jsx(Button,{icon:"pi pi-arrow-up",className:"p-button-text p-button-sm",disabled:0===n.rowIndex,onClick:()=>r(n.rowIndex,-1),"aria-label":"Move up"}),_jsx(Button,{icon:"pi pi-arrow-down",className:"p-button-text p-button-sm",disabled:n.rowIndex===l.length-1,onClick:()=>r(n.rowIndex,1),"aria-label":"Move down"}),_jsx(Button,{icon:"pi pi-pencil",className:"p-button-text p-button-sm",onClick:()=>{return e=n.rowIndex,a(e),void o(!0);var e},"aria-label":"Edit"}),_jsx(Button,{icon:"pi pi-trash",className:"p-button-text p-button-danger p-button-sm",onClick:()=>(i=>{const o=l[i];if(!o)return;if(!window.confirm(`Remove field "${o.name}"?`))return;const n=l.filter((e,t)=>t!==i);s({...t,[e]:n})})(n.rowIndex),"aria-label":"Remove"})]}),style:{width:"10rem"}})]})}),_jsx(TestFieldDialog,{visible:i,initial:null!==n?l[n]??null:null,siblingNames:l.map(e=>e.name),isResultsField:"results_fields"===e,cycleFieldNames:(t.cycle_fields??[]).map(e=>e.name).filter(Boolean),resultsFieldNames:(t.results_fields??[]).map(e=>e.name).filter(Boolean),onCancel:()=>o(!1),onSave:i=>{const a=[...l];null===n?a.push(i):a[n]=i,s({...t,[e]:a}),o(!1)}})]})};
@@ -6,6 +6,12 @@ export interface TestFieldDialogProps {
6
6
  onSave: (field: TestField) => void;
7
7
  /** Names already in use within the same array — for uniqueness validation. */
8
8
  siblingNames: string[];
9
+ /** True when this field belongs to `results_fields` — only then is the
10
+ * cross-test Aggregate section shown. */
11
+ isResultsField?: boolean;
12
+ /** Candidate target field names for an aggregate's `of`, by source. */
13
+ cycleFieldNames?: string[];
14
+ resultsFieldNames?: string[];
9
15
  }
10
16
  export declare const TestFieldDialog: React.FC<TestFieldDialogProps>;
11
17
  //# sourceMappingURL=TestFieldDialog.d.ts.map
@@ -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,EAAE,MAAM,UAAU,CAAC;AAa1C,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;CAC1B;AAID,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA2J1D,CAAC"}
1
+ {"version":3,"file":"TestFieldDialog.d.ts","sourceRoot":"","sources":["../../../../src/components/tis-editor/editor/TestFieldDialog.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAiC,MAAM,UAAU,CAAC;AA4BzE,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,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,CAuN1D,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{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"}],blank={name:"",type:"f32"};export const TestFieldDialog=({visible:e,initial:a,onCancel:t,onSave:l,siblingNames:n})=>{const[i,r]=useState(blank),[o,m]=useState(""),[s,u]=useState(null);useEffect(()=>{e&&(r(a?{...a}:{...blank}),m(null==a?.default?"":String(a.default)),u(null))},[e,a]);return _jsxs(Dialog,{header:a?`Edit field: ${a.name}`:"New field",visible:e,onHide:t,style:{width:"36rem"},children:[s&&_jsx("div",{style:{color:"#dc2626",marginBottom:"0.75rem"},children:s}),_jsx(FormRow,{label:"Name",required:!0,hint:"Wire-format key. Also the column name in CSV exports.",children:_jsx(InputText,{value:i.name,onChange:e=>r({...i,name:e.target.value})})}),_jsx(FormRow,{label:"Type",required:!0,children:_jsx(Dropdown,{value:i.type,options:FIELD_TYPES,onChange:e=>r({...i,type:e.value}),editable:!0})}),_jsx(FormRow,{label:"Units",hint:"Display label, appended to form labels (e.g. m/s).",children:_jsx(InputText,{value:i.units??"",onChange:e=>r({...i,units:e.target.value||void 0})})}),_jsx(FormRow,{label:"Label",hint:"Pretty form label. Falls back to name when empty.",children:_jsx(InputText,{value:i.label??"",onChange:e=>r({...i,label:e.target.value||void 0})})}),_jsx(FormRow,{label:"Required",children:_jsx(Checkbox,{checked:!!i.required,onChange:e=>r({...i,required:!!e.checked})})}),_jsx(FormRow,{label:"Source",hint:"Optional gm.* variable to bind this field to.",children:_jsx(InputText,{value:i.source??"",onChange:e=>r({...i,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:i.scale??null,onValueChange:e=>r({...i,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:i.min??null,onValueChange:e=>r({...i,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:i.max??null,onValueChange:e=>r({...i,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:o,onChange:e=>m(e.target.value),placeholder:"e.g. 5 or ${gm.safe_speed}"})}),_jsx(FormRow,{label:"Description",hint:"Hover tooltip in the form.",children:_jsx(InputTextarea,{rows:2,value:i.description??"",onChange:e=>r({...i,description:e.target.value||void 0})})}),_jsxs("div",{style:{display:"flex",justifyContent:"flex-end",gap:"0.5rem",marginTop:"1rem"},children:[_jsx(Button,{label:"Cancel",className:"p-button-text",onClick:t}),_jsx(Button,{label:"Save",onClick:()=>{const e={...i},t=(e=>{const a=e.trim();if(""!==a){if(a.startsWith("${"))return a;if("true"===a)return!0;if("false"===a)return!1;if(/^-?\d*\.?\d+$/.test(a)){const e=Number(a);if(Number.isFinite(e))return e}return a}})(o);void 0===t?delete e.default:e.default=t;const r=(m=e).name.trim()?/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(m.name)?n.some(e=>e===m.name&&e!==a?.name)?`A field named "${m.name}" already exists in this array.`:"number"==typeof m.min&&"number"==typeof m.max&&m.min>m.max?`Min (${m.min}) cannot be greater than Max (${m.max}).`:null:"Name must be a valid identifier (letters, digits, underscore; cannot start with a digit).":"Field name is required.";var m;r?u(r):l(e)}})]})]})};
1
+ import{jsx as _jsx,Fragment as _Fragment,jsxs as _jsxs}from"react/jsx-runtime";import{useEffect,useState}from"react";import{Dialog}from"primereact/dialog";import{Button}from"primereact/button";import{InputText}from"primereact/inputtext";import{InputTextarea}from"primereact/inputtextarea";import{Dropdown}from"primereact/dropdown";import{Checkbox}from"primereact/checkbox";import{InputNumber}from"primereact/inputnumber";import{FormRow}from"../../forms/FormRow";const FIELD_TYPES=[{label:"string",value:"string"},{label:"i32",value:"i32"},{label:"i64",value:"i64"},{label:"u32",value:"u32"},{label:"u64",value:"u64"},{label:"f32",value:"f32"},{label:"f64",value:"f64"},{label:"bool",value:"bool"}],AGG_FNS=[{label:"— none (analysis-fed) —",value:""},{label:"avg",value:"avg"},{label:"min",value:"min"},{label:"max",value:"max"},{label:"stddev",value:"stddev"},{label:"count (# included tests)",value:"count"}],AGG_FROMS=[{label:"cycle field (every cycle, every test)",value:"cycle_fields"},{label:"result (one value per test)",value:"results"}],blank={name:"",type:"f32"};export const TestFieldDialog=({visible:e,initial:a,onCancel:t,onSave:l,siblingNames:r,isResultsField:n=!1,cycleFieldNames:i=[],resultsFieldNames:o=[]})=>{const[s,u]=useState(blank),[m,d]=useState(""),[c,g]=useState(null);useEffect(()=>{e&&(u(a?{...a}:{...blank}),d(null==a?.default?"":String(a.default)),g(null))},[e,a]);const p=s.aggregate,f=p?.from??"cycle_fields",v=("results"===f?o:i).filter(e=>e&&e!==s.name).map(e=>({label:e,value:e}));return _jsxs(Dialog,{header:a?`Edit field: ${a.name}`:"New field",visible:e,onHide:t,style:{width:"36rem"},children:[c&&_jsx("div",{style:{color:"#dc2626",marginBottom:"0.75rem"},children:c}),_jsx(FormRow,{label:"Name",required:!0,hint:"Wire-format key. Also the column name in CSV exports.",children:_jsx(InputText,{value:s.name,onChange:e=>u({...s,name:e.target.value})})}),_jsx(FormRow,{label:"Type",required:!0,children:_jsx(Dropdown,{value:s.type,options:FIELD_TYPES,onChange:e=>u({...s,type:e.value}),editable:!0})}),_jsx(FormRow,{label:"Units",hint:"Display label, appended to form labels (e.g. m/s).",children:_jsx(InputText,{value:s.units??"",onChange:e=>u({...s,units:e.target.value||void 0})})}),_jsx(FormRow,{label:"Label",hint:"Pretty form label. Falls back to name when empty.",children:_jsx(InputText,{value:s.label??"",onChange:e=>u({...s,label:e.target.value||void 0})})}),_jsx(FormRow,{label:"Required",children:_jsx(Checkbox,{checked:!!s.required,onChange:e=>u({...s,required:!!e.checked})})}),_jsx(FormRow,{label:"Source",hint:"Optional gm.* variable to bind this field to.",children:_jsx(InputText,{value:s.source??"",onChange:e=>u({...s,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:s.scale??null,onValueChange:e=>u({...s,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:s.min??null,onValueChange:e=>u({...s,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:s.max??null,onValueChange:e=>u({...s,max:"number"==typeof e.value?e.value:void 0}),mode:"decimal",minFractionDigits:0,maxFractionDigits:9})}),_jsx(FormRow,{label:"Default",hint:"Applied every time the method loads (operator can override). A literal (display units) like 5, or an FQDN token like ${gm.safe_speed} that snapshots that value on load.",children:_jsx(InputText,{value:m,onChange:e=>d(e.target.value),placeholder:"e.g. 5 or ${gm.safe_speed}"})}),n&&_jsxs(_Fragment,{children:[_jsx("div",{style:{borderTop:"1px solid #e5e7eb",margin:"0.75rem 0 0.25rem"}}),_jsx(FormRow,{label:"Aggregate",hint:"Cross-test rollup. When set, this result is derived server-side over the non-excluded tests sharing the same sample_id — the analysis script does not supply it.",children:_jsx(Dropdown,{value:p?.fn??"",options:AGG_FNS,onChange:e=>(e=>{if(""===e)return void u({...s,aggregate:void 0});if("count"===e)return void u({...s,aggregate:{fn:"count"}});const a={fn:e,of:s.aggregate?.of,from:s.aggregate?.from??"cycle_fields"};u({...s,aggregate:a})})(e.value)})}),p&&"count"!==p.fn&&_jsxs(_Fragment,{children:[_jsx(FormRow,{label:"Aggregate from",hint:"Which per-test source the target field is read from.",children:_jsx(Dropdown,{value:f,options:AGG_FROMS,onChange:e=>u({...s,aggregate:{...p,from:e.value}})})}),_jsx(FormRow,{label:"Aggregate of",required:!0,hint:"The field to reduce across tests.",children:_jsx(Dropdown,{value:p.of??null,options:v,onChange:e=>u({...s,aggregate:{...p,of:e.value||void 0}}),editable:!0,placeholder:v.length?"Select a field":`No ${f} defined yet`})})]})]}),_jsx(FormRow,{label:"Description",hint:"Hover tooltip in the form.",children:_jsx(InputTextarea,{rows:2,value:s.description??"",onChange:e=>u({...s,description:e.target.value||void 0})})}),_jsxs("div",{style:{display:"flex",justifyContent:"flex-end",gap:"0.5rem",marginTop:"1rem"},children:[_jsx(Button,{label:"Cancel",className:"p-button-text",onClick:t}),_jsx(Button,{label:"Save",onClick:()=>{const e={...s},t=(e=>{const a=e.trim();if(""!==a){if(a.startsWith("${"))return a;if("true"===a)return!0;if("false"===a)return!1;if(/^-?\d*\.?\d+$/.test(a)){const e=Number(a);if(Number.isFinite(e))return e}return a}})(m);void 0===t?delete e.default:e.default=t;const n=(i=e).name.trim()?/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(i.name)?r.some(e=>e===i.name&&e!==a?.name)?`A field named "${i.name}" already exists in this array.`:"number"==typeof i.min&&"number"==typeof i.max&&i.min>i.max?`Min (${i.min}) cannot be greater than Max (${i.max}).`:i.aggregate&&"count"!==i.aggregate.fn&&!i.aggregate.of?.trim()?`Aggregate "${i.aggregate.fn}" needs a target field (the field to ${i.aggregate.fn} across tests).`:null:"Name must be a valid identifier (letters, digits, underscore; cannot start with a digit).":"Field name is required.";var i;n?g(n):l(e)}})]})]})};
@@ -21,6 +21,21 @@ export interface TestField {
21
21
  * numeric input rejects values outside `[min, max]`. */
22
22
  min?: number;
23
23
  max?: number;
24
+ /** results_fields only: a cross-test rollup derived server-side over the
25
+ * non-excluded runs sharing this test's sample_id. When set, the field
26
+ * is NOT supplied by the analysis script — the server computes it. */
27
+ aggregate?: AggregateSpec;
28
+ }
29
+ export type AggFn = 'avg' | 'min' | 'max' | 'stddev' | 'count';
30
+ export type AggFrom = 'cycle_fields' | 'results';
31
+ export interface AggregateSpec {
32
+ /** Reduction applied across the gathered values. */
33
+ fn: AggFn;
34
+ /** Field to aggregate — a cycle_field or results_field name (per `from`).
35
+ * Ignored for `fn: "count"` (which counts included tests). */
36
+ of?: string;
37
+ /** Which per-test source `of` is read from. Defaults to "cycle_fields". */
38
+ from?: AggFrom;
24
39
  }
25
40
  export interface ChartAxis {
26
41
  field?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/tis-editor/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/tis-editor/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/components/tis-editor/validation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACG,UAAU,EACxB,MAAM,SAAS,CAAC;AAIjB,MAAM,WAAW,eAAe;IAC5B,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,GAAG,eAAe,EAAE,CAsGjF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAOtG"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/components/tis-editor/validation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACG,UAAU,EACxB,MAAM,SAAS,CAAC;AAIjB,MAAM,WAAW,eAAe;IAC5B,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,GAAG,eAAe,EAAE,CAyIjF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAOtG"}
@@ -1 +1 @@
1
- const FIELD_ARRAY_KEYS=["project_fields","config_fields","cycle_fields","results_fields"];export function validateMethod(e,a){const n=[];for(const t of FIELD_ARRAY_KEYS){const o=a[t]??[],s=new Set;o.forEach((a,o)=>{a.name&&""!==a.name.trim()?s.has(a.name)?n.push({path:`${t}.${o}.name`,message:`${e}.${t}: duplicate field name "${a.name}"`}):s.add(a.name):n.push({path:`${t}.${o}.name`,message:`${e}.${t}: empty field name`}),"number"==typeof a.min&&"number"==typeof a.max&&a.min>a.max&&n.push({path:`${t}.${o}.min`,message:`${e}.${t}.${a.name}: min (${a.min}) is greater than max (${a.max})`})})}const t=new Set;for(const e of FIELD_ARRAY_KEYS){(a[e]??[]).forEach(e=>e.name&&t.add(e.name))}const o=new Set(Object.keys(a.raw_data?.columns??{})),s=e=>e.field&&e.field.trim()?{key:e.field,ok:t.has(e.field)}:e.column&&e.column.trim()?{key:e.column,ok:o.has(e.column)}:null,i=a.views??{};for(const[a,t]of Object.entries(i)){if(t?.x){const o=s(t.x);o&&!o.ok&&n.push({path:`views.${a}.x`,message:`${e}.views.${a}: x.field/column "${o.key}" does not match any field or raw_data column`})}(t?.y??[]).forEach((t,o)=>{const i=s(t);i&&!i.ok&&n.push({path:`views.${a}.y.${o}`,message:`${e}.views.${a}.y[${o}]: field/column "${i.key}" does not match any field or raw_data column`})})}const m=a.raw_data;m&&(m.blob_name&&""!==String(m.blob_name).trim()||n.push({path:"raw_data.blob_name",message:`${e}.raw_data: blob_name is empty`}));const c=new Set((a.config_fields??[]).map(e=>e.name).filter(Boolean)),f=a.configurations??[],r=new Set;return f.forEach((a,t)=>{const o="string"==typeof a?.name?a.name.trim():"";o?r.has(o)?n.push({path:`configurations.${t}.name`,message:`${e}.configurations: duplicate configuration name "${o}"`}):r.add(o):n.push({path:`configurations.${t}.name`,message:`${e}.configurations[${t}]: empty configuration name`});const s=a?.defaults??{};for(const a of Object.keys(s))c.has(a)||n.push({path:`configurations.${t}.defaults.${a}`,message:`${e}.configurations.${o||t}: override "${a}" does not match any config_field`})}),n}export function validateMethods(e){const a={};for(const[n,t]of Object.entries(e)){const e=validateMethod(n,t);e.length>0&&(a[n]=e)}return a}
1
+ const FIELD_ARRAY_KEYS=["project_fields","config_fields","cycle_fields","results_fields"];export function validateMethod(e,a){const s=[];for(const t of FIELD_ARRAY_KEYS){const n=a[t]??[],o=new Set;n.forEach((a,n)=>{a.name&&""!==a.name.trim()?o.has(a.name)?s.push({path:`${t}.${n}.name`,message:`${e}.${t}: duplicate field name "${a.name}"`}):o.add(a.name):s.push({path:`${t}.${n}.name`,message:`${e}.${t}: empty field name`}),"number"==typeof a.min&&"number"==typeof a.max&&a.min>a.max&&s.push({path:`${t}.${n}.min`,message:`${e}.${t}.${a.name}: min (${a.min}) is greater than max (${a.max})`})})}const t=new Set(["i32","i64","u32","u64","f32","f64"]),n=new Map((a.cycle_fields??[]).map(e=>[e.name,String(e.type)])),o=new Map((a.results_fields??[]).map(e=>[e.name,String(e.type)]));for(const t of["project_fields","config_fields","cycle_fields"])(a[t]??[]).forEach((a,n)=>{a.aggregate&&s.push({path:`${t}.${n}.aggregate`,message:`${e}.${t}.${a.name}: aggregate is only valid on results_fields`})});(a.results_fields??[]).forEach((a,i)=>{const r=a.aggregate;if(!r||"count"===r.fn)return;const f=r.of?.trim();if(!f)return void s.push({path:`results_fields.${i}.aggregate`,message:`${e}.results_fields.${a.name}: aggregate "${r.fn}" requires a target field (of)`});const m="results"===(r.from??"cycle_fields")?o:n,c="results"===(r.from??"cycle_fields")?"results_fields":"cycle_fields",l=m.get(f);void 0===l?s.push({path:`results_fields.${i}.aggregate`,message:`${e}.results_fields.${a.name}: aggregate "of" references "${f}" which is not a ${c} of this method`}):t.has(l)||s.push({path:`results_fields.${i}.aggregate`,message:`${e}.results_fields.${a.name}: aggregate target "${f}" has non-numeric type "${l}"`})});const i=new Set;for(const e of FIELD_ARRAY_KEYS){(a[e]??[]).forEach(e=>e.name&&i.add(e.name))}const r=new Set(Object.keys(a.raw_data?.columns??{})),f=e=>e.field&&e.field.trim()?{key:e.field,ok:i.has(e.field)}:e.column&&e.column.trim()?{key:e.column,ok:r.has(e.column)}:null,m=a.views??{};for(const[a,t]of Object.entries(m)){if(t?.x){const n=f(t.x);n&&!n.ok&&s.push({path:`views.${a}.x`,message:`${e}.views.${a}: x.field/column "${n.key}" does not match any field or raw_data column`})}(t?.y??[]).forEach((t,n)=>{const o=f(t);o&&!o.ok&&s.push({path:`views.${a}.y.${n}`,message:`${e}.views.${a}.y[${n}]: field/column "${o.key}" does not match any field or raw_data column`})})}const c=a.raw_data;c&&(c.blob_name&&""!==String(c.blob_name).trim()||s.push({path:"raw_data.blob_name",message:`${e}.raw_data: blob_name is empty`}));const l=new Set((a.config_fields??[]).map(e=>e.name).filter(Boolean)),g=a.configurations??[],d=new Set;return g.forEach((a,t)=>{const n="string"==typeof a?.name?a.name.trim():"";n?d.has(n)?s.push({path:`configurations.${t}.name`,message:`${e}.configurations: duplicate configuration name "${n}"`}):d.add(n):s.push({path:`configurations.${t}.name`,message:`${e}.configurations[${t}]: empty configuration name`});const o=a?.defaults??{};for(const a of Object.keys(o))l.has(a)||s.push({path:`configurations.${t}.defaults.${a}`,message:`${e}.configurations.${n||t}: override "${a}" does not match any config_field`})}),s}export function validateMethods(e){const a={};for(const[s,t]of Object.entries(e)){const e=validateMethod(s,t);e.length>0&&(a[s]=e)}return a}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adcops/autocore-react",
3
- "version": "3.3.106",
3
+ "version": "3.3.109",
4
4
  "description": "A React component library for industrial user interfaces.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -45,6 +45,9 @@ export type { TestMethodDialogProps } from './tis/TestMethodDialog';
45
45
  export { ResultHistoryTable } from './tis/ResultHistoryTable';
46
46
  export type { ResultHistoryTableProps } from './tis/ResultHistoryTable';
47
47
 
48
+ export { SampleSummaryPanel } from './tis/SampleSummaryPanel';
49
+ export type { SampleSummaryPanelProps } from './tis/SampleSummaryPanel';
50
+
48
51
  export { TestDataView } from './tis/TestDataView';
49
52
  export type { TestDataViewProps, ChartAxis, ChartSeries, ChartView, RawDataShape } from './tis/TestDataView';
50
53
 
@@ -0,0 +1,12 @@
1
+ /* Runs excluded from cross-test aggregation are kept visible but dimmed and
2
+ struck through, so an operator can see at a glance which rows don't feed
3
+ the avg/min/max/stddev/count results for their sample group. */
4
+ .tis-row-excluded > td {
5
+ opacity: 0.55;
6
+ text-decoration: line-through;
7
+ }
8
+ /* Keep the action buttons (Include / Download) legible and clickable. */
9
+ .tis-row-excluded > td .p-button {
10
+ opacity: 1;
11
+ text-decoration: none;
12
+ }
@@ -6,6 +6,8 @@ import { Tag } from 'primereact/tag';
6
6
  import { EventEmitterContext } from '../../core/EventEmitterContext';
7
7
  import { MessageType } from '../../hub/CommandMessage';
8
8
  import { useTis } from './TisProvider';
9
+ import { SampleSummaryPanel } from './SampleSummaryPanel';
10
+ import './ResultHistoryTable.css';
9
11
 
10
12
  export interface ResultHistoryTableProps {
11
13
  /**
@@ -72,6 +74,7 @@ export const ResultHistoryTable: React.FC<ResultHistoryTableProps> = (props) =>
72
74
  const [loading, setLoading] = useState(false);
73
75
  const [downloading, setDownloading] = useState<InFlight | null>(null);
74
76
  const [projectBusy, setProjectBusy] = useState<ProjectDownloadKind | null>(null);
77
+ const [excludingRunId, setExcludingRunId] = useState<string | null>(null);
75
78
  const { invoke } = useContext(EventEmitterContext);
76
79
 
77
80
  const loadTests = async () => {
@@ -169,6 +172,34 @@ export const ResultHistoryTable: React.FC<ResultHistoryTableProps> = (props) =>
169
172
  }
170
173
  };
171
174
 
175
+ // Toggle a run's inclusion in cross-test aggregation. Excluded runs are
176
+ // kept on disk (not deleted) but dropped from avg/min/max/stddev/count
177
+ // results for their sample group. The server recomputes + rebroadcasts
178
+ // the sample summary on success.
179
+ const handleToggleExclude = async (rowData: any) => {
180
+ const runId = rowData?.run_id;
181
+ const rowMethodId = rowData?.method_id ?? methodId;
182
+ if (!runId || !rowMethodId || !projectId) return;
183
+ const nextExcluded = rowData?.excluded !== true;
184
+ setExcludingRunId(runId);
185
+ try {
186
+ const resp: any = await invoke('tis.set_test_excluded' as any, MessageType.Request, {
187
+ project_id: projectId, method_id: rowMethodId, run_id: runId, excluded: nextExcluded,
188
+ } as any);
189
+ if (!resp?.success) {
190
+ alert(`Failed to ${nextExcluded ? 'exclude' : 'include'} ${runId}` +
191
+ (resp?.error_message ? `: ${resp.error_message}` : ''));
192
+ return;
193
+ }
194
+ await loadTests();
195
+ } catch (err) {
196
+ console.error('Failed to toggle exclusion', err);
197
+ alert(`Exclude toggle failed: ${err instanceof Error ? err.message : String(err)}`);
198
+ } finally {
199
+ setExcludingRunId(null);
200
+ }
201
+ };
202
+
172
203
  const handleProjectDownload = async (kind: ProjectDownloadKind) => {
173
204
  if (!projectId) return;
174
205
  const topic = kind === 'report' ? 'tis.export_project_csv'
@@ -284,6 +315,11 @@ export const ResultHistoryTable: React.FC<ResultHistoryTableProps> = (props) =>
284
315
  </div>
285
316
  </div>
286
317
 
318
+ {/* Per-sample cross-test rollup. Renders nothing unless a method
319
+ declares aggregate results_fields, so projects without the
320
+ feature see the history table unchanged. */}
321
+ <SampleSummaryPanel projectId={projectId} methodId={methodId} />
322
+
287
323
  <DataTable
288
324
  value={tests}
289
325
  loading={loading}
@@ -294,6 +330,7 @@ export const ResultHistoryTable: React.FC<ResultHistoryTableProps> = (props) =>
294
330
  scrollHeight="flex"
295
331
  tableStyle={{ minWidth: 0 }}
296
332
  style={{ width: '100%' }}
333
+ rowClassName={(rowData: any) => (rowData?.excluded === true ? 'tis-row-excluded' : '')}
297
334
  selectionMode="single"
298
335
  onSelectionChange={(e: any) => {
299
336
  const row = e.value;
@@ -335,6 +372,30 @@ export const ResultHistoryTable: React.FC<ResultHistoryTableProps> = (props) =>
335
372
  }}
336
373
  style={{ minWidth: '8rem' }}
337
374
  />
375
+ <Column
376
+ header="Include"
377
+ style={{ width: '8rem' }}
378
+ body={(rowData) => {
379
+ const isExcluded = rowData?.excluded === true;
380
+ const isBusy = excludingRunId === rowData.run_id;
381
+ return (
382
+ <Button
383
+ icon={isBusy ? 'pi pi-spin pi-spinner'
384
+ : (isExcluded ? 'pi pi-eye-slash' : 'pi pi-check')}
385
+ label={isExcluded ? 'Excluded' : 'Included'}
386
+ size="small"
387
+ outlined
388
+ severity={isExcluded ? 'warning' : 'success'}
389
+ disabled={excludingRunId !== null}
390
+ onClick={() => handleToggleExclude(rowData)}
391
+ tooltip={isExcluded
392
+ ? 'Excluded from cross-test results — click to include'
393
+ : 'Included in cross-test results — click to exclude'}
394
+ tooltipOptions={{ position: 'left' }}
395
+ />
396
+ );
397
+ }}
398
+ />
338
399
  <Column
339
400
  header="Download"
340
401
  style={{ width: '14rem' }}
@@ -0,0 +1,171 @@
1
+ import React, { useState, useEffect, useContext } from 'react';
2
+ import { DataTable } from 'primereact/datatable';
3
+ import { Column } from 'primereact/column';
4
+ import { Dropdown } from 'primereact/dropdown';
5
+ import { EventEmitterContext } from '../../core/EventEmitterContext';
6
+ import { MessageType } from '../../hub/CommandMessage';
7
+ import { useTis } from './TisProvider';
8
+
9
+ /**
10
+ * Per-method cross-test rollup, one row per sample_id.
11
+ *
12
+ * Distinct from the per-run results overlay in <TestDataView>: this panel
13
+ * answers "across every run of sample X, what's the avg/min/max/stddev/count?"
14
+ * — the question operators ask when a sample is run repeatedly. Backed by
15
+ * `tis.sample_summaries`; refreshes on the server's `tis.sample_summary_updated`
16
+ * broadcast (a sibling finishing or an exclusion toggle).
17
+ *
18
+ * Renders nothing unless some method in the project declares aggregate
19
+ * results_fields. When `methodId` is omitted and more than one method has
20
+ * aggregates, a small method picker is shown.
21
+ */
22
+ export interface SampleSummaryPanelProps {
23
+ /** Defaults to the active project from <TisProvider>. */
24
+ projectId?: string;
25
+ /** Lock the panel to one method. When omitted, the user picks among
26
+ * methods that declare aggregate results_fields. */
27
+ methodId?: string;
28
+ }
29
+
30
+ interface SummaryRow {
31
+ sample_id: string;
32
+ summary: Record<string, unknown>;
33
+ included_count: number;
34
+ excluded_count: number;
35
+ }
36
+
37
+ interface AggColumn {
38
+ name: string;
39
+ header: string;
40
+ scale?: number;
41
+ }
42
+
43
+ export const SampleSummaryPanel: React.FC<SampleSummaryPanelProps> = (props) => {
44
+ const tis = useTis();
45
+ const { invoke, subscribe, unsubscribe } = useContext(EventEmitterContext);
46
+ const projectId = props.projectId ?? tis.selection.projectId ?? undefined;
47
+
48
+ // Methods that actually declare an aggregate results_field — the only
49
+ // ones worth showing here.
50
+ const methodsWithAggregates = React.useMemo(() => {
51
+ return Object.entries(tis.schemas)
52
+ .filter(([, m]) => ((m?.results_fields ?? []) as any[]).some((f) => f?.aggregate))
53
+ .map(([id]) => id);
54
+ }, [tis.schemas]);
55
+
56
+ const [picked, setPicked] = useState<string | undefined>(undefined);
57
+ const methodId = props.methodId ?? picked ?? methodsWithAggregates[0];
58
+
59
+ // Keep the local pick valid as schemas load / change.
60
+ useEffect(() => {
61
+ if (props.methodId) return;
62
+ if (!picked || !methodsWithAggregates.includes(picked)) {
63
+ setPicked(methodsWithAggregates[0]);
64
+ }
65
+ }, [props.methodId, picked, methodsWithAggregates]);
66
+
67
+ const [rows, setRows] = useState<SummaryRow[]>([]);
68
+ const [loading, setLoading] = useState(false);
69
+
70
+ // Columns come from the schema's aggregate results_fields (label + units),
71
+ // so empty-but-declared columns still show with a blank cell.
72
+ const aggColumns: AggColumn[] = React.useMemo(() => {
73
+ const schema = methodId ? (tis.schemas[methodId] as any) : undefined;
74
+ const fields: any[] = (schema?.results_fields ?? []).filter((f: any) => f?.aggregate);
75
+ return fields.map((f) => ({
76
+ name: f.name,
77
+ header: (f.label || f.name) + (f.units ? ` [${f.units}]` : ''),
78
+ scale: typeof f.scale === 'number' ? f.scale : undefined,
79
+ }));
80
+ }, [tis.schemas, methodId]);
81
+
82
+ useEffect(() => {
83
+ if (!projectId || !methodId) { setRows([]); return; }
84
+ let cancelled = false;
85
+ const load = async () => {
86
+ setLoading(true);
87
+ try {
88
+ const resp: any = await invoke('tis.sample_summaries' as any, MessageType.Request,
89
+ { project_id: projectId, method_id: methodId } as any);
90
+ if (!cancelled && resp?.success) setRows(resp.data?.summaries ?? []);
91
+ } catch (err) {
92
+ console.error('Failed to load sample summaries', err);
93
+ } finally {
94
+ if (!cancelled) setLoading(false);
95
+ }
96
+ };
97
+ load();
98
+ // Any rollup change for this project+method refreshes the whole panel
99
+ // (cheap; also picks up brand-new sample groups the broadcast can't
100
+ // patch in place). Also follow the live run lifecycle like the table.
101
+ const onSummary = (payload: any) => {
102
+ if (payload?.project_id === projectId && payload?.method_id === methodId) load();
103
+ };
104
+ const id = subscribe('tis.sample_summary_updated', onSummary);
105
+ return () => { cancelled = true; unsubscribe(id); };
106
+ // eslint-disable-next-line react-hooks/exhaustive-deps
107
+ }, [projectId, methodId, tis.state.activeRunId, tis.state.active]);
108
+
109
+ // Feature not configured for this project → render nothing.
110
+ if (methodsWithAggregates.length === 0) return null;
111
+
112
+ const fmt = (v: unknown, scale?: number): string => {
113
+ if (v === null || v === undefined) return '—';
114
+ if (typeof v === 'number') {
115
+ if (!Number.isFinite(v)) return String(v);
116
+ const scaled = typeof scale === 'number' && scale !== 0 ? v * scale : v;
117
+ return Number.parseFloat(scaled.toPrecision(6)).toString();
118
+ }
119
+ return String(v);
120
+ };
121
+
122
+ return (
123
+ <div style={{ width: '100%', maxWidth: '100%', overflow: 'hidden', boxSizing: 'border-box', marginBottom: '1rem' }}>
124
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.5rem', gap: '0.5rem', flexWrap: 'wrap' }}>
125
+ <h3 style={{ margin: 0 }}>Sample Summary</h3>
126
+ {!props.methodId && methodsWithAggregates.length > 1 && (
127
+ <Dropdown
128
+ value={methodId}
129
+ options={methodsWithAggregates.map((id) => ({ label: id, value: id }))}
130
+ onChange={(e) => setPicked(e.value)}
131
+ style={{ minWidth: '12rem' }}
132
+ />
133
+ )}
134
+ </div>
135
+ <DataTable
136
+ value={rows}
137
+ loading={loading}
138
+ dataKey="sample_id"
139
+ emptyMessage="No samples yet."
140
+ scrollable
141
+ tableStyle={{ minWidth: 0 }}
142
+ style={{ width: '100%' }}
143
+ size="small"
144
+ stripedRows
145
+ >
146
+ <Column field="sample_id" header="Sample ID" sortable style={{ minWidth: '8rem' }} />
147
+ {aggColumns.map((c) => (
148
+ <Column
149
+ key={c.name}
150
+ header={c.header}
151
+ bodyStyle={{ fontVariantNumeric: 'tabular-nums', textAlign: 'right' }}
152
+ style={{ minWidth: '7rem' }}
153
+ body={(row: SummaryRow) => fmt(row.summary?.[c.name], c.scale)}
154
+ />
155
+ ))}
156
+ <Column
157
+ header="# Tests"
158
+ bodyStyle={{ textAlign: 'right' }}
159
+ style={{ width: '6rem' }}
160
+ body={(row: SummaryRow) => row.included_count}
161
+ />
162
+ <Column
163
+ header="Excluded"
164
+ bodyStyle={{ textAlign: 'right' }}
165
+ style={{ width: '6rem' }}
166
+ body={(row: SummaryRow) => (row.excluded_count > 0 ? row.excluded_count : '—')}
167
+ />
168
+ </DataTable>
169
+ </div>
170
+ );
171
+ };
@@ -199,6 +199,10 @@ export const TestDataView: React.FC<TestDataViewProps> = (props) => {
199
199
  const [meta, setMeta] = useState<any>(null);
200
200
  const [cycles, setCycles] = useState<any[]>([]);
201
201
  const [results, setResults] = useState<any>({});
202
+ // Server-derived cross-test aggregate results_fields (avg/min/max/stddev/
203
+ // count over the sample group). Overlaid on `results` for display — these
204
+ // fields are never in the per-test results blob.
205
+ const [sampleSummary, setSampleSummary] = useState<any>({});
202
206
  const [rawOpen, setRawOpen] = useState(false);
203
207
  const [configOpen, setConfigOpen] = useState(false);
204
208
 
@@ -351,6 +355,45 @@ export const TestDataView: React.FC<TestDataViewProps> = (props) => {
351
355
  // eslint-disable-next-line react-hooks/exhaustive-deps
352
356
  }, [projectId, methodId, runId, throttleMs]);
353
357
 
358
+ // -----------------------------------------------------------------
359
+ // Cross-test aggregate summary (sample-group rollup)
360
+ //
361
+ // Keyed by sample_id, not run_id: every run of the same sample shares
362
+ // the rollup. Fetched once per sample and refreshed on the server's
363
+ // sample_summary_updated broadcast (fired when a sibling finishes or its
364
+ // exclusion is toggled). Empty unless the method declares aggregate
365
+ // results_fields.
366
+ // -----------------------------------------------------------------
367
+ const sampleId: string =
368
+ (typeof meta?.sample_id === 'string' && meta.sample_id)
369
+ ? meta.sample_id
370
+ : (typeof meta?.config?.sample_id === 'string' ? meta.config.sample_id : '');
371
+
372
+ useEffect(() => {
373
+ if (!projectId || !methodId || !sampleId) { setSampleSummary({}); return; }
374
+ let cancelled = false;
375
+ const fetchSummary = async () => {
376
+ try {
377
+ const resp: any = await invoke(
378
+ 'tis.sample_summary' as any, MessageType.Request as any,
379
+ { project_id: projectId, method_id: methodId, sample_id: sampleId } as any);
380
+ if (!cancelled && resp?.success) setSampleSummary(resp.data?.summary ?? {});
381
+ } catch (e) {
382
+ console.error('[TestDataView] sample_summary fetch failed', e);
383
+ }
384
+ };
385
+ fetchSummary();
386
+ const onSummary = (payload: any) => {
387
+ if (payload?.project_id === projectId
388
+ && payload?.method_id === methodId
389
+ && payload?.sample_id === sampleId) {
390
+ setSampleSummary(payload.summary ?? {});
391
+ }
392
+ };
393
+ const id = subscribe('tis.sample_summary_updated', onSummary);
394
+ return () => { cancelled = true; unsubscribe(id); };
395
+ }, [projectId, methodId, sampleId, invoke, subscribe, unsubscribe]);
396
+
354
397
  // -----------------------------------------------------------------
355
398
  // Raw-trace data fetch (lazy)
356
399
  //
@@ -721,7 +764,10 @@ export const TestDataView: React.FC<TestDataViewProps> = (props) => {
721
764
 
722
765
  <div className="p-card" style={{ padding: '1rem' }}>
723
766
  <h3 style={{ marginTop: 0 }}>Results</h3>
724
- <ResultsGrid schema={schema.results_fields} values={results} />
767
+ {/* Overlay the server-derived cross-test rollup (avg/min/max/
768
+ stddev/count) onto this run's own results. Aggregate fields
769
+ are never in `results` — the summary is their only source. */}
770
+ <ResultsGrid schema={schema.results_fields} values={{ ...results, ...sampleSummary }} />
725
771
  </div>
726
772
 
727
773
  {/*
@@ -462,6 +462,18 @@ export const TestSetupForm: React.FC<TestSetupFormProps> = ({
462
462
  for (const field of schema.config_fields) {
463
463
  if (field.name === 'sample_id') continue;
464
464
  if (field.default === undefined || field.default === null) continue;
465
+ // A source-bound field's default must only SEED when GM has no
466
+ // value yet — never clobber an existing one. The defaults effect
467
+ // re-runs after every completed run (clearStagedConfig resets the
468
+ // marker), so without this guard the operator's setting (e.g.
469
+ // cof_window_pct) is reset to the literal default on each load.
470
+ // When GM already holds a value, keep it; the source-seed effect
471
+ // below mirrors the live value into stagedConfig.
472
+ if (field.source) {
473
+ const liveTag = findTagByFqdn(field.source);
474
+ const liveVal = liveTag ? rawValues[liveTag.tagName] : undefined;
475
+ if (liveVal !== undefined && liveVal !== null) continue;
476
+ }
465
477
  // Resolve the default to a RAW value. Literals are authored in
466
478
  // DISPLAY units (converted via `scale`); an `${fqdn}` token
467
479
  // snapshots that tag's current (already-raw) value so a
@@ -17,7 +17,14 @@ const DESCRIPTIONS: Record<FieldArrayKey, string> = {
17
17
  project_fields: 'System-level fields (operator, station). Filled by the HMI but not cycled.',
18
18
  config_fields: 'Operator-input config (speeds, loads). Snapshotted into test.json on start.',
19
19
  cycle_fields: 'Per-cycle capture. One row appended to cycles.jsonl per cycle.',
20
- results_fields: 'Post-test summary (min/max/avg, pass/fail). Written once at finish.',
20
+ results_fields: 'Post-test summary (min/max/avg, pass/fail). Written once at finish. A field can also be a cross-test Aggregate (avg/min/max/stddev/count over tests sharing a sample_id).',
21
+ };
22
+
23
+ /** Short human label for a field's aggregate spec, e.g. "avg(cof)" / "count". */
24
+ const aggregateLabel = (f: TestField): string => {
25
+ const a = f.aggregate;
26
+ if (!a) return '';
27
+ return a.fn === 'count' ? 'count' : `${a.fn}(${a.of ?? '?'})`;
21
28
  };
22
29
 
23
30
  export interface FieldArrayEditorProps {
@@ -116,6 +123,13 @@ export const FieldArrayEditor: React.FC<FieldArrayEditorProps> = ({ arrayKey, me
116
123
  style={{ width: '4rem' }}
117
124
  />
118
125
  <Column field="label" header="Label" />
126
+ {arrayKey === 'results_fields' && (
127
+ <Column
128
+ header="Aggregate"
129
+ body={(r: TestField) => aggregateLabel(r)}
130
+ style={{ width: '8rem' }}
131
+ />
132
+ )}
119
133
  <Column header="" body={rowActions} style={{ width: '10rem' }} />
120
134
  </DataTable>
121
135
  </FormSection>
@@ -123,6 +137,9 @@ export const FieldArrayEditor: React.FC<FieldArrayEditorProps> = ({ arrayKey, me
123
137
  visible={dialogOpen}
124
138
  initial={editingIdx !== null ? (fields[editingIdx] ?? null) : null}
125
139
  siblingNames={fields.map(f => f.name)}
140
+ isResultsField={arrayKey === 'results_fields'}
141
+ cycleFieldNames={(method.cycle_fields ?? []).map(f => f.name).filter(Boolean)}
142
+ resultsFieldNames={(method.results_fields ?? []).map(f => f.name).filter(Boolean)}
126
143
  onCancel={() => setDialogOpen(false)}
127
144
  onSave={handleSaveField}
128
145
  />
@@ -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 } from '../types';
10
+ import type { TestField, AggFn, AggFrom, AggregateSpec } from '../types';
11
11
 
12
12
  const FIELD_TYPES: { label: string; value: string }[] = [
13
13
  { label: 'string', value: 'string' },
@@ -20,6 +20,21 @@ const FIELD_TYPES: { label: string; value: string }[] = [
20
20
  { label: 'bool', value: 'bool' },
21
21
  ];
22
22
 
23
+ // '' = no aggregation (the field is supplied by the analysis script).
24
+ const AGG_FNS: { label: string; value: '' | AggFn }[] = [
25
+ { label: '— none (analysis-fed) —', value: '' },
26
+ { label: 'avg', value: 'avg' },
27
+ { label: 'min', value: 'min' },
28
+ { label: 'max', value: 'max' },
29
+ { label: 'stddev', value: 'stddev' },
30
+ { label: 'count (# included tests)', value: 'count' },
31
+ ];
32
+
33
+ const AGG_FROMS: { label: string; value: AggFrom }[] = [
34
+ { label: 'cycle field (every cycle, every test)', value: 'cycle_fields' },
35
+ { label: 'result (one value per test)', value: 'results' },
36
+ ];
37
+
23
38
  export interface TestFieldDialogProps {
24
39
  visible: boolean;
25
40
  initial: TestField | null;
@@ -27,12 +42,19 @@ export interface TestFieldDialogProps {
27
42
  onSave: (field: TestField) => void;
28
43
  /** Names already in use within the same array — for uniqueness validation. */
29
44
  siblingNames: string[];
45
+ /** True when this field belongs to `results_fields` — only then is the
46
+ * cross-test Aggregate section shown. */
47
+ isResultsField?: boolean;
48
+ /** Candidate target field names for an aggregate's `of`, by source. */
49
+ cycleFieldNames?: string[];
50
+ resultsFieldNames?: string[];
30
51
  }
31
52
 
32
53
  const blank: TestField = { name: '', type: 'f32' };
33
54
 
34
55
  export const TestFieldDialog: React.FC<TestFieldDialogProps> = ({
35
56
  visible, initial, onCancel, onSave, siblingNames,
57
+ isResultsField = false, cycleFieldNames = [], resultsFieldNames = [],
36
58
  }) => {
37
59
  const [draft, setDraft] = useState<TestField>(blank);
38
60
  // `default` is edited as free text so a numeric literal, a string, or an
@@ -73,9 +95,29 @@ export const TestFieldDialog: React.FC<TestFieldDialogProps> = ({
73
95
  if (typeof f.min === 'number' && typeof f.max === 'number' && f.min > f.max) {
74
96
  return `Min (${f.min}) cannot be greater than Max (${f.max}).`;
75
97
  }
98
+ if (f.aggregate && f.aggregate.fn !== 'count' && !f.aggregate.of?.trim()) {
99
+ return `Aggregate "${f.aggregate.fn}" needs a target field (the field to ${f.aggregate.fn} across tests).`;
100
+ }
76
101
  return null;
77
102
  };
78
103
 
104
+ // Build the aggregate spec from the chosen function. '' clears it; count
105
+ // drops of/from (it reduces over the set of included tests, not a field).
106
+ const setAggFn = (fn: '' | AggFn) => {
107
+ if (fn === '') { setDraft({ ...draft, aggregate: undefined }); return; }
108
+ if (fn === 'count') { setDraft({ ...draft, aggregate: { fn: 'count' } }); return; }
109
+ const next: AggregateSpec = { fn, of: draft.aggregate?.of, from: draft.aggregate?.from ?? 'cycle_fields' };
110
+ setDraft({ ...draft, aggregate: next });
111
+ };
112
+
113
+ const agg = draft.aggregate;
114
+ const aggFrom: AggFrom = agg?.from ?? 'cycle_fields';
115
+ // `of` candidates depend on the source; exclude this field's own name so
116
+ // a result can't aggregate itself.
117
+ const aggTargets = (aggFrom === 'results' ? resultsFieldNames : cycleFieldNames)
118
+ .filter(n => n && n !== draft.name)
119
+ .map(n => ({ label: n, value: n }));
120
+
79
121
  const handleSave = () => {
80
122
  const candidate: TestField = { ...draft };
81
123
  const dv = coerceDefault(defaultText);
@@ -173,6 +215,45 @@ export const TestFieldDialog: React.FC<TestFieldDialogProps> = ({
173
215
  placeholder="e.g. 5 or ${gm.safe_speed}"
174
216
  />
175
217
  </FormRow>
218
+ {isResultsField && (
219
+ <>
220
+ <div style={{ borderTop: '1px solid #e5e7eb', margin: '0.75rem 0 0.25rem' }} />
221
+ <FormRow
222
+ label="Aggregate"
223
+ 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."
224
+ >
225
+ <Dropdown
226
+ value={agg?.fn ?? ''}
227
+ options={AGG_FNS}
228
+ onChange={(e) => setAggFn(e.value)}
229
+ />
230
+ </FormRow>
231
+ {agg && agg.fn !== 'count' && (
232
+ <>
233
+ <FormRow label="Aggregate from" hint="Which per-test source the target field is read from.">
234
+ <Dropdown
235
+ value={aggFrom}
236
+ options={AGG_FROMS}
237
+ onChange={(e) =>
238
+ setDraft({ ...draft, aggregate: { ...agg, from: e.value } })
239
+ }
240
+ />
241
+ </FormRow>
242
+ <FormRow label="Aggregate of" required hint="The field to reduce across tests.">
243
+ <Dropdown
244
+ value={agg.of ?? null}
245
+ options={aggTargets}
246
+ onChange={(e) =>
247
+ setDraft({ ...draft, aggregate: { ...agg, of: e.value || undefined } })
248
+ }
249
+ editable
250
+ placeholder={aggTargets.length ? 'Select a field' : `No ${aggFrom} defined yet`}
251
+ />
252
+ </FormRow>
253
+ </>
254
+ )}
255
+ </>
256
+ )}
176
257
  <FormRow label="Description" hint="Hover tooltip in the form.">
177
258
  <InputTextarea
178
259
  rows={2}
@@ -27,6 +27,23 @@ export interface TestField {
27
27
  * numeric input rejects values outside `[min, max]`. */
28
28
  min?: number;
29
29
  max?: number;
30
+ /** results_fields only: a cross-test rollup derived server-side over the
31
+ * non-excluded runs sharing this test's sample_id. When set, the field
32
+ * is NOT supplied by the analysis script — the server computes it. */
33
+ aggregate?: AggregateSpec;
34
+ }
35
+
36
+ export type AggFn = 'avg' | 'min' | 'max' | 'stddev' | 'count';
37
+ export type AggFrom = 'cycle_fields' | 'results';
38
+
39
+ export interface AggregateSpec {
40
+ /** Reduction applied across the gathered values. */
41
+ fn: AggFn;
42
+ /** Field to aggregate — a cycle_field or results_field name (per `from`).
43
+ * Ignored for `fn: "count"` (which counts included tests). */
44
+ of?: string;
45
+ /** Which per-test source `of` is read from. Defaults to "cycle_fields". */
46
+ from?: AggFrom;
30
47
  }
31
48
 
32
49
  export interface ChartAxis {
@@ -39,6 +39,41 @@ export function validateMethod(methodId: string, m: TestMethod): ValidationError
39
39
  });
40
40
  }
41
41
 
42
+ // Aggregate specs: only on results_fields; target must exist in the
43
+ // chosen source array and be numeric. `count` needs no target. Mirrors
44
+ // tis_servelet::validate_method.
45
+ const NUMERIC_TYPES = new Set(['i32', 'i64', 'u32', 'u64', 'f32', 'f64']);
46
+ const cycleTypes = new Map<string, string>(
47
+ ((m.cycle_fields as TestField[] | undefined) ?? []).map((f) => [f.name, String(f.type)])
48
+ );
49
+ const resultTypes = new Map<string, string>(
50
+ ((m.results_fields as TestField[] | undefined) ?? []).map((f) => [f.name, String(f.type)])
51
+ );
52
+ for (const key of ['project_fields', 'config_fields', 'cycle_fields'] as const) {
53
+ ((m[key] as TestField[] | undefined) ?? []).forEach((f, i) => {
54
+ if (f.aggregate) {
55
+ errs.push({ path: `${key}.${i}.aggregate`, message: `${methodId}.${key}.${f.name}: aggregate is only valid on results_fields` });
56
+ }
57
+ });
58
+ }
59
+ ((m.results_fields as TestField[] | undefined) ?? []).forEach((f, i) => {
60
+ const a = f.aggregate;
61
+ if (!a || a.fn === 'count') return;
62
+ const of = a.of?.trim();
63
+ if (!of) {
64
+ errs.push({ path: `results_fields.${i}.aggregate`, message: `${methodId}.results_fields.${f.name}: aggregate "${a.fn}" requires a target field (of)` });
65
+ return;
66
+ }
67
+ const lookup = (a.from ?? 'cycle_fields') === 'results' ? resultTypes : cycleTypes;
68
+ const srcLabel = (a.from ?? 'cycle_fields') === 'results' ? 'results_fields' : 'cycle_fields';
69
+ const ty = lookup.get(of);
70
+ if (ty === undefined) {
71
+ errs.push({ path: `results_fields.${i}.aggregate`, message: `${methodId}.results_fields.${f.name}: aggregate "of" references "${of}" which is not a ${srcLabel} of this method` });
72
+ } else if (!NUMERIC_TYPES.has(ty)) {
73
+ errs.push({ path: `results_fields.${i}.aggregate`, message: `${methodId}.results_fields.${f.name}: aggregate target "${of}" has non-numeric type "${ty}"` });
74
+ }
75
+ });
76
+
42
77
  // Build sets for axis-reference resolution.
43
78
  const knownFields = new Set<string>();
44
79
  for (const key of FIELD_ARRAY_KEYS) {