@adcops/autocore-react 3.3.48 → 3.3.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/index.d.ts +10 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/tis/ResultHistoryTable.d.ts +19 -0
- package/dist/components/tis/ResultHistoryTable.d.ts.map +1 -0
- package/dist/components/tis/ResultHistoryTable.js +1 -0
- package/dist/components/{TestDataView.d.ts → tis/TestDataView.d.ts} +9 -5
- package/dist/components/tis/TestDataView.d.ts.map +1 -0
- package/dist/components/tis/TestDataView.js +1 -0
- package/dist/components/tis/TestRawDataView.d.ts +18 -0
- package/dist/components/tis/TestRawDataView.d.ts.map +1 -0
- package/dist/components/tis/TestRawDataView.js +1 -0
- package/dist/components/tis/TestSetupForm.d.ts +35 -0
- package/dist/components/tis/TestSetupForm.d.ts.map +1 -0
- package/dist/components/tis/TestSetupForm.js +1 -0
- package/dist/components/tis/TisProvider.d.ts +93 -0
- package/dist/components/tis/TisProvider.d.ts.map +1 -0
- package/dist/components/tis/TisProvider.js +1 -0
- package/package.json +1 -1
- package/src/components/index.ts +40 -2
- package/src/components/tis/ResultHistoryTable.tsx +277 -0
- package/src/components/{TestDataView.tsx → tis/TestDataView.tsx} +72 -38
- package/src/components/{TestRawDataView.tsx → tis/TestRawDataView.tsx} +41 -24
- package/src/components/tis/TestSetupForm.tsx +314 -0
- package/src/components/tis/TisProvider.tsx +404 -0
- package/dist/components/ResultHistoryTable.d.ts +0 -7
- package/dist/components/ResultHistoryTable.d.ts.map +0 -1
- package/dist/components/ResultHistoryTable.js +0 -1
- package/dist/components/TestDataView.d.ts.map +0 -1
- package/dist/components/TestDataView.js +0 -1
- package/dist/components/TestRawDataView.d.ts +0 -14
- package/dist/components/TestRawDataView.d.ts.map +0 -1
- package/dist/components/TestRawDataView.js +0 -1
- package/dist/components/TestSetupForm.d.ts +0 -24
- package/dist/components/TestSetupForm.d.ts.map +0 -1
- package/dist/components/TestSetupForm.js +0 -1
- package/src/components/ResultHistoryTable.tsx +0 -162
- package/src/components/TestSetupForm.tsx +0 -255
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useContext } from 'react';
|
|
2
|
-
import { DataTable } from 'primereact/datatable';
|
|
3
|
-
import { Column } from 'primereact/column';
|
|
4
|
-
import { Button } from 'primereact/button';
|
|
5
|
-
import { EventEmitterContext } from '../core/EventEmitterContext';
|
|
6
|
-
import { MessageType } from '../hub/CommandMessage';
|
|
7
|
-
|
|
8
|
-
export interface ResultHistoryTableProps {
|
|
9
|
-
projectId: string;
|
|
10
|
-
definitionId: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Convert a results `raw_data` blob (`{ colA: [...], colB: [...], ... }`) into
|
|
15
|
-
* a CSV string. Keys with scalar (non-array) values are skipped; array lengths
|
|
16
|
-
* are truncated to the shortest column so the grid is rectangular.
|
|
17
|
-
*
|
|
18
|
-
* Column order: `t` first if present (it's the canonical x-axis in every
|
|
19
|
-
* current test schema), then the remaining keys in their JSON order. Each
|
|
20
|
-
* cell is quoted only when it contains a comma, quote, or newline — matching
|
|
21
|
-
* RFC 4180.
|
|
22
|
-
*/
|
|
23
|
-
const rawBlobToCsv = (blob: any): string => {
|
|
24
|
-
if (!blob || typeof blob !== 'object') return '';
|
|
25
|
-
|
|
26
|
-
const entries: Array<[string, any[]]> = Object.entries(blob)
|
|
27
|
-
.filter(([, v]) => Array.isArray(v)) as Array<[string, any[]]>;
|
|
28
|
-
if (entries.length === 0) return '';
|
|
29
|
-
|
|
30
|
-
entries.sort(([a], [b]) => (a === 't' ? -1 : b === 't' ? 1 : 0));
|
|
31
|
-
|
|
32
|
-
const columns = entries.map(([k]) => k);
|
|
33
|
-
const nRows = entries.reduce((min, [, arr]) => Math.min(min, arr.length), Infinity);
|
|
34
|
-
|
|
35
|
-
const escape = (v: any): string => {
|
|
36
|
-
if (v === null || v === undefined) return '';
|
|
37
|
-
const s = String(v);
|
|
38
|
-
return /[",\n\r]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const lines: string[] = [columns.join(',')];
|
|
42
|
-
for (let i = 0; i < nRows; i++) {
|
|
43
|
-
lines.push(entries.map(([, arr]) => escape(arr[i])).join(','));
|
|
44
|
-
}
|
|
45
|
-
return lines.join('\n');
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Browser download shim: turn a string into a transient blob URL, click it,
|
|
50
|
-
* and clean up. Works without any extra libraries.
|
|
51
|
-
*/
|
|
52
|
-
const downloadCsv = (filename: string, csv: string) => {
|
|
53
|
-
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
|
54
|
-
const url = URL.createObjectURL(blob);
|
|
55
|
-
const a = document.createElement('a');
|
|
56
|
-
a.href = url;
|
|
57
|
-
a.download = filename;
|
|
58
|
-
document.body.appendChild(a);
|
|
59
|
-
a.click();
|
|
60
|
-
a.remove();
|
|
61
|
-
URL.revokeObjectURL(url);
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export const ResultHistoryTable: React.FC<ResultHistoryTableProps> = ({ projectId, definitionId }) => {
|
|
65
|
-
const [tests, setTests] = useState<any[]>([]);
|
|
66
|
-
const [loading, setLoading] = useState(false);
|
|
67
|
-
const [downloadingRunId, setDownloadingRunId] = useState<string | null>(null);
|
|
68
|
-
const { invoke } = useContext(EventEmitterContext);
|
|
69
|
-
|
|
70
|
-
const loadTests = async () => {
|
|
71
|
-
setLoading(true);
|
|
72
|
-
try {
|
|
73
|
-
const resp: any = await invoke('results.list_tests' as any, MessageType.Request, {
|
|
74
|
-
project_id: projectId,
|
|
75
|
-
definition_id: definitionId
|
|
76
|
-
} as any);
|
|
77
|
-
if (resp.success && resp.data && resp.data.tests) {
|
|
78
|
-
setTests(resp.data.tests);
|
|
79
|
-
}
|
|
80
|
-
} catch (err) {
|
|
81
|
-
console.error("Failed to load tests", err);
|
|
82
|
-
}
|
|
83
|
-
setLoading(false);
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
loadTests();
|
|
88
|
-
}, [projectId, definitionId]);
|
|
89
|
-
|
|
90
|
-
const formatDate = (dateStr: string) => {
|
|
91
|
-
if (!dateStr) return '';
|
|
92
|
-
return new Date(dateStr).toLocaleString();
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const handleDownload = async (rowData: any) => {
|
|
96
|
-
const runId = rowData?.run_id;
|
|
97
|
-
if (!runId) return;
|
|
98
|
-
setDownloadingRunId(runId);
|
|
99
|
-
try {
|
|
100
|
-
const resp: any = await invoke('results.read_raw' as any, MessageType.Request, {
|
|
101
|
-
project_id: projectId,
|
|
102
|
-
definition_id: definitionId,
|
|
103
|
-
run_id: runId,
|
|
104
|
-
name: 'trace',
|
|
105
|
-
} as any);
|
|
106
|
-
|
|
107
|
-
if (!resp?.success || !resp.data) {
|
|
108
|
-
console.warn('results.read_raw returned no data for', runId, resp?.error_message);
|
|
109
|
-
alert(`No raw trace available for ${runId}${resp?.error_message ? `: ${resp.error_message}` : ''}`);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const csv = rawBlobToCsv(resp.data);
|
|
114
|
-
if (!csv) {
|
|
115
|
-
alert(`Raw trace for ${runId} is empty or has no array columns.`);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
downloadCsv(`${projectId}_${definitionId}_${runId}.csv`, csv);
|
|
119
|
-
} catch (err) {
|
|
120
|
-
console.error('Failed to download raw trace', err);
|
|
121
|
-
alert(`Download failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
122
|
-
} finally {
|
|
123
|
-
setDownloadingRunId(null);
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
return (
|
|
128
|
-
<div>
|
|
129
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
|
|
130
|
-
<h3>Test History: {definitionId}</h3>
|
|
131
|
-
<Button icon="pi pi-refresh" label="Refresh" onClick={loadTests} disabled={loading} />
|
|
132
|
-
</div>
|
|
133
|
-
|
|
134
|
-
<DataTable value={tests} loading={loading} paginator rows={10} emptyMessage="No tests found.">
|
|
135
|
-
<Column field="run_id" header="Run ID" sortable />
|
|
136
|
-
<Column field="start_time" header="Date/Time" body={(rowData) => formatDate(rowData.start_time)} sortable />
|
|
137
|
-
<Column header="Config / Results" body={(rowData) => (
|
|
138
|
-
<div style={{ fontSize: '0.85em', color: 'var(--text-secondary-color)' }}>
|
|
139
|
-
<div><strong>Config:</strong> {JSON.stringify(rowData.config)}</div>
|
|
140
|
-
<div><strong>Results:</strong> {JSON.stringify(rowData.results)}</div>
|
|
141
|
-
</div>
|
|
142
|
-
)} />
|
|
143
|
-
<Column
|
|
144
|
-
header="Raw Data"
|
|
145
|
-
style={{ width: '8rem' }}
|
|
146
|
-
body={(rowData) => (
|
|
147
|
-
<Button
|
|
148
|
-
icon={downloadingRunId === rowData.run_id ? 'pi pi-spin pi-spinner' : 'pi pi-download'}
|
|
149
|
-
label="CSV"
|
|
150
|
-
size="small"
|
|
151
|
-
outlined
|
|
152
|
-
disabled={downloadingRunId !== null}
|
|
153
|
-
onClick={() => handleDownload(rowData)}
|
|
154
|
-
tooltip={`Download raw_data/trace.json as CSV`}
|
|
155
|
-
tooltipOptions={{ position: 'left' }}
|
|
156
|
-
/>
|
|
157
|
-
)}
|
|
158
|
-
/>
|
|
159
|
-
</DataTable>
|
|
160
|
-
</div>
|
|
161
|
-
);
|
|
162
|
-
};
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useContext } from 'react';
|
|
2
|
-
import { AutoComplete } from 'primereact/autocomplete';
|
|
3
|
-
import type { AutoCompleteCompleteEvent } from 'primereact/autocomplete';
|
|
4
|
-
import { EventEmitterContext } from '../core/EventEmitterContext';
|
|
5
|
-
import { AutoCoreTagContext } from '../core/AutoCoreTagContext';
|
|
6
|
-
import { MessageType } from '../hub/CommandMessage';
|
|
7
|
-
import { ValueInput } from './ValueInput';
|
|
8
|
-
import { TextInput } from './TextInput';
|
|
9
|
-
import { InputText } from 'primereact/inputtext';
|
|
10
|
-
|
|
11
|
-
export interface TestFieldDef {
|
|
12
|
-
name: string;
|
|
13
|
-
type: string;
|
|
14
|
-
units?: string;
|
|
15
|
-
required?: boolean;
|
|
16
|
-
source?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface TestDefinition {
|
|
20
|
-
project_fields: TestFieldDef[];
|
|
21
|
-
config_fields: TestFieldDef[];
|
|
22
|
-
cycle_fields: TestFieldDef[];
|
|
23
|
-
results_fields: TestFieldDef[];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface TestSetupFormProps {
|
|
27
|
-
schema: TestDefinition;
|
|
28
|
-
defaultProjectId?: string;
|
|
29
|
-
defaultDefinitionId?: string;
|
|
30
|
-
onProjectChange?: (projectId: string) => void;
|
|
31
|
-
onDefinitionChange?: (definitionId: string) => void;
|
|
32
|
-
onValidationChange?: (isValid: boolean, config: any) => void;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const TestSetupForm: React.FC<TestSetupFormProps> = ({
|
|
36
|
-
schema,
|
|
37
|
-
defaultProjectId = "",
|
|
38
|
-
defaultDefinitionId = "default",
|
|
39
|
-
onProjectChange,
|
|
40
|
-
onDefinitionChange,
|
|
41
|
-
onValidationChange
|
|
42
|
-
}) => {
|
|
43
|
-
const [config, setConfig] = useState<any>({});
|
|
44
|
-
const [projectId, setProjectId] = useState<string>(defaultProjectId);
|
|
45
|
-
const [definitionId, setDefinitionId] = useState<string>(defaultDefinitionId);
|
|
46
|
-
|
|
47
|
-
// Notify parent when projectId or definitionId changes
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
if (onProjectChange) onProjectChange(projectId);
|
|
50
|
-
}, [projectId, onProjectChange]);
|
|
51
|
-
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
if (onDefinitionChange) onDefinitionChange(definitionId);
|
|
54
|
-
}, [definitionId, onDefinitionChange]);
|
|
55
|
-
|
|
56
|
-
const [existingProjects, setExistingProjects] = useState<string[]>([]);
|
|
57
|
-
const [filteredProjects, setFilteredProjects] = useState<string[]>([]);
|
|
58
|
-
|
|
59
|
-
const [isValid, setIsValid] = useState(false);
|
|
60
|
-
const { invoke, write } = useContext(EventEmitterContext);
|
|
61
|
-
const { rawValues, findTagByFqdn } = useContext(AutoCoreTagContext);
|
|
62
|
-
|
|
63
|
-
// Seed and live-update fields that declare a `source`.
|
|
64
|
-
//
|
|
65
|
-
// The schema's `source` is the remote FQDN (e.g. "gm.x_cycle_move_speed"),
|
|
66
|
-
// but the event bus dispatches on the local `tagName` that the
|
|
67
|
-
// AutoCoreTagProvider registered. We resolve FQDN → tagName via
|
|
68
|
-
// `findTagByFqdn` and then read straight from the provider's `rawValues`,
|
|
69
|
-
// which the provider already eager-reads on mount and keeps current via
|
|
70
|
-
// subscriptions. That single source covers both the initial population
|
|
71
|
-
// (page reload / definition change) and live updates — no separate
|
|
72
|
-
// subscribe or invoke needed here.
|
|
73
|
-
useEffect(() => {
|
|
74
|
-
if (!schema) return;
|
|
75
|
-
|
|
76
|
-
const allFields = [...schema.project_fields, ...schema.config_fields];
|
|
77
|
-
|
|
78
|
-
setConfig((prev: any) => {
|
|
79
|
-
let next = prev;
|
|
80
|
-
for (const field of allFields) {
|
|
81
|
-
if (!field.source) continue;
|
|
82
|
-
|
|
83
|
-
const tag = findTagByFqdn(field.source);
|
|
84
|
-
if (!tag) {
|
|
85
|
-
console.warn(
|
|
86
|
-
`TestSetupForm: no tag registered for source "${field.source}" ` +
|
|
87
|
-
`(field "${field.name}"). Add it to acTagSpec with "ux": true ` +
|
|
88
|
-
`in project.json, or this field won't populate or update.`
|
|
89
|
-
);
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const rawVal = rawValues[tag.tagName];
|
|
94
|
-
if (rawVal === undefined || rawVal === null) continue;
|
|
95
|
-
if (next[field.name] === rawVal) continue;
|
|
96
|
-
|
|
97
|
-
if (next === prev) next = { ...prev };
|
|
98
|
-
next[field.name] = rawVal;
|
|
99
|
-
}
|
|
100
|
-
return next;
|
|
101
|
-
});
|
|
102
|
-
}, [schema, rawValues, findTagByFqdn]);
|
|
103
|
-
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
const fetchProjects = async () => {
|
|
106
|
-
try {
|
|
107
|
-
const resp: any = await invoke('results.list_projects' as any, MessageType.Request as any, {} as any);
|
|
108
|
-
if (resp.success && resp.data && resp.data.projects) {
|
|
109
|
-
setExistingProjects(resp.data.projects);
|
|
110
|
-
}
|
|
111
|
-
} catch (err) {
|
|
112
|
-
console.error("Failed to list projects", err);
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
fetchProjects();
|
|
116
|
-
}, [invoke]);
|
|
117
|
-
|
|
118
|
-
const searchProjects = (event: AutoCompleteCompleteEvent) => {
|
|
119
|
-
const query = event.query.toLowerCase();
|
|
120
|
-
setFilteredProjects(existingProjects.filter(p => p.toLowerCase().includes(query)));
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const handleDefinitionIdChange = (value: string) => {
|
|
124
|
-
const sanitized = value.replace(/[^a-zA-Z0-9_]/g, '');
|
|
125
|
-
setDefinitionId(sanitized);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
useEffect(() => {
|
|
129
|
-
if (!schema) return;
|
|
130
|
-
let valid = true;
|
|
131
|
-
if (!projectId || projectId.trim() === '') valid = false;
|
|
132
|
-
if (!definitionId || definitionId.trim() === '') valid = false;
|
|
133
|
-
|
|
134
|
-
const allFields = [...schema.project_fields, ...schema.config_fields];
|
|
135
|
-
|
|
136
|
-
for (const field of allFields) {
|
|
137
|
-
if (field.required) {
|
|
138
|
-
if (config[field.name] === undefined || config[field.name] === '' || config[field.name] === null) {
|
|
139
|
-
valid = false;
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
setIsValid(valid);
|
|
145
|
-
if (onValidationChange) {
|
|
146
|
-
onValidationChange(valid, config);
|
|
147
|
-
}
|
|
148
|
-
}, [config, schema, projectId, definitionId, onValidationChange]);
|
|
149
|
-
|
|
150
|
-
const isFieldValid = (field: TestFieldDef) => {
|
|
151
|
-
if (!field.required) return true;
|
|
152
|
-
return config[field.name] !== undefined && config[field.name] !== '' && config[field.name] !== null;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const handleProjectIdChange = (value: string | null | undefined) => {
|
|
156
|
-
const val = value || '';
|
|
157
|
-
const sanitized = val.replace(/[^a-zA-Z0-9_]/g, '');
|
|
158
|
-
setProjectId(sanitized);
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const handleFieldChange = async (field: TestFieldDef, val: any) => {
|
|
162
|
-
setConfig({...config, [field.name]: val});
|
|
163
|
-
if (field.source) {
|
|
164
|
-
try {
|
|
165
|
-
await write(field.source, val);
|
|
166
|
-
} catch(e) {
|
|
167
|
-
console.error("Failed to write to source:", e);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const renderField = (field: TestFieldDef) => {
|
|
173
|
-
const valid = isFieldValid(field);
|
|
174
|
-
const isNum = field.type !== 'string' && field.type !== 'bool';
|
|
175
|
-
|
|
176
|
-
return (
|
|
177
|
-
<React.Fragment key={field.name}>
|
|
178
|
-
<span className="ac-form-label">{field.name}</span>
|
|
179
|
-
{isNum ? (
|
|
180
|
-
<ValueInput
|
|
181
|
-
label={undefined}
|
|
182
|
-
value={config[field.name] != null ? Number(config[field.name]) : null}
|
|
183
|
-
onValueChanged={(val) => handleFieldChange(field, val)}
|
|
184
|
-
className={!valid ? 'p-invalid' : ''}
|
|
185
|
-
/>
|
|
186
|
-
) : (
|
|
187
|
-
<TextInput
|
|
188
|
-
label={undefined}
|
|
189
|
-
value={config[field.name] != null ? String(config[field.name]) : ''}
|
|
190
|
-
onValueChanged={(val) => handleFieldChange(field, val)}
|
|
191
|
-
className={!valid ? 'p-invalid' : ''}
|
|
192
|
-
/>
|
|
193
|
-
)}
|
|
194
|
-
|
|
195
|
-
<span className="ac-form-units">{field.units ?? ''}</span>
|
|
196
|
-
|
|
197
|
-
<span style={{ color: valid ? 'var(--green-500)' : 'var(--red-500)', display: 'flex', alignItems: 'center' }}>
|
|
198
|
-
<i className={valid ? "pi pi-check" : "pi pi-times"}></i>
|
|
199
|
-
</span>
|
|
200
|
-
</React.Fragment>
|
|
201
|
-
);
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
if (!schema) {
|
|
205
|
-
return (
|
|
206
|
-
<div className="ac-form-grid" style={{ padding: '1.25rem' }}>
|
|
207
|
-
<h3 className="ac-form-section">No Test Definition Selected</h3>
|
|
208
|
-
</div>
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return (
|
|
213
|
-
<div className="ac-form-grid" style={{ padding: '1.25rem' }}>
|
|
214
|
-
<h3 className="ac-form-section" style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
|
215
|
-
Project & Definition
|
|
216
|
-
<span style={{ color: isValid ? 'var(--green-500)' : 'var(--red-500)' }}>
|
|
217
|
-
<i className={isValid ? "pi pi-check-circle" : "pi pi-exclamation-circle"}></i>
|
|
218
|
-
</span>
|
|
219
|
-
</h3>
|
|
220
|
-
|
|
221
|
-
<span className="ac-form-label">Project ID</span>
|
|
222
|
-
<AutoComplete
|
|
223
|
-
value={projectId}
|
|
224
|
-
suggestions={filteredProjects}
|
|
225
|
-
completeMethod={searchProjects}
|
|
226
|
-
onChange={(e) => handleProjectIdChange(e.value)}
|
|
227
|
-
dropdown
|
|
228
|
-
placeholder="Enter or select Project ID"
|
|
229
|
-
className={!projectId || projectId.trim() === '' ? 'p-invalid' : ''}
|
|
230
|
-
/>
|
|
231
|
-
<span />
|
|
232
|
-
<span style={{ color: projectId && projectId.trim() !== '' ? 'var(--green-500)' : 'var(--red-500)', display: 'flex', alignItems: 'center' }}>
|
|
233
|
-
<i className={projectId && projectId.trim() !== '' ? "pi pi-check" : "pi pi-times"}></i>
|
|
234
|
-
</span>
|
|
235
|
-
|
|
236
|
-
<span className="ac-form-label">Definition ID</span>
|
|
237
|
-
<InputText
|
|
238
|
-
value={definitionId}
|
|
239
|
-
onChange={(e) => handleDefinitionIdChange(e.target.value)}
|
|
240
|
-
placeholder="Enter Definition ID"
|
|
241
|
-
className={!definitionId || definitionId.trim() === '' ? 'p-invalid' : ''}
|
|
242
|
-
/>
|
|
243
|
-
<span />
|
|
244
|
-
<span style={{ color: definitionId && definitionId.trim() !== '' ? 'var(--green-500)' : 'var(--red-500)', display: 'flex', alignItems: 'center' }}>
|
|
245
|
-
<i className={definitionId && definitionId.trim() !== '' ? "pi pi-check" : "pi pi-times"}></i>
|
|
246
|
-
</span>
|
|
247
|
-
|
|
248
|
-
<h3 className="ac-form-section" style={{ marginTop: '1rem' }}>Project Information</h3>
|
|
249
|
-
{schema.project_fields.map(renderField)}
|
|
250
|
-
|
|
251
|
-
<h3 className="ac-form-section" style={{ marginTop: '1rem' }}>Test Configuration</h3>
|
|
252
|
-
{schema.config_fields.map(renderField)}
|
|
253
|
-
</div>
|
|
254
|
-
);
|
|
255
|
-
};
|