@adcops/autocore-react 3.3.46 → 3.3.48
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResultHistoryTable.d.ts","sourceRoot":"","sources":["../../src/components/ResultHistoryTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAO/D,MAAM,WAAW,uBAAuB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACxB;
|
|
1
|
+
{"version":3,"file":"ResultHistoryTable.d.ts","sourceRoot":"","sources":["../../src/components/ResultHistoryTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAO/D,MAAM,WAAW,uBAAuB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACxB;AAqDD,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAkGhE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsxs as _jsxs,jsx as _jsx}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{EventEmitterContext}from"../core/EventEmitterContext";import{MessageType}from"../hub/CommandMessage";export const ResultHistoryTable=({projectId:e,definitionId:t})=>{const[s,r]=useState([]),[
|
|
1
|
+
import{jsxs as _jsxs,jsx as _jsx}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{EventEmitterContext}from"../core/EventEmitterContext";import{MessageType}from"../hub/CommandMessage";const rawBlobToCsv=e=>{if(!e||"object"!=typeof e)return"";const t=Object.entries(e).filter(([,e])=>Array.isArray(e));if(0===t.length)return"";t.sort(([e],[t])=>"t"===e?-1:"t"===t?1:0);const s=t.map(([e])=>e),r=t.reduce((e,[,t])=>Math.min(e,t.length),1/0),o=e=>{if(null==e)return"";const t=String(e);return/[",\n\r]/.test(t)?`"${t.replace(/"/g,'""')}"`:t},n=[s.join(",")];for(let e=0;e<r;e++)n.push(t.map(([,t])=>o(t[e])).join(","));return n.join("\n")},downloadCsv=(e,t)=>{const s=new Blob([t],{type:"text/csv;charset=utf-8;"}),r=URL.createObjectURL(s),o=document.createElement("a");o.href=r,o.download=e,document.body.appendChild(o),o.click(),o.remove(),URL.revokeObjectURL(r)};export const ResultHistoryTable=({projectId:e,definitionId:t})=>{const[s,r]=useState([]),[o,n]=useState(!1),[a,i]=useState(null),{invoke:l}=useContext(EventEmitterContext),c=async()=>{n(!0);try{const s=await l("results.list_tests",MessageType.Request,{project_id:e,definition_id:t});s.success&&s.data&&s.data.tests&&r(s.data.tests)}catch(e){}n(!1)};useEffect(()=>{c()},[e,t]);return _jsxs("div",{children:[_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"1rem"},children:[_jsxs("h3",{children:["Test History: ",t]}),_jsx(Button,{icon:"pi pi-refresh",label:"Refresh",onClick:c,disabled:o})]}),_jsxs(DataTable,{value:s,loading:o,paginator:!0,rows:10,emptyMessage:"No tests found.",children:[_jsx(Column,{field:"run_id",header:"Run ID",sortable:!0}),_jsx(Column,{field:"start_time",header:"Date/Time",body:e=>{return(t=e.start_time)?new Date(t).toLocaleString():"";var t},sortable:!0}),_jsx(Column,{header:"Config / Results",body:e=>_jsxs("div",{style:{fontSize:"0.85em",color:"var(--text-secondary-color)"},children:[_jsxs("div",{children:[_jsx("strong",{children:"Config:"})," ",JSON.stringify(e.config)]}),_jsxs("div",{children:[_jsx("strong",{children:"Results:"})," ",JSON.stringify(e.results)]})]})}),_jsx(Column,{header:"Raw Data",style:{width:"8rem"},body:s=>_jsx(Button,{icon:a===s.run_id?"pi pi-spin pi-spinner":"pi pi-download",label:"CSV",size:"small",outlined:!0,disabled:null!==a,onClick:()=>(async s=>{const r=s?.run_id;if(r){i(r);try{const s=await l("results.read_raw",MessageType.Request,{project_id:e,definition_id:t,run_id:r,name:"trace"});if(!s?.success||!s.data)return void alert(`No raw trace available for ${r}${s?.error_message?`: ${s.error_message}`:""}`);const o=rawBlobToCsv(s.data);if(!o)return void alert(`Raw trace for ${r} is empty or has no array columns.`);downloadCsv(`${e}_${t}_${r}.csv`,o)}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{i(null)}}})(s),tooltip:"Download raw_data/trace.json as CSV",tooltipOptions:{position:"left"}})})]})]})};
|
package/package.json
CHANGED
|
@@ -10,9 +10,61 @@ export interface ResultHistoryTableProps {
|
|
|
10
10
|
definitionId: string;
|
|
11
11
|
}
|
|
12
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
|
+
|
|
13
64
|
export const ResultHistoryTable: React.FC<ResultHistoryTableProps> = ({ projectId, definitionId }) => {
|
|
14
65
|
const [tests, setTests] = useState<any[]>([]);
|
|
15
66
|
const [loading, setLoading] = useState(false);
|
|
67
|
+
const [downloadingRunId, setDownloadingRunId] = useState<string | null>(null);
|
|
16
68
|
const { invoke } = useContext(EventEmitterContext);
|
|
17
69
|
|
|
18
70
|
const loadTests = async () => {
|
|
@@ -40,13 +92,45 @@ export const ResultHistoryTable: React.FC<ResultHistoryTableProps> = ({ projectI
|
|
|
40
92
|
return new Date(dateStr).toLocaleString();
|
|
41
93
|
};
|
|
42
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
|
+
|
|
43
127
|
return (
|
|
44
128
|
<div>
|
|
45
129
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
|
|
46
130
|
<h3>Test History: {definitionId}</h3>
|
|
47
131
|
<Button icon="pi pi-refresh" label="Refresh" onClick={loadTests} disabled={loading} />
|
|
48
132
|
</div>
|
|
49
|
-
|
|
133
|
+
|
|
50
134
|
<DataTable value={tests} loading={loading} paginator rows={10} emptyMessage="No tests found.">
|
|
51
135
|
<Column field="run_id" header="Run ID" sortable />
|
|
52
136
|
<Column field="start_time" header="Date/Time" body={(rowData) => formatDate(rowData.start_time)} sortable />
|
|
@@ -56,6 +140,22 @@ export const ResultHistoryTable: React.FC<ResultHistoryTableProps> = ({ projectI
|
|
|
56
140
|
<div><strong>Results:</strong> {JSON.stringify(rowData.results)}</div>
|
|
57
141
|
</div>
|
|
58
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
|
+
/>
|
|
59
159
|
</DataTable>
|
|
60
160
|
</div>
|
|
61
161
|
);
|