Skip to content

Commit 66b2f1e

Browse files
committed
feat: create a history based autocomplete input
1 parent 11ab39f commit 66b2f1e

File tree

4 files changed

+178
-35
lines changed

4 files changed

+178
-35
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<template>
2+
<el-autocomplete
3+
v-model="input"
4+
clearable
5+
:fetch-suggestions="querySearch"
6+
@select="handleSelect"
7+
@keyup.enter="handleEnter"
8+
:placeholder="props.placeholder"
9+
>
10+
<template #default="{ item }">
11+
<div style="display: flex; justify-content: space-between; align-items: center;">
12+
<span>{{ item.value }}</span>
13+
<el-icon @click.stop="deleteHistoryItem(item)">
14+
<delete />
15+
</el-icon>
16+
</div>
17+
</template>
18+
</el-autocomplete>
19+
</template>
20+
21+
<script setup lang="ts">
22+
import { ref, defineProps } from 'vue'
23+
import { ElAutocomplete, ElIcon } from 'element-plus'
24+
import { Delete } from '@element-plus/icons-vue'
25+
26+
const props = defineProps({
27+
maxItems: {
28+
type: Number,
29+
default: 10
30+
},
31+
key: {
32+
type: String,
33+
default: 'history'
34+
},
35+
storage: {
36+
type: String,
37+
default: 'localStorage'
38+
},
39+
callback: {
40+
type: Function,
41+
default: () => true
42+
},
43+
placeholder: {
44+
type: String,
45+
default: 'Type something'
46+
}
47+
})
48+
49+
const input = ref('')
50+
const suggestions = ref([])
51+
interface HistoryItem {
52+
value: string
53+
count: number
54+
timestamp: number
55+
}
56+
57+
const querySearch = (queryString: string, cb: any) => {
58+
const results = suggestions.value.filter((item: HistoryItem) => item.value.includes(queryString))
59+
cb(results)
60+
}
61+
62+
const handleSelect = (item: HistoryItem) => {
63+
input.value = item.value
64+
}
65+
66+
const handleEnter = async () => {
67+
if (props.callback) {
68+
const result = await props.callback()
69+
if (!result) {
70+
return
71+
}
72+
}
73+
if (input.value === '') {
74+
return;
75+
}
76+
77+
const history = JSON.parse(getStorage().getItem(props.key) || '[]')
78+
const existingItem = history.find((item: HistoryItem) => item.value === input.value)
79+
80+
if (existingItem) {
81+
existingItem.count++
82+
existingItem.timestamp = Date.now()
83+
} else {
84+
history.push({ value: input.value, count: 1, timestamp: Date.now() })
85+
}
86+
87+
if (history.length > props.maxItems) {
88+
history.sort((a: HistoryItem, b: HistoryItem) => a.count - b.count || a.timestamp - b.timestamp)
89+
history.shift()
90+
}
91+
92+
getStorage().setItem(props.key, JSON.stringify(history))
93+
suggestions.value = history
94+
}
95+
96+
const loadHistory = () => {
97+
suggestions.value = JSON.parse(getStorage().getItem('history') || '[]')
98+
}
99+
100+
const deleteHistoryItem = (item: HistoryItem) => {
101+
const history = JSON.parse(getStorage().getItem(props.key) || '[]')
102+
const updatedHistory = history.filter((historyItem: HistoryItem) => historyItem.value !== item.value)
103+
getStorage().setItem(props.key, JSON.stringify(updatedHistory))
104+
suggestions.value = updatedHistory
105+
}
106+
107+
const getStorage = () => {
108+
switch (props.storage) {
109+
case 'localStorage':
110+
return localStorage
111+
case 'sessionStorage':
112+
return sessionStorage
113+
default:
114+
return localStorage
115+
}
116+
}
117+
118+
loadHistory()
119+
</script>

