Skip to content

Commit cc5a463

Browse files
authored
add mysql/etcd query support (#624)
* feat: add data query service --------- Co-authored-by: rick <[email protected]>
1 parent 4a38bd2 commit cc5a463

29 files changed

+2657
-1906
lines changed

.gitattributes

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
1-
* text eol=lf
1+
*.go text eol=lf
2+
*.vue text eol=lf
3+
*.js text eol=lf
4+
*.css text eol=lf
5+
*.html text eol=lf
6+
*.md text eol=lf
7+
*.yaml text eol=lf
8+
*.json text eol=lf
9+
*.yml text eol=lf

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ console/atest-desktop/node_modules
1919
console/atest-desktop/atest
2020
console/atest-desktop/atest.exe
2121
console/atest-desktop/coverage
22+
atest-store-git

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ This is an awesome API testing tool. 🚀
2121
* Pre and post handle with the API request
2222
* Run in server mode, and provide the [gRPC](pkg/server/server.proto) and HTTP endpoint
2323
* [VS Code extension](https://github.com/LinuxSuRen/vscode-api-testing) support
24+
* Simple Database query support
2425
* Multiple storage backends supported(Local, ORM Database, S3, Git, Etcd, etc.)
2526
* [HTTP API record](https://github.com/LinuxSuRen/atest-ext-collector)
2627
* Install in multiple use cases(CLI, Container, Native-Service, [Operator](https://github.com/LinuxSuRen/atest-operator), Helm, etc.)

atest-store-git

-19.5 MB
Binary file not shown.

cmd/server.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func createServerCmd(execer fakeruntime.Execer, httpServer server.HTTPServer) (c
102102
flags.StringArrayVarP(&opt.mockConfig, "mock-config", "", nil, "The mock config files")
103103
flags.StringVarP(&opt.mockPrefix, "mock-prefix", "", "/mock", "The mock server API prefix")
104104
flags.StringVarP(&opt.extensionRegistry, "extension-registry", "", "docker.io", "The extension registry URL")
105-
flags.DurationVarP(&opt.downloadTimeout, "download-timeout", "", time.Second*10, "The timeout of extension download")
105+
flags.DurationVarP(&opt.downloadTimeout, "download-timeout", "", time.Minute, "The timeout of extension download")
106106

107107
// gc related flags
108108
flags.IntVarP(&opt.gcPercent, "gc-percent", "", 100, "The GC percent of Go")
@@ -288,6 +288,7 @@ func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) {
288288
}
289289
server.RegisterRunnerServer(s, remoteServer)
290290
server.RegisterMockServer(s, mockServerController)
291+
server.RegisterDataServerServer(s, remoteServer.(server.DataServerServer))
291292
serverLogger.Info("gRPC server listening at", "addr", lis.Addr())
292293
s.Serve(lis)
293294
}()
@@ -322,11 +323,14 @@ func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) {
322323
}
323324
err = errors.Join(
324325
server.RegisterRunnerHandlerFromEndpoint(ctx, mux, gRPCServerAddr, []grpc.DialOption{grpc.WithTransportCredentials(creds)}),
325-
server.RegisterMockHandlerFromEndpoint(ctx, mux, gRPCServerAddr, []grpc.DialOption{grpc.WithTransportCredentials(creds)}))
326+
server.RegisterMockHandlerFromEndpoint(ctx, mux, gRPCServerAddr, []grpc.DialOption{grpc.WithTransportCredentials(creds)}),
327+
server.RegisterDataServerHandlerFromEndpoint(ctx, mux, gRPCServerAddr, []grpc.DialOption{grpc.WithTransportCredentials(creds)}))
326328
} else {
329+
dialOption := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
327330
err = errors.Join(
328-
server.RegisterRunnerHandlerFromEndpoint(ctx, mux, gRPCServerAddr, []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}),
329-
server.RegisterMockHandlerFromEndpoint(ctx, mux, gRPCServerAddr, []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}))
331+
server.RegisterRunnerHandlerFromEndpoint(ctx, mux, gRPCServerAddr, dialOption),
332+
server.RegisterMockHandlerFromEndpoint(ctx, mux, gRPCServerAddr, dialOption),
333+
server.RegisterDataServerHandlerFromEndpoint(ctx, mux, gRPCServerAddr, dialOption))
330334
}
331335

