@adcops/autocore-react 3.3.116 → 3.3.118
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/tis-editor/editor/MethodFormEditor.d.ts.map +1 -1
- package/dist/components/tis-editor/editor/MethodFormEditor.js +1 -1
- package/dist/components/tis-editor/editor/ViewsEditor.d.ts.map +1 -1
- package/dist/components/tis-editor/editor/ViewsEditor.js +1 -1
- package/dist/components/tis-editor/validation.d.ts.map +1 -1
- package/dist/components/tis-editor/validation.js +1 -1
- package/package.json +1 -1
- package/src/components/tis-editor/editor/MethodFormEditor.tsx +28 -2
- package/src/components/tis-editor/editor/ViewsEditor.tsx +4 -0
- package/src/components/tis-editor/validation.ts +14 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MethodFormEditor.d.ts","sourceRoot":"","sources":["../../../../src/components/tis-editor/editor/MethodFormEditor.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,WAAW,qBAAqB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,CAAC;IACnB,6EAA6E;IAC7E,OAAO,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACpD,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,
|
|
1
|
+
{"version":3,"file":"MethodFormEditor.d.ts","sourceRoot":"","sources":["../../../../src/components/tis-editor/editor/MethodFormEditor.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,WAAW,qBAAqB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,CAAC;IACnB,6EAA6E;IAC7E,OAAO,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACpD,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAiL5D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import{useEffect,useMemo,useState}from"react";import{TabView,TabPanel}from"primereact/tabview";import{Button}from"primereact/button";import{CodeEditor}from"../../CodeEditor";import{IdentitySection}from"./IdentitySection";import{FieldArrayEditor}from"./FieldArrayEditor";import{CycleConfigSection}from"./CycleConfigSection";import{ConfigurationsEditor}from"./ConfigurationsEditor";import{ViewsEditor}from"./ViewsEditor";import{RawDataEditor}from"./RawDataEditor";import{AssetRefsEditor}from"./AssetRefsEditor";import{AnalysisEditor}from"./AnalysisEditor";import{validateMethod}from"../validation";export const MethodFormEditor=({methodId:e,method:
|
|
1
|
+
import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import{useEffect,useMemo,useState}from"react";import{TabView,TabPanel}from"primereact/tabview";import{Button}from"primereact/button";import{CodeEditor}from"../../CodeEditor";import{IdentitySection}from"./IdentitySection";import{FieldArrayEditor}from"./FieldArrayEditor";import{CycleConfigSection}from"./CycleConfigSection";import{ConfigurationsEditor}from"./ConfigurationsEditor";import{ViewsEditor}from"./ViewsEditor";import{RawDataEditor}from"./RawDataEditor";import{AssetRefsEditor}from"./AssetRefsEditor";import{AnalysisEditor}from"./AnalysisEditor";import{validateMethod}from"../validation";export const MethodFormEditor=({methodId:e,method:r,onApply:t,knownAssetTypes:i=[],busy:o})=>{const[s,n]=useState(r),[a,d]=useState(""),[l,h]=useState(null),[c,m]=useState(0);useEffect(()=>{n(r),d(JSON.stringify(r,null,2)),h(null)},[r,e]);const f=useMemo(()=>{try{return JSON.stringify(s)!==JSON.stringify(r)}catch{return!0}},[s,r]),y=useMemo(()=>validateMethod(e,s),[s,e]),g=e=>{n(e),d(JSON.stringify(e,null,2))};return _jsxs("div",{style:{display:"flex",flexDirection:"column",height:"100%",minHeight:0},children:[_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",padding:"0.5rem 1rem",borderBottom:"1px solid var(--surface-d, #e2e8f0)",background:"var(--surface-b, #f8fafc)"},children:[_jsxs("strong",{children:[e,f&&_jsx("span",{style:{marginLeft:"0.5rem",color:"#ea580c"},children:"•"}),y.length>0&&_jsxs("span",{style:{marginLeft:"0.5rem",background:"#fef2f2",color:"#991b1b",padding:"0.1rem 0.5rem",borderRadius:4,fontSize:"0.75rem",fontWeight:600},children:[y.length," issue",1===y.length?"":"s"]})]}),_jsx("div",{style:{display:"flex",gap:"0.5rem"},children:_jsx(Button,{label:"Apply",icon:"pi pi-check",disabled:o||!f||!!l||y.length>0,onClick:async()=>{l||await t(s)}})})]}),y.length>0&&_jsxs("div",{style:{padding:"0.5rem 1rem",background:"#fef2f2",color:"#991b1b",borderBottom:"1px solid #fecaca",fontSize:"0.85rem"},children:[_jsxs("strong",{children:["Fix ",1===y.length?"this issue":"these issues"," before applying:"]}),_jsx("ul",{style:{margin:"0.25rem 0 0 1.25rem"},children:y.map((e,r)=>_jsx("li",{children:e.message},r))})]}),_jsx("div",{style:{flex:1,minHeight:0,overflow:"auto"},children:_jsxs(TabView,{activeIndex:c,onTabChange:e=>m(e.index),children:[_jsx(TabPanel,{header:"Identity",children:_jsx(IdentitySection,{method:s,onChange:g})}),_jsxs(TabPanel,{header:"Fields",children:[_jsx(FieldArrayEditor,{arrayKey:"project_fields",method:s,onChange:g}),_jsx(FieldArrayEditor,{arrayKey:"config_fields",method:s,onChange:g}),_jsx(FieldArrayEditor,{arrayKey:"cycle_config_fields",method:s,onChange:g}),_jsx(CycleConfigSection,{method:s,onChange:g}),_jsx(FieldArrayEditor,{arrayKey:"cycle_fields",method:s,onChange:g}),_jsx(FieldArrayEditor,{arrayKey:"results_fields",method:s,onChange:g})]}),_jsx(TabPanel,{header:"Configurations",children:_jsx(ConfigurationsEditor,{method:s,onChange:g})}),_jsx(TabPanel,{header:"Views",children:_jsx(ViewsEditor,{method:s,onChange:g})}),_jsx(TabPanel,{header:"Raw Data",children:_jsx(RawDataEditor,{method:s,onChange:g})}),_jsx(TabPanel,{header:"Assets",children:_jsx(AssetRefsEditor,{method:s,onChange:g,knownAssetTypes:i})}),_jsx(TabPanel,{header:"Analysis",children:_jsx(AnalysisEditor,{method:s,onChange:g})}),_jsxs(TabPanel,{header:"JSON",children:[l&&_jsx("div",{style:{color:"#dc2626",marginBottom:"0.5rem",fontFamily:"monospace"},children:l}),_jsx("div",{style:{height:"60vh"},children:_jsx(CodeEditor,{code:a,language:"json",theme:"vs-dark",readOnly:!1,cursorStyle:"line",onCodeChanged:e=>{const r=e??"";d(r);try{const e=JSON.parse(r);n(e),h(null)}catch(e){h(String(e?.message??e))}}})})]})]})})]})};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ViewsEditor.d.ts","sourceRoot":"","sources":["../../../../src/components/tis-editor/editor/ViewsEditor.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAwB,UAAU,EAAE,MAAM,UAAU,CAAC;AAEjE,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACxC;AAQD,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,
|
|
1
|
+
{"version":3,"file":"ViewsEditor.d.ts","sourceRoot":"","sources":["../../../../src/components/tis-editor/editor/ViewsEditor.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAwB,UAAU,EAAE,MAAM,UAAU,CAAC;AAEjE,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACxC;AAQD,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAwFlD,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{ChartViewDialog}from"./ChartViewDialog";export const ViewsEditor=({method:e,onChange:t})=>{const i=e.views??{},a=Object.entries(i).map(([e,t])=>({id:e,title:t.title??"",type:t.type??""})),[s,n]=useState(!1),[o,r]=useState(null),l=[...(e.project_fields??[]).map(e=>e.name),...(e.config_fields??[]).map(e=>e.name),...(e.cycle_config_fields??[]).map(e=>e.name),...(e.cycle_fields??[]).map(e=>e.name),...(e.results_fields??[]).map(e=>e.name),...Object.keys(e.raw_data?.columns??{})];return _jsxs(_Fragment,{children:[_jsx(FormSection,{title:"Views",description:"Named chart definitions rendered by <TestDataView> (cycle_scatter) and <TestRawDataView> (raw_trace).",actions:_jsx(Button,{label:"Add view",icon:"pi pi-plus",size:"small",onClick:()=>{r(null),n(!0)}}),children:_jsxs(DataTable,{value:a,dataKey:"id",emptyMessage:"No views defined.",children:[_jsx(Column,{field:"id",header:"View ID"}),_jsx(Column,{field:"title",header:"Title"}),_jsx(Column,{field:"type",header:"Type",style:{width:"8rem"}}),_jsx(Column,{header:"",body:a=>_jsxs("div",{style:{display:"flex",gap:"0.25rem"},children:[_jsx(Button,{icon:"pi pi-pencil",className:"p-button-text p-button-sm",onClick:()=>{return e=a.id,r(e),void n(!0);var e},"aria-label":"Edit"}),_jsx(Button,{icon:"pi pi-trash",className:"p-button-text p-button-danger p-button-sm",onClick:()=>(a=>{if(!window.confirm(`Remove view "${a}"?`))return;const s={...i};delete s[a],t({...e,views:s})})(a.id),"aria-label":"Remove"})]}),style:{width:"6rem"}})]})}),_jsx(ChartViewDialog,{visible:s,initial:o?{viewId:o,view:i[o]}:null,knownKeys:l,siblingIds:Object.keys(i),onCancel:()=>n(!1),onSave:(a,s)=>{const r={...i};o&&o!==a&&delete r[o],r[a]=s,t({...e,views:r}),n(!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{ChartViewDialog}from"./ChartViewDialog";export const ViewsEditor=({method:e,onChange:t})=>{const i=e.views??{},a=Object.entries(i).map(([e,t])=>({id:e,title:t.title??"",type:t.type??""})),[s,n]=useState(!1),[o,r]=useState(null),l=[...(e.project_fields??[]).map(e=>e.name),...(e.config_fields??[]).map(e=>e.name),...(e.cycle_config_fields??[]).map(e=>e.name),...(e.cycle_fields??[]).map(e=>e.name),...(e.results_fields??[]).map(e=>e.name),...Object.keys(e.raw_data?.columns??{}),"cycle_index","timestamp"];return _jsxs(_Fragment,{children:[_jsx(FormSection,{title:"Views",description:"Named chart definitions rendered by <TestDataView> (cycle_scatter) and <TestRawDataView> (raw_trace).",actions:_jsx(Button,{label:"Add view",icon:"pi pi-plus",size:"small",onClick:()=>{r(null),n(!0)}}),children:_jsxs(DataTable,{value:a,dataKey:"id",emptyMessage:"No views defined.",children:[_jsx(Column,{field:"id",header:"View ID"}),_jsx(Column,{field:"title",header:"Title"}),_jsx(Column,{field:"type",header:"Type",style:{width:"8rem"}}),_jsx(Column,{header:"",body:a=>_jsxs("div",{style:{display:"flex",gap:"0.25rem"},children:[_jsx(Button,{icon:"pi pi-pencil",className:"p-button-text p-button-sm",onClick:()=>{return e=a.id,r(e),void n(!0);var e},"aria-label":"Edit"}),_jsx(Button,{icon:"pi pi-trash",className:"p-button-text p-button-danger p-button-sm",onClick:()=>(a=>{if(!window.confirm(`Remove view "${a}"?`))return;const s={...i};delete s[a],t({...e,views:s})})(a.id),"aria-label":"Remove"})]}),style:{width:"6rem"}})]})}),_jsx(ChartViewDialog,{visible:s,initial:o?{viewId:o,view:i[o]}:null,knownKeys:l,siblingIds:Object.keys(i),onCancel:()=>n(!1),onSave:(a,s)=>{const r={...i};o&&o!==a&&delete r[o],r[a]=s,t({...e,views:r}),n(!1)}})]})};
|
|
@@ -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;
|
|
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;AAmBjB,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,CA0JjF;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_config_fields","cycle_fields","results_fields"];export function validateMethod(e,a){const s=[];for(const n of FIELD_ARRAY_KEYS){const t=a[n]??[],o=new Set;t.forEach((a,t)=>{a.name&&""!==a.name.trim()?o.has(a.name)?s.push({path:`${n}.${t}.name`,message:`${e}.${n}: duplicate field name "${a.name}"`}):o.add(a.name):s.push({path:`${n}.${t}.name`,message:`${e}.${n}: empty field name`}),"number"==typeof a.min&&"number"==typeof a.max&&a.min>a.max&&s.push({path:`${n}.${t}.min`,message:`${e}.${n}.${a.name}: min (${a.min}) is greater than max (${a.max})`})})}const n=new Set(["i32","i64","u32","u64","f32","f64"]),t=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 n of["project_fields","config_fields","cycle_config_fields","cycle_fields"])(a[n]??[]).forEach((a,t)=>{a.aggregate&&s.push({path:`${n}.${t}.aggregate`,message:`${e}.${n}.${a.name}: aggregate is only valid on results_fields`})});(a.results_fields??[]).forEach((a,i)=>{const c=a.aggregate;if(!c||"count"===c.fn)return;const f=c.of?.trim();if(!f)return void s.push({path:`results_fields.${i}.aggregate`,message:`${e}.results_fields.${a.name}: aggregate "${c.fn}" requires a target field (of)`});const r="results"===(c.from??"cycle_fields")?o:t,l="results"===(c.from??"cycle_fields")?"results_fields":"cycle_fields",m=r.get(f);void 0===m?s.push({path:`results_fields.${i}.aggregate`,message:`${e}.results_fields.${a.name}: aggregate "of" references "${f}" which is not a ${l} of this method`}):n.has(m)||s.push({path:`results_fields.${i}.aggregate`,message:`${e}.results_fields.${a.name}: aggregate target "${f}" has non-numeric type "${m}"`})});const i=new Set((a.cycle_fields??[]).map(e=>e.name).filter(Boolean));(a.cycle_config_fields??[]).forEach((a,n)=>{a.name&&i.has(a.name)&&s.push({path:`cycle_config_fields.${n}.name`,message:`${e}: "${a.name}" is declared as both a cycle_config_field and a cycle_field. They share one row namespace; rename one.`})});const c=new Set;for(const e of FIELD_ARRAY_KEYS){(a[e]??[]).forEach(e=>e.name&&c.add(e.name))}const f=new Set(Object.keys(a.raw_data?.columns??{})),r=e=>e.field&&e.field.trim()?{key:e.field,ok:c.has(e.field)}:e.column&&e.column.trim()?{key:e.column,ok:f.has(e.column)}:null,l=a.views??{};for(const[a,n]of Object.entries(l)){if(n?.x){const t=r(n.x);t&&!t.ok&&s.push({path:`views.${a}.x`,message:`${e}.views.${a}: x.field/column "${t.key}" does not match any field or raw_data column`})}(n?.y??[]).forEach((n,t)=>{const o=r(n);o&&!o.ok&&s.push({path:`views.${a}.y.${t}`,message:`${e}.views.${a}.y[${t}]: field/column "${o.key}" does not match any field or raw_data column`})})}const m=a.raw_data;m&&(m.blob_name&&""!==String(m.blob_name).trim()||s.push({path:"raw_data.blob_name",message:`${e}.raw_data: blob_name is empty`}));const d=new Set((a.config_fields??[]).map(e=>e.name).filter(Boolean)),g=a.configurations??[],u=new Set;return g.forEach((a,n)=>{const t="string"==typeof a?.name?a.name.trim():"";t?u.has(t)?s.push({path:`configurations.${n}.name`,message:`${e}.configurations: duplicate configuration name "${t}"`}):u.add(t):s.push({path:`configurations.${n}.name`,message:`${e}.configurations[${n}]: empty configuration name`});const o=a?.defaults??{};for(const a of Object.keys(o))d.has(a)||s.push({path:`configurations.${n}.defaults.${a}`,message:`${e}.configurations.${t||n}: override "${a}" does not match any config_field`})}),s}export function validateMethods(e){const a={};for(const[s,n]of Object.entries(e)){const e=validateMethod(s,n);e.length>0&&(a[s]=e)}return a}
|
|
1
|
+
const FIELD_ARRAY_KEYS=["project_fields","config_fields","cycle_config_fields","cycle_fields","results_fields"],SERVER_ASSIGNED_CYCLE_COLUMNS=["cycle_index","timestamp","checksum"];export function validateMethod(e,a){const s=[];for(const n of FIELD_ARRAY_KEYS){const t=a[n]??[],o=new Set;t.forEach((a,t)=>{a.name&&""!==a.name.trim()?o.has(a.name)?s.push({path:`${n}.${t}.name`,message:`${e}.${n}: duplicate field name "${a.name}"`}):o.add(a.name):s.push({path:`${n}.${t}.name`,message:`${e}.${n}: empty field name`}),"number"==typeof a.min&&"number"==typeof a.max&&a.min>a.max&&s.push({path:`${n}.${t}.min`,message:`${e}.${n}.${a.name}: min (${a.min}) is greater than max (${a.max})`})})}const n=new Set(["i32","i64","u32","u64","f32","f64"]),t=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 n of["project_fields","config_fields","cycle_config_fields","cycle_fields"])(a[n]??[]).forEach((a,t)=>{a.aggregate&&s.push({path:`${n}.${t}.aggregate`,message:`${e}.${n}.${a.name}: aggregate is only valid on results_fields`})});(a.results_fields??[]).forEach((a,i)=>{const c=a.aggregate;if(!c||"count"===c.fn)return;const f=c.of?.trim();if(!f)return void s.push({path:`results_fields.${i}.aggregate`,message:`${e}.results_fields.${a.name}: aggregate "${c.fn}" requires a target field (of)`});const r="results"===(c.from??"cycle_fields")?o:t,l="results"===(c.from??"cycle_fields")?"results_fields":"cycle_fields",m=r.get(f);void 0===m?s.push({path:`results_fields.${i}.aggregate`,message:`${e}.results_fields.${a.name}: aggregate "of" references "${f}" which is not a ${l} of this method`}):n.has(m)||s.push({path:`results_fields.${i}.aggregate`,message:`${e}.results_fields.${a.name}: aggregate target "${f}" has non-numeric type "${m}"`})});const i=new Set((a.cycle_fields??[]).map(e=>e.name).filter(Boolean));(a.cycle_config_fields??[]).forEach((a,n)=>{a.name&&i.has(a.name)&&s.push({path:`cycle_config_fields.${n}.name`,message:`${e}: "${a.name}" is declared as both a cycle_config_field and a cycle_field. They share one row namespace; rename one.`})});const c=new Set;for(const e of FIELD_ARRAY_KEYS){(a[e]??[]).forEach(e=>e.name&&c.add(e.name))}for(const e of SERVER_ASSIGNED_CYCLE_COLUMNS)c.add(e);const f=new Set(Object.keys(a.raw_data?.columns??{})),r=e=>e.field&&e.field.trim()?{key:e.field,ok:c.has(e.field)}:e.column&&e.column.trim()?{key:e.column,ok:f.has(e.column)}:null,l=a.views??{};for(const[a,n]of Object.entries(l)){if(n?.x){const t=r(n.x);t&&!t.ok&&s.push({path:`views.${a}.x`,message:`${e}.views.${a}: x.field/column "${t.key}" does not match any field or raw_data column`})}(n?.y??[]).forEach((n,t)=>{const o=r(n);o&&!o.ok&&s.push({path:`views.${a}.y.${t}`,message:`${e}.views.${a}.y[${t}]: field/column "${o.key}" does not match any field or raw_data column`})})}const m=a.raw_data;m&&(m.blob_name&&""!==String(m.blob_name).trim()||s.push({path:"raw_data.blob_name",message:`${e}.raw_data: blob_name is empty`}));const d=new Set((a.config_fields??[]).map(e=>e.name).filter(Boolean)),g=a.configurations??[],u=new Set;return g.forEach((a,n)=>{const t="string"==typeof a?.name?a.name.trim():"";t?u.has(t)?s.push({path:`configurations.${n}.name`,message:`${e}.configurations: duplicate configuration name "${t}"`}):u.add(t):s.push({path:`configurations.${n}.name`,message:`${e}.configurations[${n}]: empty configuration name`});const o=a?.defaults??{};for(const a of Object.keys(o))d.has(a)||s.push({path:`configurations.${n}.defaults.${a}`,message:`${e}.configurations.${t||n}: override "${a}" does not match any config_field`})}),s}export function validateMethods(e){const a={};for(const[s,n]of Object.entries(e)){const e=validateMethod(s,n);e.length>0&&(a[s]=e)}return a}
|
package/package.json
CHANGED
|
@@ -105,7 +105,6 @@ export const MethodFormEditor: React.FC<MethodFormEditorProps> = ({
|
|
|
105
105
|
{dirty && <span style={{ marginLeft: '0.5rem', color: '#ea580c' }}>•</span>}
|
|
106
106
|
{validationErrors.length > 0 && (
|
|
107
107
|
<span
|
|
108
|
-
title={validationErrors.map(e => e.message).join('\n')}
|
|
109
108
|
style={{
|
|
110
109
|
marginLeft: '0.5rem',
|
|
111
110
|
background: '#fef2f2',
|
|
@@ -114,7 +113,6 @@ export const MethodFormEditor: React.FC<MethodFormEditorProps> = ({
|
|
|
114
113
|
borderRadius: 4,
|
|
115
114
|
fontSize: '0.75rem',
|
|
116
115
|
fontWeight: 600,
|
|
117
|
-
cursor: 'help',
|
|
118
116
|
}}
|
|
119
117
|
>
|
|
120
118
|
{validationErrors.length} issue{validationErrors.length === 1 ? '' : 's'}
|
|
@@ -131,6 +129,34 @@ export const MethodFormEditor: React.FC<MethodFormEditorProps> = ({
|
|
|
131
129
|
</div>
|
|
132
130
|
</div>
|
|
133
131
|
|
|
132
|
+
{/* Validation issues, spelled out. These disable Apply, so the
|
|
133
|
+
operator has to be able to READ them — the count used to live
|
|
134
|
+
in a hover `title`, which on the touchscreen HMIs this ships
|
|
135
|
+
to is no affordance at all: Apply was greyed out and nothing
|
|
136
|
+
on screen said why. (gen4_ac, 2026-08-12: a method whose only
|
|
137
|
+
problem was a false-positive chart-axis error couldn't be
|
|
138
|
+
edited at all, and the reason was invisible.) */}
|
|
139
|
+
{validationErrors.length > 0 && (
|
|
140
|
+
<div
|
|
141
|
+
style={{
|
|
142
|
+
padding: '0.5rem 1rem',
|
|
143
|
+
background: '#fef2f2',
|
|
144
|
+
color: '#991b1b',
|
|
145
|
+
borderBottom: '1px solid #fecaca',
|
|
146
|
+
fontSize: '0.85rem',
|
|
147
|
+
}}
|
|
148
|
+
>
|
|
149
|
+
<strong>
|
|
150
|
+
Fix {validationErrors.length === 1 ? 'this issue' : 'these issues'} before applying:
|
|
151
|
+
</strong>
|
|
152
|
+
<ul style={{ margin: '0.25rem 0 0 1.25rem' }}>
|
|
153
|
+
{validationErrors.map((e, i) => (
|
|
154
|
+
<li key={i}>{e.message}</li>
|
|
155
|
+
))}
|
|
156
|
+
</ul>
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
|
|
134
160
|
<div style={{ flex: 1, minHeight: 0, overflow: 'auto' }}>
|
|
135
161
|
<TabView activeIndex={activeTab} onTabChange={(e) => setActiveTab(e.index)}>
|
|
136
162
|
<TabPanel header="Identity">
|
|
@@ -37,6 +37,10 @@ export const ViewsEditor: React.FC<ViewsEditorProps> = ({ method, onChange }) =>
|
|
|
37
37
|
...((method.cycle_fields ?? []) as TestField[]).map(f => f.name),
|
|
38
38
|
...((method.results_fields ?? []) as TestField[]).map(f => f.name),
|
|
39
39
|
...Object.keys((method.raw_data as any)?.columns ?? {}),
|
|
40
|
+
// Written onto every row by the server, not declared anywhere —
|
|
41
|
+
// offered here so "peak load by cycle" is a pick, not a guess.
|
|
42
|
+
'cycle_index',
|
|
43
|
+
'timestamp',
|
|
40
44
|
];
|
|
41
45
|
|
|
42
46
|
const openNew = () => { setEditingId(null); setDialogOpen(true); };
|
|
@@ -14,6 +14,19 @@ const FIELD_ARRAY_KEYS = [
|
|
|
14
14
|
'project_fields', 'config_fields', 'cycle_config_fields', 'cycle_fields', 'results_fields',
|
|
15
15
|
] as const;
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Columns TIS writes onto every cycle row itself, in `add_cycle`. Never
|
|
19
|
+
* declared in a field array — the author can't add them and the control
|
|
20
|
+
* program doesn't supply them — but every recorded row carries them, so a
|
|
21
|
+
* chart view may plot against one. `x.field: "cycle_index"` is the
|
|
22
|
+
* canonical "value by cycle" chart.
|
|
23
|
+
*
|
|
24
|
+
* Mirrors `SERVER_ASSIGNED_CYCLE_COLUMNS` in autocore-tis `main.rs`. Keep
|
|
25
|
+
* the two in step: this list being short by one is what made a valid method
|
|
26
|
+
* unsaveable (gen4_ac, 2026-08-12).
|
|
27
|
+
*/
|
|
28
|
+
const SERVER_ASSIGNED_CYCLE_COLUMNS = ['cycle_index', 'timestamp', 'checksum'] as const;
|
|
29
|
+
|
|
17
30
|
export interface ValidationError {
|
|
18
31
|
/** Dotted path: "<arrayKey>.<index>.<sub>" or "views.<viewId>.x" etc. */
|
|
19
32
|
path: string;
|
|
@@ -98,6 +111,7 @@ export function validateMethod(methodId: string, m: TestMethod): ValidationError
|
|
|
98
111
|
const arr = (m[key] as TestField[] | undefined) ?? [];
|
|
99
112
|
arr.forEach((f) => f.name && knownFields.add(f.name));
|
|
100
113
|
}
|
|
114
|
+
for (const name of SERVER_ASSIGNED_CYCLE_COLUMNS) knownFields.add(name);
|
|
101
115
|
const knownColumns = new Set<string>(
|
|
102
116
|
Object.keys(((m.raw_data as any)?.columns ?? {}) as Record<string, unknown>)
|
|
103
117
|
);
|