console/atest-ui/src/views/DataManager.vue

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,23 @@
22
import { ref, watch } from 'vue'
33
import { API } from './net'
44
import { ElMessage } from 'element-plus'
5+
import { Codemirror } from 'vue-codemirror'
6+
import HistoryInput from '../components/HistoryInput.vue'
57
68
const stores = ref([])
79
const kind = ref('')
810
const store = ref('')
911
const sqlQuery = ref('')
1012
const queryResult = ref([])
13+
const queryResultAsJSON= ref('')
1114
const columns = ref([])
1215
const queryTip = ref('')
1316
const databases = ref([])
1417
const tables = ref([])
1518
const currentDatabase = ref('')
1619
const loadingStores = ref(true)
20+
const dataFormat = ref('table')
21+
const dataFormatOptions = ['table', 'json']
1722
1823
const tablesTree = ref([])
1924
watch(store, (s) => {
@@ -78,6 +83,7 @@ const ormDataHandler = (data) => {
7883
tables.value = data.meta.tables
7984
currentDatabase.value = data.meta.currentDatabase
8085
queryResult.value = result
86+
queryResultAsJSON.value = JSON.stringify(result, null, 2)
8187
columns.value = Array.from(cols).sort((a, b) => {
8288
if (a === 'id') return -1;
8389
if (b === 'id') return 1;
@@ -111,10 +117,13 @@ const executeQuery = async () => {
111117
break;
112118
}
113119
114-
API.DataQuery(store.value, kind.value, currentDatabase.value, sqlQuery.value, (data) => {
120+
let success = false
121+
try {
122+
const data = await API.DataQueryAsync(store.value, kind.value, currentDatabase.value, sqlQuery.value);
115123
switch (kind.value) {
116124
case 'atest-store-orm':
117125
ormDataHandler(data)
126+
success = true
118127
break;
119128
case 'atest-store-etcd':
120129
keyValueDataHandler(data)
@@ -129,19 +138,20 @@ const executeQuery = async () => {
129138
type: 'error'
130139
});
131140
}
132-
}, (e) => {
141+
} catch (e: any) {
133142
ElMessage({
134143
showClose: true,
135144
message: e.message,
136145
type: 'error'
137146
});
138-
})
147+
}
148+
return success
139149
}
140150
</script>
141151

142152
<template>
143153
<div>
144-
<el-container style="height: calc(100vh - 45px);">
154+
<el-container style="height: calc(100vh - 50px);">
145155
<el-aside v-if="kind === 'atest-store-orm'">
146156
<el-scrollbar>
147157
<el-select v-model="currentDatabase" placeholder="Select database" @change="queryTables" filterable>
@@ -163,23 +173,30 @@ const executeQuery = async () => {
163173
</el-select>
164174
</el-form-item>
165175
</el-col>
166-
<el-col :span="17">
176+
<el-col :span="16">
167177
<el-form-item>
168-
<el-input v-model="sqlQuery" :placeholder="queryTip" @keyup.enter="executeQuery"></el-input>
178+
<HistoryInput :placeholder="queryTip" :callback="executeQuery" v-model="sqlQuery"/>
169179
</el-form-item>
170180
</el-col>
171181
<el-col :span="2">
172182
<el-form-item>
173183
<el-button type="primary" @click="executeQuery">Execute</el-button>
174184
</el-form-item>
175185
</el-col>
186+
<el-col :span="2">
187+
<el-select v-model="dataFormat" placeholder="Select data format">
188+
<el-option v-for="item in dataFormatOptions" :key="item" :label="item" :value="item"></el-option>
189+
</el-select>
190+
</el-col>
176191
</el-row>
177192
</el-form>
178193
</el-header>
179194
<el-main>
180-
<el-table :data="queryResult">
181-
<el-table-column v-for="col in columns" :key="col" :prop="col" :label="col"></el-table-column>
195+
<el-table :data="queryResult" stripe v-if="dataFormat === 'table'">
196+
<el-table-column v-for="col in columns" :key="col" :prop="col" :label="col" sortable/>
182197
</el-table>
198+
<Codemirror v-else-if="dataFormat === 'json'"
199+
v-model="queryResultAsJSON"/>
183200
</el-main>
184201
</el-container>
185202
</el-container>

console/atest-ui/src/views/TestCase.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -973,7 +973,7 @@ const renameTestCase = (name: string) => {
973973
</div>
974974
</el-header>
975975

976-
<el-main style="padding-left: 5px;">
976+
<el-main style="padding-left: 5px; min-height: 280px;">
977977
<el-tabs v-model="requestActiveTab">
978978
<el-tab-pane name="query" v-if="props.kindName !== 'tRPC' && props.kindName !== 'gRPC'">
979979
<template #label>

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

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
16+
import { ca } from 'element-plus/es/locales.mjs'
1617
import { Cache } from './cache'
1718

1819
async function DefaultResponseProcess(response: any) {
@@ -779,32 +780,38 @@ var SBOM = (callback: (d: any) => void) => {
779780
.then(callback)
780781
}
781782

783+
interface QueryObject {
784+
sql: string
785+
key: string
786+
}
787+
var DataQueryAsync = (store: string, kind: string, currentDatabase: string, query: string) => {
788+
const queryObj = {} as QueryObject;
789+
switch (kind) {
790+
case 'atest-store-orm':
791+
queryObj['sql'] = query;
792+
queryObj['key'] = currentDatabase;
793+
break;
794+
case 'atest-store-etcd':
795+
queryObj['key'] = query;
796+
break;
797+
case 'atest-store-redis':
798+
queryObj['key'] = query;
799+
break;
800+
}
801+
const requestOptions = {
802+
method: 'POST',
803+
headers: {
804+
'X-Store-Name': store,
805+
'X-Database': currentDatabase
806+
},
807+
body: JSON.stringify(queryObj)
808+
}
809+
return fetch(`/api/v1/data/query`, requestOptions)
810+
.then(DefaultResponseProcess)
811+
}
812+
782813
var DataQuery = (store: string, kind: string, currentDatabase: string, query: string, callback: (d: any) => void, errHandler: (d: any) => void) => {
783-
const queryObj = {}
784-
switch (kind) {
785-
case 'atest-store-orm':
786-
queryObj['sql'] = query;
787-
queryObj['key'] = currentDatabase;
788-
break;
789-
case 'atest-store-etcd':
790-
queryObj['key'] = query;
791-
break;
792-
case 'atest-store-redis':
793-
queryObj['key'] = query;
794-
break;
795-
}
796-
const requestOptions = {
797-
method: 'POST',
798-
headers: {
799-
'X-Store-Name': store,
800-
'X-Database': currentDatabase
801-
},
802-
body: JSON.stringify(queryObj)
803-
}
804-
fetch(`/api/v1/data/query`, requestOptions)
805-
.then(DefaultResponseProcess)
806-
.then(callback)
807-
.catch(errHandler)
814+
DataQueryAsync(store, kind, currentDatabase, query).then(callback).catch(errHandler)
808815
}
809816

810817
export const API = {
@@ -819,6 +826,6 @@ export const API = {
819826
FunctionsQuery,
820827
GetSecrets, DeleteSecret, CreateOrUpdateSecret,
821828
GetSuggestedAPIs, GetSwaggers,
822-
ReloadMockServer, GetMockConfig, SBOM, DataQuery,
829+
ReloadMockServer, GetMockConfig, SBOM, DataQuery, DataQueryAsync,
823830
getToken
824831
}

0 commit comments

Comments
 (0)