@adcops/autocore-react 3.3.72 → 3.3.75
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/assets/HomeMotor.d.ts +4 -0
- package/dist/assets/HomeMotor.d.ts.map +1 -0
- package/dist/assets/HomeMotor.js +1 -0
- package/dist/assets/svg/home_motor.svg +57 -0
- package/dist/components/ValueInput.d.ts +1 -1
- package/dist/components/ValueInput.d.ts.map +1 -1
- package/dist/components/tis/ResultHistoryTable.d.ts.map +1 -1
- package/dist/components/tis/ResultHistoryTable.js +1 -1
- package/dist/components/tis/TestDataView.d.ts.map +1 -1
- package/dist/components/tis/TestDataView.js +1 -1
- package/dist/components/tis/TestRawDataView.d.ts.map +1 -1
- package/dist/components/tis/TestRawDataView.js +1 -1
- package/dist/components/tis/TestSetupForm.d.ts +7 -0
- package/dist/components/tis/TestSetupForm.d.ts.map +1 -1
- package/dist/components/tis/TestSetupForm.js +1 -1
- package/dist/themes/adc-dark/blue/theme.css +9 -0
- package/dist/themes/adc-dark/blue/theme.css.map +1 -1
- package/package.json +5 -1
- package/src/assets/HomeMotor.tsx +37 -0
- package/src/assets/svg/home_motor.svg +57 -0
- package/src/components/ValueInput.tsx +2 -2
- package/src/components/tis/ResultHistoryTable.tsx +155 -42
- package/src/components/tis/TestDataView.tsx +160 -14
- package/src/components/tis/TestRawDataView.tsx +118 -8
- package/src/components/tis/TestSetupForm.tsx +42 -1
- package/src/themes/adc-dark/_extensions.scss +9 -0
|
@@ -56,10 +56,16 @@ export const TestRawDataView: React.FC<TestRawDataViewProps> = (props) => {
|
|
|
56
56
|
const { invoke } = useContext(EventEmitterContext);
|
|
57
57
|
|
|
58
58
|
const [raw, setRaw] = useState<Record<string, number[]> | null>(null);
|
|
59
|
+
const [envelope, setEnvelope] = useState<any | null>(null);
|
|
59
60
|
const [loading, setLoading] = useState(true);
|
|
60
61
|
const [error, setError] = useState<string | null>(null);
|
|
61
62
|
const chartRef = useRef<any>(null);
|
|
62
63
|
|
|
64
|
+
// Per-test cycle picker. Default to latest cycle on disk; the
|
|
65
|
+
// operator can flip backward through earlier cycles.
|
|
66
|
+
const [cycles, setCycles] = useState<number[]>([]);
|
|
67
|
+
const [selectedCycle, setSelectedCycle] = useState<number | null>(null);
|
|
68
|
+
|
|
63
69
|
// raw_trace-capable views only — cycle scatter lives in <TestDataView>.
|
|
64
70
|
const traceViews = useMemo(() => {
|
|
65
71
|
const out: { name: string; view: ChartView }[] = [];
|
|
@@ -75,10 +81,45 @@ export const TestRawDataView: React.FC<TestRawDataViewProps> = (props) => {
|
|
|
75
81
|
|
|
76
82
|
const effectiveBlobName = blobName ?? schema?.raw_data?.blob_name ?? 'trace';
|
|
77
83
|
|
|
78
|
-
//
|
|
84
|
+
// Reset cycle state when the run identity changes; the new run has
|
|
85
|
+
// its own cycle list and "latest" target.
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
setCycles([]);
|
|
88
|
+
setSelectedCycle(null);
|
|
89
|
+
}, [projectId, methodId, runId, effectiveBlobName]);
|
|
90
|
+
|
|
91
|
+
// Discover available cycles for this run/blob. Runs ahead of the
|
|
92
|
+
// data fetch so the cycle picker can render immediately.
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (!projectId || !methodId || !runId) return;
|
|
95
|
+
let cancelled = false;
|
|
96
|
+
(async () => {
|
|
97
|
+
try {
|
|
98
|
+
const resp: any = await invoke(
|
|
99
|
+
'tis.list_raw' as any, MessageType.Request as any,
|
|
100
|
+
{ project_id: projectId, method_id: methodId, run_id: runId } as any);
|
|
101
|
+
if (cancelled || !resp?.success) return;
|
|
102
|
+
const list: any[] = resp.data?.cycles ?? [];
|
|
103
|
+
const indices = list
|
|
104
|
+
.filter(c => c?.name === effectiveBlobName && typeof c?.cycle_index === 'number')
|
|
105
|
+
.map(c => c.cycle_index as number)
|
|
106
|
+
.sort((a, b) => a - b);
|
|
107
|
+
setCycles(indices);
|
|
108
|
+
if (indices.length > 0) {
|
|
109
|
+
setSelectedCycle(prev => prev ?? indices[indices.length - 1]);
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
// Listing failure is non-fatal — the data fetch below
|
|
113
|
+
// still tries "latest" and the picker just stays empty.
|
|
114
|
+
}
|
|
115
|
+
})();
|
|
116
|
+
return () => { cancelled = true; };
|
|
117
|
+
}, [projectId, methodId, runId, effectiveBlobName, invoke]);
|
|
118
|
+
|
|
119
|
+
// Lazy fetch — runs on mount / when identifiers / selectedCycle change.
|
|
79
120
|
useEffect(() => {
|
|
80
121
|
if (!projectId || !methodId || !runId) {
|
|
81
|
-
setRaw(null); setLoading(false); setError(null);
|
|
122
|
+
setRaw(null); setEnvelope(null); setLoading(false); setError(null);
|
|
82
123
|
return;
|
|
83
124
|
}
|
|
84
125
|
let cancelled = false;
|
|
@@ -86,13 +127,18 @@ export const TestRawDataView: React.FC<TestRawDataViewProps> = (props) => {
|
|
|
86
127
|
setError(null);
|
|
87
128
|
(async () => {
|
|
88
129
|
try {
|
|
130
|
+
const args: Record<string, any> = {
|
|
131
|
+
project_id: projectId, method_id: methodId,
|
|
132
|
+
run_id: runId, name: effectiveBlobName,
|
|
133
|
+
};
|
|
134
|
+
if (selectedCycle != null) args.cycle_index = selectedCycle;
|
|
89
135
|
const resp: any = await invoke(
|
|
90
|
-
'tis.read_raw' as any, MessageType.Request as any,
|
|
91
|
-
{ project_id: projectId, method_id: methodId,
|
|
92
|
-
run_id: runId, name: effectiveBlobName } as any);
|
|
136
|
+
'tis.read_raw' as any, MessageType.Request as any, args as any);
|
|
93
137
|
if (cancelled) return;
|
|
94
138
|
if (resp?.success) {
|
|
95
|
-
|
|
139
|
+
const payload = resp.data ?? {};
|
|
140
|
+
setEnvelope(payload);
|
|
141
|
+
setRaw(unwrapEnvelope(payload));
|
|
96
142
|
} else {
|
|
97
143
|
setError(resp?.error_message ?? 'Failed to read raw data');
|
|
98
144
|
}
|
|
@@ -103,7 +149,7 @@ export const TestRawDataView: React.FC<TestRawDataViewProps> = (props) => {
|
|
|
103
149
|
}
|
|
104
150
|
})();
|
|
105
151
|
return () => { cancelled = true; };
|
|
106
|
-
}, [projectId, methodId, runId, effectiveBlobName, invoke]);
|
|
152
|
+
}, [projectId, methodId, runId, effectiveBlobName, selectedCycle, invoke]);
|
|
107
153
|
|
|
108
154
|
const chartData = useMemo(() => {
|
|
109
155
|
if (!raw || !selectedView) return null;
|
|
@@ -174,7 +220,7 @@ export const TestRawDataView: React.FC<TestRawDataViewProps> = (props) => {
|
|
|
174
220
|
|
|
175
221
|
return (
|
|
176
222
|
<div className="vblock" style={{ display: 'flex', flexDirection: 'column', gap: '1rem', height: '100%' }}>
|
|
177
|
-
<div className="flex" style={{ gap: '1rem', alignItems: 'center' }}>
|
|
223
|
+
<div className="flex" style={{ gap: '1rem', alignItems: 'center', flexWrap: 'wrap' }}>
|
|
178
224
|
<Dropdown
|
|
179
225
|
value={selectedView}
|
|
180
226
|
options={traceViews.map(v => ({ label: v.view.title ?? v.name, value: v.name }))}
|
|
@@ -182,12 +228,30 @@ export const TestRawDataView: React.FC<TestRawDataViewProps> = (props) => {
|
|
|
182
228
|
placeholder="Select a view"
|
|
183
229
|
/>
|
|
184
230
|
<h3 style={{ margin: 0 }}>{selectedViewDef?.title ?? ''}</h3>
|
|
231
|
+
{cycles.length > 1 && (
|
|
232
|
+
<>
|
|
233
|
+
<label htmlFor="rawview-cycle-picker"
|
|
234
|
+
style={{ color: 'var(--text-secondary-color)' }}>Cycle:</label>
|
|
235
|
+
<Dropdown
|
|
236
|
+
inputId="rawview-cycle-picker"
|
|
237
|
+
value={selectedCycle}
|
|
238
|
+
options={cycles.map(c => ({ label: `Cycle ${c}`, value: c }))}
|
|
239
|
+
onChange={(e) => setSelectedCycle(Number(e.value))}
|
|
240
|
+
style={{ minWidth: '8rem' }}
|
|
241
|
+
/>
|
|
242
|
+
<span style={{ color: 'var(--text-secondary-color)' }}>
|
|
243
|
+
of {cycles.length}
|
|
244
|
+
</span>
|
|
245
|
+
</>
|
|
246
|
+
)}
|
|
185
247
|
<div style={{ flex: 1 }} />
|
|
186
248
|
<Button icon="pi pi-refresh" label="Reset Zoom"
|
|
187
249
|
outlined
|
|
188
250
|
onClick={() => chartRef.current?.resetZoom?.()} />
|
|
189
251
|
</div>
|
|
190
252
|
|
|
253
|
+
<EnvelopeMetaStrip envelope={envelope} />
|
|
254
|
+
|
|
191
255
|
<div style={{ flex: 1, minHeight: 0, height: chartHeight, position: 'relative' }}>
|
|
192
256
|
{loading && <Overlay>Loading raw data…</Overlay>}
|
|
193
257
|
{error && <Overlay>{error}</Overlay>}
|
|
@@ -215,6 +279,52 @@ const EmptyState: React.FC<{ message: string }> = ({ message }) => (
|
|
|
215
279
|
<div style={{ padding: '1rem', color: 'var(--text-secondary-color)' }}>{message}</div>
|
|
216
280
|
);
|
|
217
281
|
|
|
282
|
+
// tis.read_raw returns one of:
|
|
283
|
+
// - per-cycle envelope: { cycle_index, cycle_fields, context, data }
|
|
284
|
+
// - legacy flat blob: { col: number[] }
|
|
285
|
+
// Chart/CSV code only wants the columnar payload — strip the envelope
|
|
286
|
+
// when present, otherwise pass the blob through.
|
|
287
|
+
const unwrapEnvelope = (blob: any): Record<string, number[]> => {
|
|
288
|
+
if (!blob || typeof blob !== 'object') return {};
|
|
289
|
+
if ('data' in blob && blob.data && typeof blob.data === 'object'
|
|
290
|
+
&& Object.values(blob.data).some(v => Array.isArray(v))) {
|
|
291
|
+
return blob.data;
|
|
292
|
+
}
|
|
293
|
+
return blob;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// Strip of cycle_index + cycle_fields + context, rendered above the
|
|
297
|
+
// chart so the operator can see *which* cycle they're looking at and
|
|
298
|
+
// what schema-declared metric values were active for it. Renders
|
|
299
|
+
// nothing for legacy flat blobs (no envelope to read).
|
|
300
|
+
const EnvelopeMetaStrip: React.FC<{ envelope: any }> = ({ envelope }) => {
|
|
301
|
+
if (!envelope || typeof envelope !== 'object') return null;
|
|
302
|
+
const ci = envelope.cycle_index;
|
|
303
|
+
const cf = envelope.cycle_fields;
|
|
304
|
+
const ctx = envelope.context;
|
|
305
|
+
if (ci == null && !cf && !ctx) return null;
|
|
306
|
+
const kv = (obj: any) => {
|
|
307
|
+
if (!obj || typeof obj !== 'object') return null;
|
|
308
|
+
return Object.entries(obj)
|
|
309
|
+
.filter(([, v]) => v !== null && typeof v !== 'object')
|
|
310
|
+
.map(([k, v]) => (
|
|
311
|
+
<span key={k} style={{ marginRight: '1rem' }}>
|
|
312
|
+
<span style={{ color: 'var(--text-secondary-color)' }}>{k}: </span>
|
|
313
|
+
<span>{String(v)}</span>
|
|
314
|
+
</span>
|
|
315
|
+
));
|
|
316
|
+
};
|
|
317
|
+
return (
|
|
318
|
+
<div style={{ padding: '0.5rem 0.25rem', fontSize: '0.9rem',
|
|
319
|
+
borderTop: '1px solid var(--surface-border)',
|
|
320
|
+
borderBottom: '1px solid var(--surface-border)' }}>
|
|
321
|
+
{ci != null && <div><strong>Cycle {ci}</strong></div>}
|
|
322
|
+
{cf && <div style={{ marginTop: '0.25rem' }}>{kv(cf)}</div>}
|
|
323
|
+
{ctx && <div style={{ marginTop: '0.25rem' }}>{kv(ctx)}</div>}
|
|
324
|
+
</div>
|
|
325
|
+
);
|
|
326
|
+
};
|
|
327
|
+
|
|
218
328
|
const CHART_COLORS = [
|
|
219
329
|
'#4ea8de', '#f59e0b', '#22c55e', '#a855f7',
|
|
220
330
|
'#ef4444', '#14b8a6', '#eab308', '#ec4899',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect, useContext, useMemo } from 'react';
|
|
1
|
+
import React, { useState, useEffect, useContext, useMemo, useRef } from 'react';
|
|
2
2
|
import { Button } from 'primereact/button';
|
|
3
3
|
import { InputText } from 'primereact/inputtext';
|
|
4
4
|
import { Dropdown } from 'primereact/dropdown';
|
|
@@ -38,6 +38,13 @@ export interface TestFieldDef {
|
|
|
38
38
|
label?: string;
|
|
39
39
|
/** Long-form guidance surfaced as a hover tooltip on an info icon. */
|
|
40
40
|
description?: string;
|
|
41
|
+
/** Seed value applied when the operator selects this test method.
|
|
42
|
+
* For source-bound fields the default is written to the GM tag so
|
|
43
|
+
* the control program sees it; for non-source fields it's stashed
|
|
44
|
+
* straight into stagedConfig. Operator edits override per-stage,
|
|
45
|
+
* but the schema default never mutates — re-selecting the method
|
|
46
|
+
* re-applies the default. */
|
|
47
|
+
default?: any;
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
export interface TestMethod {
|
|
@@ -240,6 +247,40 @@ export const TestSetupForm: React.FC<TestSetupFormProps> = ({
|
|
|
240
247
|
);
|
|
241
248
|
const closeInfoDialog = () => setInfoDialog({ open: false, title: '', body: null });
|
|
242
249
|
|
|
250
|
+
// Apply schema-declared defaults when the operator picks a method.
|
|
251
|
+
// Source-bound fields write the default to GM (the control program
|
|
252
|
+
// is the consumer of record); non-source fields land directly in
|
|
253
|
+
// stagedConfig. We track the last method we seeded so subsequent
|
|
254
|
+
// re-renders don't clobber operator edits — only an actual method
|
|
255
|
+
// change re-applies the defaults.
|
|
256
|
+
const defaultsAppliedFor = useRef<string>('');
|
|
257
|
+
useEffect(() => {
|
|
258
|
+
if (!schema || !methodId) return;
|
|
259
|
+
if (defaultsAppliedFor.current === methodId) return;
|
|
260
|
+
defaultsAppliedFor.current = methodId;
|
|
261
|
+
|
|
262
|
+
setConfig((prev: any) => {
|
|
263
|
+
let next = prev;
|
|
264
|
+
for (const field of schema.config_fields) {
|
|
265
|
+
if (field.name === 'sample_id') continue;
|
|
266
|
+
if (field.default === undefined || field.default === null) continue;
|
|
267
|
+
if (next === prev) next = { ...prev };
|
|
268
|
+
next[field.name] = field.default;
|
|
269
|
+
if (field.source) {
|
|
270
|
+
// Mirror handleFieldChange: write to GM so the
|
|
271
|
+
// control program sees the default. Errors here are
|
|
272
|
+
// logged but non-fatal — the form still reflects
|
|
273
|
+
// the default locally.
|
|
274
|
+
void Promise.resolve()
|
|
275
|
+
.then(() => write(field.source!, field.default))
|
|
276
|
+
.catch(e => console.error(
|
|
277
|
+
`[TestSetupForm] Failed to seed default for ${field.name}:`, e));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return next;
|
|
281
|
+
});
|
|
282
|
+
}, [schema, methodId, write]);
|
|
283
|
+
|
|
243
284
|
// Seed and live-update config_fields that declare a `source`.
|
|
244
285
|
useEffect(() => {
|
|
245
286
|
if (!schema) return;
|