This is an automated email from the ASF dual-hosted git repository.
hanahmily pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git
The following commit(s) were added to refs/heads/main by this push:
new 25fb9cb8 Add Mesure Editor. Add Stream and Measure editing Entity
functionality (#283)
25fb9cb8 is described below
commit 25fb9cb85a21c04e7e915cd40653897e3e87409f
Author: Wu ChuSheng <[email protected]>
AuthorDate: Wed Jun 21 08:25:06 2023 +0800
Add Mesure Editor. Add Stream and Measure editing Entity functionality
(#283)
* add entity editor
* add measure interval editor
---------
Co-authored-by: Gao Hongtao <[email protected]>
---
ui/src/components/Aside/index.vue | 2 +
ui/src/components/Editor/fieldsEditor.vue | 227 +++++++++++++++++++++
.../components/{StreamEditor => Editor}/index.vue | 144 ++++++++++---
.../{StreamEditor => Editor}/tagEditor.vue | 14 +-
ui/src/views/Measure/createEdit.vue | 4 +-
ui/src/views/Stream/createEdit.vue | 4 +-
6 files changed, 354 insertions(+), 41 deletions(-)
diff --git a/ui/src/components/Aside/index.vue
b/ui/src/components/Aside/index.vue
index de9816b0..4096bf28 100644
--- a/ui/src/components/Aside/index.vue
+++ b/ui/src/components/Aside/index.vue
@@ -175,6 +175,8 @@ function searchGroup() {
// init data
function getGroupLists() {
$loadingCreate()
+ data.showSearch = false
+ data.search = ''
getGroupList()
.then(res => {
if (res.status == 200) {
diff --git a/ui/src/components/Editor/fieldsEditor.vue
b/ui/src/components/Editor/fieldsEditor.vue
new file mode 100644
index 00000000..2e525911
--- /dev/null
+++ b/ui/src/components/Editor/fieldsEditor.vue
@@ -0,0 +1,227 @@
+<!--
+ ~ Licensed to Apache Software Foundation (ASF) under one or more contributor
+ ~ license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright
+ ~ ownership. Apache Software Foundation (ASF) licenses this file to you under
+ ~ the Apache License, Version 2.0 (the "License"); you may
+ ~ not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+-->
+
+<script lang="ts" setup>
+import { reactive } from "@vue/reactivity"
+import type { FormInstance } from 'element-plus'
+import { ref } from 'vue'
+
+const ruleFormRef = ref<FormInstance>()
+const data = reactive({
+ tableData: [],
+ dialogVisible: false,
+ form: {
+ name: '',
+ fieldType: '',
+ encodingMethod: '',
+ compressionMethod: ''
+ },
+ fieldOperator: 'Add',
+ fieldEditIndex: -1
+})
+
+const typeOptions = [
+ {
+ value: 'FIELD_TYPE_UNSPECIFIED',
+ label: 'UNSPECIFIED'
+ },
+ {
+ value: 'FIELD_TYPE_STRING',
+ label: 'STRING'
+ },
+ {
+ value: 'FIELD_TYPE_INT',
+ label: 'INT'
+ },
+ {
+ value: 'FIELD_TYPE_DATA_BINARY',
+ label: 'DATA_BINARY'
+ },
+ {
+ value: 'FIELD_TYPE_FLOAT',
+ label: 'FLOAT'
+ }
+]
+const encodingMethodOptions = [
+ {
+ value: 'ENCODING_METHOD_UNSPECIFIED',
+ label: 'UNSPECIFIED'
+ },
+ {
+ value: 'ENCODING_METHOD_GORILLA',
+ label: 'GORILLA'
+ }
+]
+const compressionMethodOptions = [
+ {
+ value: 'COMPRESSION_METHOD_UNSPECIFIED',
+ label: 'UNSPECIFIED'
+ },
+ {
+ value: 'COMPRESSION_METHOD_ZSTD',
+ label: 'ZSTD'
+ }
+]
+const validateName = (rule: any, value: any, callback: any) => {
+ if (value == '') {
+ callback(new Error('Please input the field name.'))
+ } else {
+ const index = data.tableData.findIndex(item => {
+ return item.name == value
+ })
+ if (index >= 0) {
+ if (data.fieldOperator == 'Edit' && data.fieldEditIndex == index) {
+ return callback()
+ }
+ return callback(new Error('The name is exists'))
+ }
+ callback()
+ }
+}
+const rules = {
+ name: [
+ {
+ required: true, validator: validateName, trigger: 'blur'
+ }
+ ],
+ fieldType: [
+ {
+ required: true, message: 'Please select the field type', trigger:
'blur'
+ }
+ ],
+ encodingMethod: [
+ {
+ required: true, message: 'Please select the encoding method',
trigger: 'blur'
+ }
+ ],
+ compressionMethod: [
+ {
+ required: true, message: 'Please select the compression method',
trigger: 'blur'
+ }
+ ],
+}
+const confirmForm = async (formEl: FormInstance | undefined) => {
+ if (!formEl) return
+ await formEl.validate((valid) => {
+ if (valid) {
+ if (data.fieldOperator == 'Add') {
+ data.tableData.push(data.form)
+ initForm()
+ } else {
+ data.tableData[data.fieldEditIndex] = data.form
+ initForm()
+ }
+ data.dialogVisible = false
+ }
+ })
+}
+function initForm() {
+ data.form = {
+ name: '',
+ fieldType: '',
+ encodingMethod: '',
+ compressionMethod: ''
+ }
+}
+function openAddField() {
+ data.fieldOperator = 'Add'
+ data.dialogVisible = true
+}
+function openEditField(index) {
+ data.form = JSON.parse(JSON.stringify(data.tableData[index]))
+ data.fieldEditIndex = index
+ data.fieldOperator = 'Edit'
+ data.dialogVisible = true
+}
+function deleteTableData(index) {
+ data.tableData.splice(index, 1)
+}
+function getFields() {
+ return data.tableData
+}
+function setFields(value) {
+ data.tableData = value
+}
+defineExpose({
+ getFields,
+ setFields
+})
+</script>
+
+<template>
+ <el-button size="small" type="primary" color="#6E38F7" style="margin-top:
20px;"
+ @click="openAddField">Add Field</el-button>
+ <el-table :data="data.tableData" style="width: 100%; margin-top: 20px;"
border>
+ <el-table-column label="Name" prop="name"></el-table-column>
+ <el-table-column label="Field Type" prop="fieldType"></el-table-column>
+ <el-table-column label="Encoding Method"
prop="encodingMethod"></el-table-column>
+ <el-table-column label="Compression Method"
prop="compressionMethod"></el-table-column>
+ <el-table-column label="Operator">
+ <template #default="scope">
+ <el-button link type="primary"
@click.prevent="openEditField(scope.$index)"
+ style="color: var(--color-main); font-weight:
bold;">Edit</el-button>
+ <el-popconfirm @confirm="deleteTableData(scope.$index)"
title="Are you sure to delete this?">
+ <template #reference>
+ <el-button link type="danger" style="color:
red;font-weight: bold;">Delete</el-button>
+ </template>
+ </el-popconfirm>
+ </template>
+ </el-table-column>
+ </el-table>
+ <el-dialog v-model="data.dialogVisible" :close-on-click-modal="false"
align-center
+ :title="data.fieldOperator == 'Add' ? 'Create Field' : 'Edit Field'"
width="30%">
+ <el-form ref="ruleFormRef" :rules="rules" :model="data.form"
label-width="180" label-position="left">
+ <el-form-item label="Name" prop="name">
+ <el-input v-model="data.form.name" placeholder="Input the
field name"
+ :disabled="data.fieldOperator == 'Edit'"></el-input>
+ </el-form-item>
+ <el-form-item label="Field Type" prop="fieldType">
+ <el-select v-model="data.form.fieldType" default-first-option
:reserve-keyword="false"
+ placeholder="Choose field type" style="width: 100%;">
+ <el-option v-for="item in typeOptions" :key="item.value"
:label="item.label" :value="item.value" />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item label="Encoding Method" prop="encodingMethod">
+ <el-select style="width: 100%"
v-model="data.form.encodingMethod" placeholder="Choose encoding method">
+ <el-option v-for="item in encodingMethodOptions"
:key="item.value" :label="item.label"
+ :value="item.value" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="Compression Method" prop="compressionMethod">
+ <el-select style="width: 100%"
v-model="data.form.compressionMethod"
+ placeholder="Choose compression method">
+ <el-option v-for="item in compressionMethodOptions"
:key="item.value" :label="item.label"
+ :value="item.value" />
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <span class="dialog-footer">
+ <div style="width:100%" class="flex center">
+ <el-button size="small" @click="data.dialogVisible = false;
initForm()">Cancel</el-button>
+ <el-button size="small" type="primary" color="#6E38F7"
@click=" confirmForm(ruleFormRef) ">
+ Confirm
+ </el-button>
+ </div>
+ </span>
+ </el-dialog>
+</template>
+
+
+<style lang="scss" scoped></style>
\ No newline at end of file
diff --git a/ui/src/components/StreamEditor/index.vue
b/ui/src/components/Editor/index.vue
similarity index 64%
rename from ui/src/components/StreamEditor/index.vue
rename to ui/src/components/Editor/index.vue
index 08dc1199..ef91ed46 100644
--- a/ui/src/components/StreamEditor/index.vue
+++ b/ui/src/components/Editor/index.vue
@@ -22,9 +22,10 @@ import { watch, getCurrentInstance } from '@vue/runtime-core'
import { reactive, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router'
import TagEditor from './tagEditor.vue'
+import FieldsEditor from './fieldsEditor.vue'
import type { FormInstance } from 'element-plus'
import { ElMessage } from 'element-plus'
-import { createResources, editResources, getStreamOrMeasureList } from
'@/api/index'
+import { createResources, editResources, getStreamOrMeasureList,
getStreamOrMeasure } from '@/api/index'
const $loadingCreate =
getCurrentInstance().appContext.config.globalProperties.$loadingCreate
const $loadingClose =
getCurrentInstance().appContext.config.globalProperties.$loadingClose
@@ -34,6 +35,7 @@ const router = useRouter()
const route = useRoute()
const ruleFormRef = ref<FormInstance>()
const tagEditorRef = ref()
+const fieldEditorRef = ref()
const rules = {
group: [
{
@@ -52,37 +54,71 @@ const data = reactive({
operator: route.params.operator,
form: {
group: route.params.group,
- name: route.params.group
+ name: route.params.group,
+ interval: 1,
+ intervalUnit: 'ns'
}
})
+const options = [
+ {
+ label: 'ns',
+ value: 'ns'
+ },
+ {
+ label: 'us',
+ value: 'us'
+ },
+ {
+ label: 'µs',
+ value: 'µs'
+ },
+ {
+ label: 'ms',
+ value: 'ms'
+ },
+ {
+ label: 's',
+ value: 's'
+ },
+ {
+ label: 'm',
+ value: 'm'
+ },
+ {
+ label: 'h',
+ value: 'h'
+ },
+ {
+ label: 'd',
+ value: 'd'
+ }
+]
+
watch(() => route, () => {
data.form.group = route.params.group
data.form.name = route.params.name
- data.type = route.params.type
+ data.type = route.params.type + ''
data.operator = route.params.operator
initData()
}, {
immediate: true,
deep: true
-})/* let tableData = [{
- name: 'stream-ids',
- tags: [{
- name: 'start_time',
- type: 'TAG_TYPE_INT',
- indexedOnly: false
- }]
-}, */
+})
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid) => {
if (valid) {
const arr = tagEditorRef.value.getTagFamilies()
const tagFamilies = []
+ const entity = []
arr.forEach(item => {
const index = tagFamilies.findIndex(tagItem => {
return tagItem.name == item.tagFamily
})
+ if (item.entity == true) {
+ entity.push(item.tag)
+ }
if (index >= 0) {
let obj = {
name: item.tag,
@@ -103,16 +139,33 @@ const submit = async (formEl: FormInstance | undefined)
=> {
}
tagFamilies.push(obj)
})
+ if (entity.length == 0) {
+ return ElMessage({
+ message: 'At least one Entity is required',
+ type: "error",
+ duration: 5000
+ })
+ }
const form = {
metadata: {
group: data.form.group,
name: data.form.name
},
- tagFamilies: tagFamilies
+ tagFamilies: tagFamilies,
+ entity: {
+ tagNames: entity
+ }
+ }
+ if (data.type == 'measure') {
+ const fields = fieldEditorRef.value.getFields()
+ form['fields'] = fields
+ form['interval'] = data.form.interval + data.form.intervalUnit
}
$loadingCreate()
+ let params = {}
+ params[data.type + ''] = form
if (data.operator == 'edit' && data.form.group && data.form.name) {
- return editResources('stream', data.form.group,
data.form.name, { stream: form })
+ return editResources(data.type, data.form.group,
data.form.name, params)
.then((res) => {
if (res.status == 200) {
ElMessage({
@@ -129,7 +182,7 @@ const submit = async (formEl: FormInstance | undefined) => {
$loadingClose()
})
}
- createResources('stream', { stream: form })
+ createResources(data.type, params)
.then((res) => {
if (res.status == 200) {
ElMessage({
@@ -150,12 +203,12 @@ const submit = async (formEl: FormInstance | undefined)
=> {
}
function openResourses() {
const route = {
- name: 'stream',
+ name: data.type + '',
params: {
group: data.form.group,
name: data.form.name,
operator: 'read',
- type: 'stream'
+ type: data.type + ''
}
}
router.push(route)
@@ -170,27 +223,44 @@ function openResourses() {
function initData() {
if (data.operator == 'edit' && data.form.group && data.form.name) {
$loadingCreate()
- getStreamOrMeasureList('stream', data.form.group)
+ getStreamOrMeasure(data.type, data.form.group, data.form.name)
.then(res => {
if (res.status == 200) {
- const index = res.data.stream.findIndex(item => {
- return item.metadata.group == data.form.group &&
item.metadata.name == data.form.name
- })
- if (index >= 0) {
- const tagFamilies = res.data.stream[index].tagFamilies
- const arr = []
- tagFamilies.forEach(item => {
- item.tags.forEach(tag => {
- let obj = {
- tagFamily: item.name,
- tag: tag.name,
- type: tag.type,
- indexedOnly: tag.indexedOnly
- }
- arr.push(obj)
+ const tagFamilies = res.data[data.type + ''].tagFamilies
+ const entity = res.data[data.type + ''].entity.tagNames
+ const arr = []
+ tagFamilies.forEach(item => {
+ item.tags.forEach(tag => {
+ let index = entity.findIndex(entityItem => {
+ return entityItem == tag.name
})
+ let obj = {
+ tagFamily: item.name,
+ tag: tag.name,
+ type: tag.type,
+ indexedOnly: tag.indexedOnly,
+ entity: index >= 0 ? true : false
+ }
+ arr.push(obj)
})
- tagEditorRef.value.setTagFamilies(arr)
+ })
+ tagEditorRef.value.setTagFamilies(arr)
+ if (data.type == 'measure') {
+ const fields = res.data[data.type + ''].fields
+ const intervalArr = res.data[data.type +
''].interval.split('')
+ let interval = 0
+ let intervalUnit = ''
+ intervalArr.forEach(char => {
+ let code = char.charCodeAt()
+ if (code >= 48 && code < 58) {
+ interval = interval * 10 + (char - 0)
+ } else {
+ intervalUnit = intervalUnit + char
+ }
+ })
+ data.form.interval = interval
+ data.form.intervalUnit = intervalUnit
+ fieldEditorRef.value.setFields(fields)
}
}
})
@@ -236,8 +306,16 @@ function initData() {
<el-form-item label="name" prop="name">
<el-input clearable v-model="data.form.name"></el-input>
</el-form-item>
+ <el-form-item v-if="data.type == 'measure'" label="interval"
prop="interval">
+ <el-input-number v-model="data.form.interval" min="1" />
+ <el-select v-model="data.form.intervalUnit" style="width:
100px; margin-left: 5px;">
+ <el-option v-for="item in options" :key="item.value"
:label="item.label" :value="item.value" />
+ </el-select>
+ </el-form-item>
</el-form>
<TagEditor ref="tagEditorRef"></TagEditor>
+ <el-divider v-if="data.type == 'measure'" border-style="dashed" />
+ <FieldsEditor ref="fieldEditorRef" v-if="data.type ==
'measure'"></FieldsEditor>
</el-card>
</div>
</template>
diff --git a/ui/src/components/StreamEditor/tagEditor.vue
b/ui/src/components/Editor/tagEditor.vue
similarity index 94%
rename from ui/src/components/StreamEditor/tagEditor.vue
rename to ui/src/components/Editor/tagEditor.vue
index 5b8859d1..a4c9c84b 100644
--- a/ui/src/components/StreamEditor/tagEditor.vue
+++ b/ui/src/components/Editor/tagEditor.vue
@@ -31,7 +31,8 @@ const data = reactive({
tagFamily: '',
tag: '',
type: 'TAG_TYPE_INT',
- indexedOnly: false
+ indexedOnly: false,
+ entity: false
},
tagFamilyOptions: [],
tagOperator: 'Add',
@@ -163,7 +164,8 @@ function initForm() {
tagFamily: '',
tag: '',
type: 'TAG_TYPE_INT',
- indexedOnly: false
+ indexedOnly: false,
+ entity: false
}
}
function addTagFamily() {
@@ -213,12 +215,13 @@ defineExpose({
<template>
<el-button size="small" type="primary" color="#6E38F7" style="margin-top:
20px;"
- @click="openAddTagFamily">Add</el-button>
+ @click="openAddTagFamily">Add Tag</el-button>
<el-table :data="data.tableData" :span-method="objectSpanMethod"
style="width: 100%; margin-top: 20px;" border>
<el-table-column label="Tag Family" prop="tagFamily"></el-table-column>
<el-table-column label="Tag" prop="tag"></el-table-column>
<el-table-column label="Type" prop="type"></el-table-column>
<el-table-column label="IndexedOnly"
prop="indexedOnly"></el-table-column>
+ <el-table-column label="Entity" prop="entity"></el-table-column>
<el-table-column label="Operator">
<template #default="scope">
<el-button link type="primary"
@click.prevent="openEditTagFamily(scope.$index)"
@@ -231,7 +234,7 @@ defineExpose({
</template>
</el-table-column>
</el-table>
- <el-dialog v-model="data.dialogVisible" :close-on-click-modal="false"
align-center title="Create Tag Family"
+ <el-dialog v-model="data.dialogVisible" :close-on-click-modal="false"
align-center :title="data.tagOperator == 'Add' ? 'Create Tag Family' : 'Edit
Tag Family'"
width="30%">
<el-form ref="ruleFormRef" :rules="rules" :model="data.form"
label-width="120" label-position="left">
<el-form-item label="Tag Family" prop="tagFamily">
@@ -253,6 +256,9 @@ defineExpose({
<el-form-item label="IndexedOnly" prop="indexedOnly">
<el-switch v-model="data.form.indexedOnly" />
</el-form-item>
+ <el-form-item label="Entity" prop="entity">
+ <el-switch v-model="data.form.entity" />
+ </el-form-item>
</el-form>
<span class="dialog-footer">
<div style="width:100%" class="flex center">
diff --git a/ui/src/views/Measure/createEdit.vue
b/ui/src/views/Measure/createEdit.vue
index 8d5758ae..1ba16b16 100644
--- a/ui/src/views/Measure/createEdit.vue
+++ b/ui/src/views/Measure/createEdit.vue
@@ -18,12 +18,12 @@
-->
<script setup>
-import MeasureEditor from '@/components/MeasureEditor/index.vue'
+import Editor from '@/components/Editor/index.vue'
</script>
<template>
<div class="about">
- <MeasureEditor></MeasureEditor>
+ <Editor></Editor>
</div>
</template>
diff --git a/ui/src/views/Stream/createEdit.vue
b/ui/src/views/Stream/createEdit.vue
index 9e8710b9..b0d1e3bc 100644
--- a/ui/src/views/Stream/createEdit.vue
+++ b/ui/src/views/Stream/createEdit.vue
@@ -18,12 +18,12 @@
-->
<script setup>
-import StreamEditor from '@/components/StreamEditor/index.vue'
+import Editor from '@/components/Editor/index.vue'
</script>
<template>
<div class="about">
- <StreamEditor></StreamEditor>
+ <Editor></Editor>
</div>
</template>