Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit27ea453

Browse files
Merge pull request#1803 from iamfaran/feat/1799-curl-import
[Feat]: Add Import from cURL in Query Library
2 parents4278242 +12b4b41 commit27ea453

File tree

7 files changed

+304
-16
lines changed

7 files changed

+304
-16
lines changed

‎client/packages/lowcoder/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"types":"src/index.sdk.ts",
88
"dependencies": {
99
"@ant-design/icons":"^5.3.0",
10+
"@bany/curl-to-json":"^1.2.8",
1011
"@codemirror/autocomplete":"^6.11.1",
1112
"@codemirror/commands":"^6.3.2",
1213
"@codemirror/lang-css":"^6.2.1",
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
importReact,{useState}from"react";
2+
import{Modal,Input,Button,message}from"antd";
3+
import{trans}from"i18n";
4+
importparseCurlfrom"@bany/curl-to-json";
5+
const{ TextArea}=Input;
6+
interfaceCurlImportModalProps{
7+
open:boolean;
8+
onCancel:()=>void;
9+
onSuccess:(parsedData:any)=>void;
10+
}
11+
12+
exportfunctionCurlImportModal(props:CurlImportModalProps){
13+
const{ open, onCancel, onSuccess}=props;
14+
const[curlCommand,setCurlCommand]=useState("");
15+
const[loading,setLoading]=useState(false);
16+
17+
consthandleImport=async()=>{
18+
if(!curlCommand.trim()){
19+
message.error("Please enter a cURL command");
20+
return;
21+
}
22+
23+
setLoading(true);
24+
try{
25+
// Parse the cURL command using the correct import
26+
constparsedData=parseCurl(curlCommand);
27+
28+
29+
30+
// Log the result for now as requested
31+
// console.log("Parsed cURL data:", parsedData);
32+
33+
// Call success callback with parsed data
34+
onSuccess(parsedData);
35+
36+
// Reset form and close modal
37+
setCurlCommand("");
38+
onCancel();
39+
40+
message.success("cURL command imported successfully!");
41+
}catch(error:any){
42+
console.error("Error parsing cURL command:",error);
43+
message.error(`Failed to parse cURL command:${error.message}`);
44+
}finally{
45+
setLoading(false);
46+
}
47+
};
48+
49+
consthandleCancel=()=>{
50+
setCurlCommand("");
51+
onCancel();
52+
};
53+
54+
return(
55+
<Modal
56+
title="Import from cURL"
57+
open={open}
58+
onCancel={handleCancel}
59+
footer={[
60+
<Buttonkey="cancel"onClick={handleCancel}>
61+
Cancel
62+
</Button>,
63+
<Buttonkey="import"type="primary"loading={loading}onClick={handleImport}>
64+
Import
65+
</Button>,
66+
]}
67+
width={600}
68+
>
69+
<divstyle={{marginBottom:16}}>
70+
<divstyle={{marginBottom:8,fontWeight:500}}>
71+
Paste cURL Command Here
72+
</div>
73+
<divstyle={{marginBottom:12,color:"#666",fontSize:"12px"}}>
74+
<divstyle={{marginBottom:4}}>
75+
<strong>Examples:</strong>
76+
</div>
77+
<divstyle={{marginBottom:2}}>
78+
GET:<code>curl -X GET https://jsonplaceholder.typicode.com/posts/1</code>
79+
</div>
80+
<divstyle={{marginBottom:2}}>
81+
POST:<code>curl -X POST https://jsonplaceholder.typicode.com/posts -H "Content-Type: application/json" -d '&#123;"title":"foo","body":"bar","userId":1&#125;'</code>
82+
</div>
83+
<div>
84+
Users:<code>curl -X GET https://jsonplaceholder.typicode.com/users</code>
85+
</div>
86+
</div>
87+
<TextArea
88+
value={curlCommand}
89+
onChange={(e)=>setCurlCommand(e.target.value)}
90+
placeholder="curl -X GET https://jsonplaceholder.typicode.com/posts/1"
91+
rows={8}
92+
style={{fontFamily:"monospace"}}
93+
/>
94+
</div>
95+
</Modal>
96+
);
97+
}

‎client/packages/lowcoder/src/components/ResCreatePanel.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { getUser } from "../redux/selectors/usersSelectors";
2626
importDataSourceIconfrom"./DataSourceIcon";
2727
import{genRandomKey}from"comps/utils/idGenerator";
2828
import{isPublicApplication}from"@lowcoder-ee/redux/selectors/applicationSelector";
29+
import{CurlImportModal}from"./CurlImport";
2930

3031
constWrapper=styled.div<{$placement:PageType}>`
3132
width: 100%;
@@ -230,6 +231,7 @@ export function ResCreatePanel(props: ResCreateModalProps) {
230231
const{ onSelect, onClose, recentlyUsed, datasource, placement="editor"}=props;
231232
const[isScrolling,setScrolling]=useState(false);
232233
const[visible,setVisible]=useState(false);
234+
const[curlModalVisible,setCurlModalVisible]=useState(false);
233235

234236
constisPublicApp=useSelector(isPublicApplication);
235237
constuser=useSelector(getUser);
@@ -244,6 +246,14 @@ export function ResCreatePanel(props: ResCreateModalProps) {
244246
setScrolling(top>0);
245247
},100);
246248

249+
consthandleCurlImportSuccess=(parsedData:any)=>{
250+
onSelect(BottomResTypeEnum.Query,{
251+
compType:"restApi",
252+
dataSourceId:QUICK_REST_API_ID,
253+
curlData:parsedData
254+
});
255+
};
256+
247257
return(
248258
<Wrapper$placement={placement}>
249259
<Title$shadow={isScrolling}$placement={placement}>
@@ -331,6 +341,10 @@ export function ResCreatePanel(props: ResCreateModalProps) {
331341
<ResButtonsize={buttonSize}identifier={"streamApi"}onSelect={onSelect}/>
332342
<ResButtonsize={buttonSize}identifier={"alasql"}onSelect={onSelect}/>
333343
<ResButtonsize={buttonSize}identifier={"graphql"}onSelect={onSelect}/>
344+
<DataSourceButtonsize={buttonSize}onClick={()=>setCurlModalVisible(true)}>
345+
<DataSourceIconsize="large"dataSourceType="restApi"/>
346+
Import from cURL
347+
</DataSourceButton>
334348
</DataSourceListWrapper>
335349
</div>
336350

@@ -374,6 +388,11 @@ export function ResCreatePanel(props: ResCreateModalProps) {
374388
onCancel={()=>setVisible(false)}
375389
onCreated={()=>setVisible(false)}
376390
/>
391+
<CurlImportModal
392+
open={curlModalVisible}
393+
onCancel={()=>setCurlModalVisible(false)}
394+
onSuccess={handleCurlImportSuccess}
395+
/>
377396
</Wrapper>
378397
);
379398
}

‎client/packages/lowcoder/src/comps/queries/queryComp.tsx

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import {
6464
import{QueryContext}from"../../util/context/QueryContext";
6565
import{useFixedDelay}from"../../util/hooks";
6666
import{JSONObject,JSONValue}from"../../util/jsonTypes";
67+
import{processCurlData}from"../../util/curlUtils";
6768
import{BoolPureControl}from"../controls/boolControl";
6869
import{millisecondsControl}from"../controls/millisecondControl";
6970
import{paramsMillisecondsControl}from"../controls/paramsControl";
@@ -743,18 +744,42 @@ class QueryListComp extends QueryListTmpComp implements BottomResListComp {
743744
constname=this.genNewName(editorState);
744745
constcompType=extraInfo?.compType||"js";
745746
constdataSourceId=extraInfo?.dataSourceId;
747+
constcurlData=extraInfo?.curlData;
748+
console.log("CURL DATA",curlData);
749+
750+
// Build the basic payload
751+
letpayload:any={
752+
id:id,
753+
name:name,
754+
datasourceId:dataSourceId,
755+
compType,
756+
triggerType:manualTriggerResource.includes(compType) ?"manual" :"automatic",
757+
isNewCreate:true,
758+
order:Date.now(),
759+
};
760+
761+
// If this is a REST API created from cURL, pre-populate the HTTP query fields
762+
if(compType==="restApi"&&curlData){
763+
constcurlConfig=processCurlData(curlData);
764+
if(curlConfig){
765+
payload={
766+
...payload,
767+
comp:{
768+
httpMethod:curlConfig.method,
769+
path:curlConfig.url,
770+
headers:curlConfig.headers,
771+
params:curlConfig.params,
772+
bodyType:curlConfig.bodyType,
773+
body:curlConfig.body,
774+
bodyFormData:curlConfig.bodyFormData,
775+
},
776+
};
777+
}
778+
}
746779

747780
this.dispatch(
748781
wrapActionExtraInfo(
749-
this.pushAction({
750-
id:id,
751-
name:name,
752-
datasourceId:dataSourceId,
753-
compType,
754-
triggerType:manualTriggerResource.includes(compType) ?"manual" :"automatic",
755-
isNewCreate:true,
756-
order:Date.now(),
757-
}),
782+
this.pushAction(payload),
758783
{
759784
compInfos:[
760785
{

‎client/packages/lowcoder/src/pages/queryLibrary/QueryLibraryEditor.tsx

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import { messageInstance } from "lowcoder-design/src/components/GlobalInstances"
4848
import{Helmet}from"react-helmet";
4949
import{fetchQLPaginationByOrg}from"@lowcoder-ee/util/pagination/axios";
5050
import{isEmpty}from"lodash";
51+
import{processCurlData}from"../../util/curlUtils";
5152

5253
constWrapper=styled.div`
5354
display: flex;
@@ -199,17 +200,39 @@ export const QueryLibraryEditor = () => {
199200
constnewName=nameGenerator.genItemName(trans("queryLibrary.unnamed"));
200201

201202
consthandleAdd=(type:BottomResTypeEnum,extraInfo?:any)=>{
203+
// Build basic query DSL
204+
letqueryDSL:any={
205+
triggerType:"manual",
206+
datasourceId:extraInfo?.dataSourceId,
207+
compType:extraInfo?.compType,
208+
};
209+
210+
// If it is a REST API created from cURL, pre-populate the HTTP query fields
211+
if(extraInfo?.compType==="restApi"&&extraInfo?.curlData){
212+
constcurlConfig=processCurlData(extraInfo.curlData);
213+
if(curlConfig){
214+
queryDSL={
215+
...queryDSL,
216+
comp:{
217+
httpMethod:curlConfig.method,
218+
path:curlConfig.url,
219+
headers:curlConfig.headers,
220+
params:curlConfig.params,
221+
bodyType:curlConfig.bodyType,
222+
body:curlConfig.body,
223+
bodyFormData:curlConfig.bodyFormData,
224+
},
225+
};
226+
}
227+
}
228+
202229
dispatch(
203230
createQueryLibrary(
204231
{
205232
name:newName,
206233
organizationId:orgId,
207234
libraryQueryDSL:{
208-
query:{
209-
triggerType:"manual",
210-
datasourceId:extraInfo?.dataSourceId,
211-
compType:extraInfo?.compType,
212-
},
235+
query:queryDSL,
213236
},
214237
},
215238
(resp)=>{
@@ -218,7 +241,6 @@ export const QueryLibraryEditor = () => {
218241
setModify(!modify);
219242
},200);
220243
setCurrentPage(Math.ceil(elements.total/pageSize));
221-
222244
},
223245
()=>{}
224246
)
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/**
2+
* Utility to convert parsed cURL data from @bany/curl-to-json library
3+
* to the format expected by REST API query components
4+
*/
5+
6+
// Body type mapping to match the dropdown values in httpQuery.tsx
7+
constCONTENT_TYPE_TO_BODY_TYPE:Record<string,string>={
8+
"application/json":"application/json",
9+
"text/plain":"text/plain",
10+
"text/html":"text/plain",
11+
"text/xml":"text/plain",
12+
"application/xml":"text/plain",
13+
"application/x-www-form-urlencoded":"application/x-www-form-urlencoded",
14+
"multipart/form-data":"multipart/form-data",
15+
};
16+
17+
/**
18+
* Parse URL-encoded form data - handles both string and object input
19+
*/
20+
functionparseUrlEncodedData(data:string|object):Array<{key:string;value:string;type:string}>{
21+
if(!data){
22+
return[{key:"",value:"",type:"text"}];
23+
}
24+
25+
try{
26+
letresult:Array<{key:string;value:string;type:string}>=[];
27+
28+
if(typeofdata==='object'){
29+
//@bany/curl-to-json already parsed it into an object
30+
Object.entries(data).forEach(([key,value])=>{
31+
result.push({
32+
key:key,
33+
value:decodeURIComponent(String(value).replace(/\+/g,' ')),// Handle URL encoding
34+
type:"text"
35+
});
36+
});
37+
}elseif(typeofdata==='string'){
38+
// Raw URL-encoded string - use URLSearchParams
39+
constparams=newURLSearchParams(data);
40+
params.forEach((value,key)=>{
41+
result.push({
42+
key:key,
43+
value:value,
44+
type:"text"
45+
});
46+
});
47+
}
48+
49+
returnresult.length>0 ?result :[{key:"",value:"",type:"text"}];
50+
}catch(error){
51+
console.warn('Failed to parse URL-encoded data:',error);
52+
return[{key:"",value:"",type:"text"}];
53+
}
54+
}
55+
56+
exportfunctionprocessCurlData(curlData:any){
57+
if(!curlData)returnnull;
58+
59+
60+
// Convert headers object to key-value array format expected by UI
61+
constheaders=curlData.header
62+
?Object.entries(curlData.header).map(([key,value])=>({ key, value}))
63+
:[{key:"",value:""}];
64+
65+
// Convert query params object to key-value array format expected by UI
66+
constparams=curlData.params
67+
?Object.entries(curlData.params).map(([key,value])=>({ key, value}))
68+
:[{key:"",value:""}];
69+
70+
// Get request body - @bany/curl-to-json may use 'body' or 'data'
71+
constbodyContent=curlData.body!==undefined ?curlData.body :curlData.data;
72+
73+
// Determine body type based on Content-Type header or content structure
74+
letbodyType="none";
75+
letbodyFormData=[{key:"",value:"",type:"text"}];
76+
letprocessedBody="";
77+
78+
if(bodyContent!==undefined&&bodyContent!==""){
79+
constcontentTypeHeader=curlData.header?.["Content-Type"]||curlData.header?.["content-type"];
80+
81+
if(contentTypeHeader){
82+
// Extract base content type (remove charset, boundary, etc.)
83+
constbaseContentType=contentTypeHeader.split(';')[0].trim().toLowerCase();
84+
bodyType=CONTENT_TYPE_TO_BODY_TYPE[baseContentType]||"text/plain";
85+
}else{
86+
// Fallback: infer from content structure
87+
if(typeofbodyContent==="object"){
88+
bodyType="application/json";
89+
}else{
90+
bodyType="text/plain";
91+
}
92+
}
93+
94+
// Handle different body types
95+
if(bodyType==="application/x-www-form-urlencoded"){
96+
bodyFormData=parseUrlEncodedData(bodyContent);
97+
processedBody="";// Form data goes in bodyFormData, not body
98+
}elseif(typeofbodyContent==="object"){
99+
processedBody=JSON.stringify(bodyContent,null,2);
100+
}else{
101+
processedBody=bodyContent;
102+
}
103+
}
104+
105+
return{
106+
method:curlData.method||"GET",
107+
url:curlData.url||"",
108+
headers,
109+
params,
110+
bodyType,
111+
body:processedBody,
112+
bodyFormData,
113+
};
114+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp