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>
 

Reply via email to