Skip to content

Commit b45d3d6

Browse files
authored
Merge pull request #6 from CovenantSQL/dev
Add client api request page for each project
2 parents 0baebef + 6ad54c8 commit b45d3d6

5 files changed

Lines changed: 441 additions & 1 deletion

File tree

src/models/app.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,19 @@ export default {
243243
route: `/project/${p.project}/db`,
244244
}
245245
routesToAppend.push(dbRoute)
246+
247+
let reqRoute = {
248+
id: `10${p.id}2`,
249+
breadcrumbParentId: `10${p.id}`,
250+
menuParentId: `10${p.id}`,
251+
name: 'Request',
252+
zh: {
253+
name: 'Request',
254+
},
255+
icon: 'api',
256+
route: `/project/${p.project}/request`,
257+
}
258+
routesToAppend.push(reqRoute)
246259
})
247260

248261
yield put({ type: 'updateRoutes', payload: { append: routesToAppend } })

src/pages/project/$db/request.js

Lines changed: 377 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,377 @@
1+
import React from 'react'
2+
import { request } from 'utils'
3+
import { connect } from 'dva'
4+
import _get from 'lodash/get'
5+
import {
6+
Row,
7+
Col,
8+
Select,
9+
Input,
10+
Button,
11+
List,
12+
Tag,
13+
Form,
14+
Icon,
15+
Checkbox,
16+
notification,
17+
} from 'antd'
18+
import classnames from 'classnames'
19+
import CodeMirror from 'react-codemirror'
20+
import { Trans } from '@lingui/react'
21+
import { CLIENT_API } from 'utils/constant'
22+
import api from '@/services/api'
23+
import { Page } from 'components'
24+
25+
import 'codemirror/mode/javascript/javascript'
26+
import 'codemirror/lib/codemirror.css'
27+
import 'codemirror/theme/monokai.css'
28+
29+
import styles from './request.less'
30+
31+
const { Option } = Select
32+
const InputGroup = Input.Group
33+
const methods = ['POST', 'GET', 'PUT', 'PATCH', 'DELETE']
34+
35+
const methodTagColor = {
36+
GET: 'green',
37+
POST: 'orange',
38+
DELETE: 'red',
39+
PUT: 'geekblue',
40+
}
41+
42+
// for local test
43+
const apiPrefix = '/v3'
44+
45+
const requests = Object.values(CLIENT_API).map(item => {
46+
let url = apiPrefix + item
47+
let method = 'GET'
48+
const paramsArray = item.split(' ')
49+
if (paramsArray.length === 2) {
50+
method = paramsArray[0]
51+
url = apiPrefix + paramsArray[1]
52+
}
53+
return {
54+
method,
55+
url,
56+
}
57+
})
58+
59+
let uuid = 2
60+
@Form.create()
61+
@connect(({ projectDetail }) => ({ config: projectDetail.config }))
62+
class RequestPage extends React.Component {
63+
constructor(props) {
64+
super(props)
65+
this.state = {
66+
method: 'GET',
67+
url: apiPrefix + CLIENT_API.find,
68+
keys: [1],
69+
result: null,
70+
visible: true,
71+
code: '',
72+
}
73+
}
74+
75+
constructAPIDomain = () => {
76+
const alias = _get(this.props.config, ['misc', 'alias'], '')
77+
return '//' + alias + '.stg-api.covenantsql.io:15153'
78+
}
79+
80+
constructTableSelection = () => {
81+
const tables = _get(this.props.config, ['tables'], [])
82+
return (
83+
<Select placeholder="Select a table" style={{ minWidth: '160px' }}>
84+
{tables.map(t => {
85+
if (!t.config.is_deleted) {
86+
return (
87+
<Select.Option key={t.table} value={t.table}>
88+
{t.table}
89+
</Select.Option>
90+
)
91+
}
92+
})}
93+
</Select>
94+
)
95+
}
96+
97+
handleRequest = () => {
98+
const { method } = this.state
99+
100+
// prefix the this.constructAPIDomain()
101+
let url = this.constructAPIDomain() + this.state.url
102+
103+
this.props.form.validateFields((err, values) => {
104+
if (!err) {
105+
const params = {}
106+
107+
if (this.state.url.indexOf(':table') > -1) {
108+
const { table_value, data_value } = values
109+
params.table = table_value
110+
try {
111+
let d = data_value.replace(/[\n\r]+/g, '')
112+
params.data = data_value && JSON.parse(d)
113+
} catch (e) {
114+
notification.error({
115+
message: 'Table change `data` JSON parse error',
116+
duration: 10,
117+
})
118+
return
119+
}
120+
} else {
121+
if (values.key) {
122+
values.key.forEach((item, index) => {
123+
if (item && values.check[index]) {
124+
params[item] = values.value[index]
125+
}
126+
})
127+
}
128+
}
129+
130+
console.log(params)
131+
request({ method, url, data: params })
132+
.then(data => {
133+
this.setState({
134+
result: JSON.stringify(data),
135+
})
136+
})
137+
.catch(e => {
138+
notification.error({
139+
message: e.message,
140+
duration: 10,
141+
})
142+
})
143+
} else {
144+
notification.error({
145+
message: 'Please input required fields',
146+
description: '',
147+
})
148+
}
149+
})
150+
}
151+
152+
handleClickListItem = ({ method, url }) => {
153+
this.setState({
154+
method,
155+
url,
156+
keys: [uuid++],
157+
result: null,
158+
})
159+
}
160+
161+
handleInputChange = e => {
162+
this.setState({
163+
url: e.target.value,
164+
})
165+
}
166+
167+
handleSelectChange = method => {
168+
this.setState({
169+
method,
170+
})
171+
}
172+
173+
handleAddField = () => {
174+
const { keys } = this.state
175+
const nextKeys = keys.concat(uuid)
176+
uuid++
177+
this.setState({
178+
keys: nextKeys,
179+
})
180+
}
181+
182+
handleRemoveField = key => {
183+
const { keys } = this.state
184+
this.setState({
185+
keys: keys.filter(item => item !== key),
186+
})
187+
}
188+
189+
handleVisible = () => {
190+
this.setState({
191+
visible: !this.state.visible,
192+
})
193+
}
194+
195+
onCodeMirrorChange = v => {
196+
this.setState({
197+
code: v,
198+
})
199+
}
200+
201+
render() {
202+
const { result, url, method, keys, visible } = this.state
203+
const { getFieldDecorator } = this.props.form
204+
205+
return (
206+
<Page inner>
207+
<div style={{ padding: '10px 10px 20px' }}>
208+
Client API domain:{' '}
209+
<Tag color="geekblue">{this.constructAPIDomain()}</Tag>
210+
</div>
211+
<Row>
212+
<Col lg={8} md={24}>
213+
<List
214+
className={styles.requestList}
215+
dataSource={requests}
216+
renderItem={item => (
217+
<List.Item
218+
className={classnames(styles.listItem, {
219+
[styles.lstItemActive]:
220+
item.method === method && item.url === url,
221+
})}
222+
onClick={this.handleClickListItem.bind(this, item)}
223+
>
224+
<span style={{ width: 72 }}>
225+
<Tag
226+
style={{ marginRight: 8 }}
227+
color={methodTagColor[item.method]}
228+
>
229+
{item.method}
230+
</Tag>
231+
</span>
232+
{item.url}
233+
</List.Item>
234+
)}
235+
/>
236+
</Col>
237+
<Col lg={16} md={24}>
238+
<Row type="flex" justify="space-between">
239+
<InputGroup compact size="large" style={{ flex: 1 }}>
240+
<Select
241+
size="large"
242+
value={method}
243+
style={{ width: 100 }}
244+
onChange={this.handleSelectChange}
245+
>
246+
{methods.map(item => (
247+
<Option value={item} key={item}>
248+
{item}
249+
</Option>
250+
))}
251+
</Select>
252+
<Input
253+
value={url}
254+
onChange={this.handleInputChange}
255+
style={{ width: 'calc(100% - 200px)' }}
256+
/>
257+
<Button
258+
ghost={visible}
259+
type={visible ? 'primary' : ''}
260+
onClick={this.handleVisible}
261+
size="large"
262+
>
263+
<Trans>Params</Trans>
264+
</Button>
265+
</InputGroup>
266+
267+
<Button
268+
size="large"
269+
type="primary"
270+
style={{ width: 100 }}
271+
onClick={this.handleRequest}
272+
>
273+
<Trans>Send</Trans>
274+
</Button>
275+
</Row>
276+
277+
<div
278+
className={classnames(styles.paramsBlock, {
279+
[styles.hideParams]: !visible || url.indexOf(':table') > -1,
280+
})}
281+
>
282+
{keys.map((key, index) => (
283+
<Row
284+
gutter={8}
285+
type="flex"
286+
justify="start"
287+
align="middle"
288+
key={key}
289+
>
290+
<Col style={{ marginTop: 8 }}>
291+
{getFieldDecorator(`check[${key}]`, {
292+
initialValue: true,
293+
})(<Checkbox defaultChecked />)}
294+
</Col>
295+
<Col style={{ marginTop: 8 }}>
296+
{getFieldDecorator(`key[${key}]`)(
297+
<Input placeholder="Key" />
298+
)}
299+
</Col>
300+
<Col style={{ marginTop: 8 }}>
301+
{getFieldDecorator(`value[${key}]`)(
302+
<Input placeholder="Value" />
303+
)}
304+
</Col>
305+
<Col style={{ marginTop: 8 }}>
306+
<Icon
307+
onClick={this.handleRemoveField.bind(this, key)}
308+
style={{ cursor: 'pointer' }}
309+
type="close"
310+
theme="outlined"
311+
/>
312+
</Col>
313+
</Row>
314+
))}
315+
316+
<Row style={{ marginTop: 8 }}>
317+
<Button onClick={this.handleAddField}>
318+
<Trans>Add Param</Trans>
319+
</Button>
320+
</Row>
321+
</div>
322+
323+
{url.indexOf(':table') > -1 && (
324+
<div>
325+
<Row gutter={8} type="flex" justify="start" align="middle">
326+
<Col style={{ marginTop: 8 }}>
327+
{getFieldDecorator(`check[table]`, {
328+
initialValue: true,
329+
})(<Checkbox defaultChecked disabled />)}
330+
</Col>
331+
<Col style={{ marginTop: 8 }}>
332+
{getFieldDecorator(`key[table]`)(
333+
<Input placeholder="table" disabled />
334+
)}
335+
</Col>
336+
<Col style={{ marginTop: 8 }}>
337+
{getFieldDecorator(`table_value`, {
338+
rules: [
339+
{
340+
required: true,
341+
message: 'Please select a table',
342+
},
343+
],
344+
})(this.constructTableSelection())}
345+
</Col>
346+
</Row>
347+
<Row gutter={8}>
348+
<div style={{ padding: '15px 10px 5px', fontWeight: '600' }}>
349+
Data:
350+
</div>
351+
{getFieldDecorator('data_value')(
352+
<CodeMirror
353+
options={{
354+
lineNumbers: true,
355+
matchBrackets: true,
356+
autoCloseBrackets: true,
357+
mode: 'application/ld+json',
358+
lineWrapping: true,
359+
theme: 'monokai',
360+
}}
361+
/>
362+
)}
363+
</Row>
364+
</div>
365+
)}
366+
367+
<div className={styles.result}>
368+
<pre>{JSON.stringify(JSON.parse(result), null, 2)}</pre>
369+
</div>
370+
</Col>
371+
</Row>
372+
</Page>
373+
)
374+
}
375+
}
376+
377+
export default RequestPage

0 commit comments

Comments
 (0)