332336
if err == nil {

console/atest-ui/src/App.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
Share,
88
ArrowDown,
99
Guide,
10+
DataAnalysis
1011
} from '@element-plus/icons-vue'
1112
import { ref, watch } from 'vue'
1213
import { API } from './views/net'
@@ -17,6 +18,7 @@ import MockManager from './views/MockManager.vue'
1718
import StoreManager from './views/StoreManager.vue'
1819
import SecretManager from './views/SecretManager.vue'
1920
import WelcomePage from './views/WelcomePage.vue'
21+
import DataManager from './views/DataManager.vue'
2022
import { useI18n } from 'vue-i18n'
2123
2224
const { t, locale: i18nLocale } = useI18n()
@@ -114,6 +116,10 @@ const toHistoryPanel = ({ ID: selectID, panelName: historyPanelName }) => {
114116
<el-icon><Guide /></el-icon>
115117
<template #title>{{ t('title.mock' )}}</template>
116118
</el-menu-item>
119+
<el-menu-item index="data" test-id="data-menu">
120+
<el-icon><DataAnalysis /></el-icon>
121+
<template #title>{{ t('title.data' )}}</template>
122+
</el-menu-item>
117123
<el-menu-item index="secret">
118124
<el-icon><document /></el-icon>
119125
<template #title>{{ t('title.secrets') }}</template>
@@ -142,6 +148,7 @@ const toHistoryPanel = ({ ID: selectID, panelName: historyPanelName }) => {
142148
</div>
143149
<TestingPanel v-if="panelName === 'testing'" @toHistoryPanel="toHistoryPanel"/>
144150
<TestingHistoryPanel v-else-if="panelName === 'history'" :ID="ID"/>
151+
<DataManager v-else-if="panelName === 'data'" />
145152
<MockManager v-else-if="panelName === 'mock'" />
146153
<StoreManager v-else-if="panelName === 'store'" />
147154
<SecretManager v-else-if="panelName === 'secret'" />

console/atest-ui/src/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@
5050
"functionQuery": "Functions Query",
5151
"output": "Output",
5252
"proxy": "Proxy",
53-
"secure": "Secure"
53+
"secure": "Secure",
54+
"data": "Data"
5455
},
5556
"tip": {
5657
"filter": "Filter Keyword",

console/atest-ui/src/locales/zh.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@
4545
"functionQuery": "函数查询",
4646
"output": "输出",
4747
"proxy": "代理",
48-
"secure": "安全"
48+
"secure": "安全",
49+
"data": "数据"
4950
},
5051
"tip": {
5152
"filter": "过滤",
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<script setup lang="ts">
2+
import { ref, watch } from 'vue'
3+
import { API } from './net'
4+
import { ElMessage } from 'element-plus'
5+
6+
const stores = ref([])
7+
const kind = ref('')
8+
const store = ref('')
9+
const sqlQuery = ref('')
10+
const queryResult = ref([])
11+
const columns = ref([])
12+
const queryTip = ref('')
13+
14+
watch(store, (s) => {
15+
stores.value.forEach((e: Store) => {
16+
if (e.name === s) {
17+
kind.value = e.kind.name
18+
return
19+
}
20+
})
21+
})
22+
watch(kind, (k) => {
23+
switch (k) {
24+
case 'atest-store-orm':
25+
sqlQuery.value = 'show tables'
26+
queryTip.value = 'Enter SQL query'
27+
break;
28+
case 'atest-store-etcd':
29+
sqlQuery.value = ''
30+
queryTip.value = 'Enter key'
31+
break;
32+
}
33+
})
34+
35+
API.GetStores((data) => {
36+
stores.value = data.data
37+
}, (e) => {
38+
ElMessage({
39+
showClose: true,
40+
message: e.message,
41+
type: 'error'
42+
});
43+
})
44+
45+
const ormDataHandler = (data) => {
46+
const result = []
47+
const cols = new Set()
48+
49+
data.items.forEach(e => {
50+
const obj = {}
51+
e.data.forEach(item => {
52+
obj[item.key] = item.value
53+
cols.add(item.key)
54+
})
55+
result.push(obj)
56+
})
57+
58+
queryResult.value = result
59+
columns.value = Array.from(cols).sort((a, b) => {
60+
if (a === 'id') return -1;
61+
if (b === 'id') return 1;
62+
return a.localeCompare(b);
63+
})
64+
}
65+
66+
const keyValueDataHandler = (data) => {
67+
queryResult.value = []
68+
data.data.forEach(e => {
69+
const obj = {}
70+
obj['key'] = e.key
71+
obj['value'] = e.value
72+
queryResult.value.push(obj)
73+
74+
columns.value = ['key', 'value']
75+
})
76+
}
77+
78+
const executeQuery = async () => {
79+
API.DataQuery(store.value, kind.value, sqlQuery.value, (data) => {
80+
switch (kind.value) {
81+
case 'atest-store-orm':
82+
ormDataHandler(data)
83+
break;
84+
case 'atest-store-etcd':
85+
keyValueDataHandler(data)
86+
break;
87+
default:
88+
ElMessage({
89+
showClose: true,
90+
message: 'Unsupported store kind',
91+
type: 'error'
92+
});
93+
}
94+
}, (e) => {
95+
ElMessage({
96+
showClose: true,
97+
message: e.message,
98+
type: 'error'
99+
});
100+
})
101+
}
102+
</script>
103+
104+
<template>
105+
<div>
106+
<el-form @submit.prevent="executeQuery">
107+
<el-row :gutter="10">
108+
<el-col :span="2">
109+
<el-form-item>
110+
<el-select v-model="store" placeholder="Select store">
111+
<el-option v-for="item in stores" :key="item.name" :label="item.name"
112+
:value="item.name" :disabled="!item.ready" :kind="item.kind.name"></el-option>
113+
</el-select>
114+
</el-form-item>
115+
</el-col>
116+
<el-col :span="18">
117+
<el-form-item>
118+
<el-input v-model="sqlQuery" :placeholder="queryTip" @keyup.enter="executeQuery"></el-input>
119+
</el-form-item>
120+
</el-col>
121+
<el-col :span="2">
122+
<el-form-item>
123+
<el-button type="primary" @click="executeQuery">Execute</el-button>
124+
</el-form-item>
125+
</el-col>
126+
</el-row>
127+
</el-form>
128+
<el-table :data="queryResult">
129+
<el-table-column v-for="col in columns" :key="col" :prop="col" :label="col"></el-table-column>
130+
</el-table>
131+
</div>
132+
</template>

console/atest-ui/src/views/net.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,29 @@ var SBOM = (callback: (d: any) => void) => {
773773
.then(callback)
774774
}
775775

776+
var DataQuery = (store: string, kind: string, query: string, callback: (d: any) => void, errHandler: (d: any) => void) => {
777+
const queryObj = {}
778+
switch (kind) {
779+
case 'atest-store-orm':
780+
queryObj['sql'] = query;
781+
break;
782+
case 'atest-store-etcd':
783+
queryObj['key'] = query;
784+
break;
785+
}
786+
const requestOptions = {
787+
method: 'POST',
788+
headers: {
789+
'X-Store-Name': store
790+
},
791+
body: JSON.stringify(queryObj)
792+
}
793+
fetch(`/api/v1/data/query`, requestOptions)
794+
.then(DefaultResponseProcess)
795+
.then(callback)
796+
.catch(errHandler)
797+
}
798+
776799
export const API = {
777800
DefaultResponseProcess,
778801
GetVersion,
@@ -785,6 +808,6 @@ export const API = {
785808
FunctionsQuery,
786809
GetSecrets, DeleteSecret, CreateOrUpdateSecret,
787810
GetSuggestedAPIs,
788-
ReloadMockServer, GetMockConfig, SBOM,
811+
ReloadMockServer, GetMockConfig, SBOM, DataQuery,
789812
getToken
790813
}

extensions/README.md

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,34 @@
1-
Ports in extensions:
2-
3-
| Type | Name | Port |
4-
|------|------|------|
5-
| Store | [orm](https://github.com/LinuxSuRen/atest-ext-store-orm) | 4071 |
6-
| Store | [s3](https://github.com/LinuxSuRen/atest-ext-store-s3) | 4072 |
7-
| Store | [etcd](https://github.com/LinuxSuRen/atest-ext-store-etcd) | 4073 |
8-
| Store | [git](https://github.com/LinuxSuRen/atest-ext-store-git) | 4074 |
9-
| Store | [mongodb](https://github.com/LinuxSuRen/atest-ext-store-mongodb) | 4075 |
10-
| Monitor | [docker-monitor](https://github.com/LinuxSuRen/atest-ext-monitor-docker) | |
11-
| Agent | [collector](https://github.com/LinuxSuRen/atest-ext-collector) | |
12-
| Secret | [Vault](https://github.com/LinuxSuRen/api-testing-vault-extension) | |
13-
14-
## Contribute a new extension
15-
16-
* First, create a repository. And please keep the same naming convertion.
17-
* Second, implement the `Loader` gRPC service which defined by [this proto](../pkg/testing/remote/loader.proto).
18-
* Finally, add the extension's name into function [SupportedExtensions](../console/atest-ui/src/views/store.ts).
19-
20-
## Naming conventions
21-
Please follow the following conventions if you want to add a new store extension:
22-
23-
`store-xxx`
24-
25-
`xxx` should be a type of a backend storage.
26-
27-
## Test
28-
29-
First, build and copy the binary file into the system path. You can run the following
30-
command in the root directory of this project:
31-
32-
```shell
33-
make build-ext-etcd copy-ext
34-
```
1+
Ports in extensions:
2+
3+
| Type | Name | Port |
4+
|------|------|------|
5+
| Store | [orm](https://github.com/LinuxSuRen/atest-ext-store-orm) | 4071 |
6+
| Store | [s3](https://github.com/LinuxSuRen/atest-ext-store-s3) | 4072 |
7+
| Store | [etcd](https://github.com/LinuxSuRen/atest-ext-store-etcd) | 4073 |
8+
| Store | [git](https://github.com/LinuxSuRen/atest-ext-store-git) | 4074 |
9+
| Store | [mongodb](https://github.com/LinuxSuRen/atest-ext-store-mongodb) | 4075 |
10+
| Monitor | [docker-monitor](https://github.com/LinuxSuRen/atest-ext-monitor-docker) | |
11+
| Agent | [collector](https://github.com/LinuxSuRen/atest-ext-collector) | |
12+
| Secret | [Vault](https://github.com/LinuxSuRen/api-testing-vault-extension) | |
13+
14+
## Contribute a new extension
15+
16+
* First, create a repository. And please keep the same naming convertion.
17+
* Second, implement the `Loader` gRPC service which defined by [this proto](../pkg/testing/remote/loader.proto).
18+
* Finally, add the extension's name into function [SupportedExtensions](../console/atest-ui/src/views/store.ts).
19+
20+
## Naming conventions
21+
Please follow the following conventions if you want to add a new store extension:
22+
23+
`store-xxx`
24+
25+
`xxx` should be a type of a backend storage.
26+
27+
## Test
28+
29+
First, build and copy the binary file into the system path. You can run the following
30+
command in the root directory of this project:
31+
32+
```shell
33+
make build-ext-etcd copy-ext
34+
```

pkg/mock/in_memory.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,10 +453,15 @@ func runWebhook(ctx context.Context, objCtx interface{}, wh *Webhook) (err error
453453
req.Header.Set(k, v)
454454
}
455455

456+
memLogger.Info("send webhook request", "api", api)
456457
resp, err := client.Do(req)
457458
if err != nil {
458459
err = fmt.Errorf("error when sending webhook")
459460
} else {
461+
if resp.StatusCode != http.StatusOK {
462+
memLogger.Info("unexpected status", "code", resp.StatusCode)
463+
}
464+
460465
data, _ := io.ReadAll(resp.Body)
461466
memLogger.V(7).Info("received from webhook", "code", resp.StatusCode, "response", string(data))
462467
}

0 commit comments

Comments
 (0)