This is an automated email from the ASF dual-hosted git repository. wusheng pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/skywalking-rocketbot-ui.git
The following commit(s) were added to refs/heads/master by this push: new fa32fdb feat: extend table chart (#459) fa32fdb is described below commit fa32fdb0d925f3bf5568f23e5ded47e86cd2ba13 Author: Qiuxia Fan <fine0...@outlook.com> AuthorDate: Tue Apr 6 08:21:50 2021 +0800 feat: extend table chart (#459) --- src/assets/lang/en.ts | 4 + src/assets/lang/zh.ts | 4 + .../components/dashboard/charts/chart-edit.vue | 250 ++++++++++++--------- .../components/dashboard/charts/chart-table.vue | 94 ++++++-- src/views/components/dashboard/dashboard-comp.vue | 4 +- src/views/components/dashboard/dashboard-item.vue | 18 +- 6 files changed, 242 insertions(+), 132 deletions(-) diff --git a/src/assets/lang/en.ts b/src/assets/lang/en.ts index cafc189..645d8c1 100644 --- a/src/assets/lang/en.ts +++ b/src/assets/lang/en.ts @@ -201,6 +201,10 @@ const m = { keywordsOfContentLogTips: 'Current storage of SkyWalking OAP server does not support this.', instanceAttributes: 'Instance Attributes', value: 'Value', + tableHeader: 'Header Names', + tableValues: 'Table Values', + show: 'Show', + hide: 'Hide', }; export default m; diff --git a/src/assets/lang/zh.ts b/src/assets/lang/zh.ts index bc9c321..f222588 100644 --- a/src/assets/lang/zh.ts +++ b/src/assets/lang/zh.ts @@ -199,6 +199,10 @@ const m = { keywordsOfContentLogTips: 'SkyWalking OAP服务器的当前存储不支持此操作', instanceAttributes: '查看实例属性', value: '数值', + tableHeader: '表头名称', + tableValues: '表值', + show: '展示', + hide: '隐藏', }; export default m; diff --git a/src/views/components/dashboard/charts/chart-edit.vue b/src/views/components/dashboard/charts/chart-edit.vue index e161df1..d7bf985 100755 --- a/src/views/components/dashboard/charts/chart-edit.vue +++ b/src/views/components/dashboard/charts/chart-edit.vue @@ -205,7 +205,6 @@ limitations under the License. --> > <option :value="'DES'">{{ $t('descendOrder') }}</option> <option :value="'ASC'">{{ $t('increaseOrder') }}</option> - <!-- <option :value="''" v-if="isBrowser">{{ $t('defaultOrder') }}</option>--> </select> </div> <div class="flex-h mb-5"> @@ -218,6 +217,27 @@ limitations under the License. --> /> </div> <div class="flex-h mb-5"> + <div class="title grey sm">{{ $t('width') }}:</div> + <input + type="number" + min="1" + max="12" + class="rk-chart-edit-input long" + :value="itemConfig.width" + @change="setItemConfig({ type: 'width', value: $event.target.value })" + /> + </div> + <div class="flex-h mb-5"> + <div class="title grey sm">{{ $t('height') }}:</div> + <input + type="number" + min="1" + class="rk-chart-edit-input long" + :value="itemConfig.height" + @change="setItemConfig({ type: 'height', value: $event.target.value })" + /> + </div> + <div class="flex-h mb-5"> <div class="title grey sm">{{ $t('aggregation') }}:</div> <select class="long" @@ -233,27 +253,34 @@ limitations under the License. --> @change="setItemConfig({ type: 'aggregationNum', value: $event.target.value })" /> </div> - <div class="flex-h mb-5"> - <div class="title grey sm">{{ $t('width') }}:</div> + <div class="flex-h mb-5" v-show="itemConfig.chartType === ChartTypeOptions[3].value"> + <div class="title grey sm">{{ $t('tableHeader') }}:</div> <input - type="number" - min="1" - max="12" + type="text" class="rk-chart-edit-input long" - :value="itemConfig.width" - @change="setItemConfig({ type: 'width', value: $event.target.value })" + placeholder="col-1" + :value="itemConfig.tableHeaderCol1" + @change="setItemConfig({ type: 'tableHeaderCol1', value: $event.target.value })" /> - </div> - <div class="flex-h"> - <div class="title grey sm">{{ $t('height') }}:</div> <input - type="number" - min="1" + type="text" class="rk-chart-edit-input long" - :value="itemConfig.height" - @change="setItemConfig({ type: 'height', value: $event.target.value })" + placeholder="col-2" + :value="itemConfig.tableHeaderCol2" + @change="setItemConfig({ type: 'tableHeaderCol2', value: $event.target.value })" /> </div> + <div class="flex-h mb-5" v-show="itemConfig.chartType === ChartTypeOptions[3].value"> + <div class="title grey sm">{{ $t('tableValues') }}:</div> + <select + class="long" + v-model="itemConfig.showTableValues" + @change="setItemConfig({ type: 'showTableValues', value: $event.target.value })" + > + <option :value="true">{{ $t('show') }}</option> + <option :value="false">{{ $t('hide') }}</option> + </select> + </div> </div> </div> </template> @@ -262,8 +289,7 @@ limitations under the License. --> import Vue from 'vue'; import { State, Getter, Mutation, Action } from 'vuex-class'; import { Component, Prop, Watch } from 'vue-property-decorator'; - - import { TopologyType, ObjectsType } from '../../../../constants/constant'; + import { TopologyType } from '@/constants/constant'; import { EntityType, BrowserEntityType, @@ -306,12 +332,20 @@ limitations under the License. --> private isLabel = false; private isIndependentSelector = false; private nameMetrics = ['sortMetrics', 'readSampledRecords']; - private pageTypes = [TopologyType.TOPOLOGY_ENDPOINT, TopologyType.TOPOLOGY_INSTANCE] as any[]; + private pageTypes = [TopologyType.TOPOLOGY_ENDPOINT, TopologyType.TOPOLOGY_INSTANCE] as string[]; private isChartType = false; private isReadSingleValue = false; private created() { this.itemConfig = this.item; + this.initConfig(); + if (!this.itemConfig.independentSelector || this.pageTypes.includes(this.type)) { + return; + } + this.setItemServices(); + } + + private initConfig() { this.isDatabase = this.pageTypes.includes(this.type) ? false : this.rocketComps.tree[this.rocketComps.group].type === DASHBOARDTYPE.DATABASE @@ -328,10 +362,6 @@ limitations under the License. --> this.isIndependentSelector = this.rocketComps.tree[this.rocketComps.group].type === 'metric' || this.pageTypes.includes(this.type); this.isChartType = ['readMetricsValues', 'readLabeledMetricsValues'].includes(this.itemConfig.queryMetricType); - if (!this.itemConfig.independentSelector || this.pageTypes.includes(this.type)) { - return; - } - this.setItemServices(); } private setItemConfig(params: { type: string; value: string }) { @@ -353,129 +383,137 @@ limitations under the License. --> } } if (params.type === 'metricName') { - this.TYPE_METRICS({ name: params.value }).then((data: Array<{ typeOfMetrics: string }>) => { - if (!data.length) { - return; - } - if (data.length > 1) { - const length = data.filter((d: { typeOfMetrics: string }) => d.typeOfMetrics !== MetricsType.REGULAR_VALUE) - .length; - if (length) { - this.$emit('updateStatus', 'metricType', MetricsType.UNKNOWN); - return; - } - } - const { typeOfMetrics } = data[0]; - this.$emit('updateStatus', 'metricType', typeOfMetrics); - this.queryMetricTypesList = QueryMetricTypes[typeOfMetrics] || []; - this.itemConfig.queryMetricType = this.queryMetricTypesList[0] && this.queryMetricTypesList[0].value; - this.isChartType = ['readMetricsValues', 'readLabeledMetricsValues'].includes( - this.itemConfig.queryMetricType, - ); - this.isLabel = typeOfMetrics === MetricsType.LABELED_VALUE ? true : false; - const values = { - metricType: typeOfMetrics, - queryMetricType: this.itemConfig.queryMetricType, - chartType: MetricChartType[this.itemConfig.queryMetricType], - metricName: params.value, - }; - if (this.type === this.pageTypes[0]) { - this.EDIT_TOPO_ENDPOINT_CONFIG({ - index: this.index, - values, - }); - } else if (this.type === this.pageTypes[1]) { - this.EDIT_TOPO_INSTANCE_CONFIG({ - index: this.index, - values, - }); - } else { - this.EDIT_COMP_CONFIG({ - index: this.index, - values, - }); - } - this.itemConfig = { - ...this.itemConfig, - ...values, - }; - }); + this.updateMetricName(params); return; } if (params.type === 'queryMetricType') { - const values = { - chartType: MetricChartType[params.value], - [params.type]: params.value, - }; + this.updateQueryMetricType(params); + return; + } + if (params.type === 'independentSelector' || params.type === 'parentService') { + this.itemConfig[params.type] = params.value === 'true' ? true : false; if (this.type === this.pageTypes[0]) { this.EDIT_TOPO_ENDPOINT_CONFIG({ index: this.index, - values, + values: { [params.type]: this.itemConfig[params.type] }, }); } else if (this.type === this.pageTypes[1]) { this.EDIT_TOPO_INSTANCE_CONFIG({ index: this.index, - values, + values: { [params.type]: this.itemConfig[params.type] }, }); } else { - this.EDIT_COMP_CONFIG({ - index: this.index, - values, - }); + this.EDIT_COMP_CONFIG({ index: this.index, values: { [params.type]: this.itemConfig[params.type] } }); } - this.itemConfig = { - ...this.itemConfig, - ...values, - }; - this.isChartType = ['readMetricsValues', 'readLabeledMetricsValues'].includes(this.itemConfig.queryMetricType); + } + if (params.type === 'aggregation' && ['milliseconds', 'seconds'].includes(this.itemConfig.aggregation)) { + this.updateAggregation(params); return; } - if (params.type === 'independentSelector' || params.type === 'parentService') { - this.itemConfig[params.type] = params.value === 'true' ? true : false; + if (this.type === this.pageTypes[0]) { + this.EDIT_TOPO_ENDPOINT_CONFIG({ + index: this.index, + values: { [params.type]: params.value }, + }); + } else if (this.type === this.pageTypes[1]) { + this.EDIT_TOPO_INSTANCE_CONFIG({ + index: this.index, + values: { [params.type]: params.value }, + }); + } else { + this.EDIT_COMP_CONFIG({ index: this.index, values: { [params.type]: params.value } }); + } + } + + private updateMetricName(params: { type: string; value: string }) { + this.TYPE_METRICS({ name: params.value }).then((data: Array<{ typeOfMetrics: string }>) => { + if (!data.length) { + return; + } + if (data.length > 1) { + const length = data.filter((d: { typeOfMetrics: string }) => d.typeOfMetrics !== MetricsType.REGULAR_VALUE) + .length; + if (length) { + this.$emit('updateStatus', 'metricType', MetricsType.UNKNOWN); + return; + } + } + const { typeOfMetrics } = data[0]; + this.$emit('updateStatus', 'metricType', typeOfMetrics); + this.queryMetricTypesList = QueryMetricTypes[typeOfMetrics] || []; + this.itemConfig.queryMetricType = this.queryMetricTypesList[0] && this.queryMetricTypesList[0].value; + this.isChartType = ['readMetricsValues', 'readLabeledMetricsValues'].includes(this.itemConfig.queryMetricType); + this.isLabel = typeOfMetrics === MetricsType.LABELED_VALUE ? true : false; + const values = { + metricType: typeOfMetrics, + queryMetricType: this.itemConfig.queryMetricType, + chartType: MetricChartType[this.itemConfig.queryMetricType], + metricName: params.value, + }; if (this.type === this.pageTypes[0]) { this.EDIT_TOPO_ENDPOINT_CONFIG({ index: this.index, - values: { [params.type]: this.itemConfig[params.type] }, + values, }); } else if (this.type === this.pageTypes[1]) { this.EDIT_TOPO_INSTANCE_CONFIG({ index: this.index, - values: { [params.type]: this.itemConfig[params.type] }, + values, }); } else { - this.EDIT_COMP_CONFIG({ index: this.index, values: { [params.type]: this.itemConfig[params.type] } }); + this.EDIT_COMP_CONFIG({ + index: this.index, + values, + }); } - - return; - } - if (params.type === 'aggregation' && ['milliseconds', 'seconds'].includes(this.itemConfig.aggregation)) { - const values = { - aggregationNum: 'YYYY-MM-DD HH:mm:ss', - [params.type]: params.value, - }; this.itemConfig = { ...this.itemConfig, ...values, }; - this.EDIT_COMP_CONFIG({ - index: this.index, - values, - }); - return; - } + }); + } + + private updateAggregation(params: { type: string; value: string }) { + const values = { + aggregationNum: 'YYYY-MM-DD HH:mm:ss', + [params.type]: params.value, + }; + this.itemConfig = { + ...this.itemConfig, + ...values, + }; + this.EDIT_COMP_CONFIG({ + index: this.index, + values, + }); + } + + private updateQueryMetricType(params: { type: string; value: string }) { + const values = { + chartType: MetricChartType[params.value], + [params.type]: params.value, + }; if (this.type === this.pageTypes[0]) { this.EDIT_TOPO_ENDPOINT_CONFIG({ index: this.index, - values: { [params.type]: params.value }, + values, }); } else if (this.type === this.pageTypes[1]) { this.EDIT_TOPO_INSTANCE_CONFIG({ index: this.index, - values: { [params.type]: params.value }, + values, }); } else { - this.EDIT_COMP_CONFIG({ index: this.index, values: { [params.type]: params.value } }); + this.EDIT_COMP_CONFIG({ + index: this.index, + values, + }); } + this.itemConfig = { + ...this.itemConfig, + ...values, + }; + this.isChartType = ['readMetricsValues', 'readLabeledMetricsValues'].includes(this.itemConfig.queryMetricType); } private setItemServices(update: boolean = false) { diff --git a/src/views/components/dashboard/charts/chart-table.vue b/src/views/components/dashboard/charts/chart-table.vue index f7bc4de..856880b 100644 --- a/src/views/components/dashboard/charts/chart-table.vue +++ b/src/views/components/dashboard/charts/chart-table.vue @@ -15,16 +15,21 @@ limitations under the License. --> <template> <div class="rk-chart-table"> - <table> - <tr> - <th>{{ $t('name') }}</th> - <th>{{ $t('value') }}</th> - </tr> - <tr v-for="key in dataKeys" :key="key"> - <td>{{ key }}</td> - <td>{{ data[key][dataLength(data[key])] }}</td> - </tr> - </table> + <div ref="chartTable"> + <div class="row flex-h" :style="`width: ${nameWidth + initWidth}px`"> + <div class="name" :style="`width: ${nameWidth}px`"> + {{ item.tableHeaderCol1 || $t('name') }} + <i class="r cp" ref="draggerName"><rk-icon icon="settings_ethernet"/></i> + </div> + <div class="value-col" v-if="showTableValues"> + {{ item.tableHeaderCol2 || $t('value') }} + </div> + </div> + <div class="row flex-h" v-for="key in dataKeys" :key="key" :style="`width: ${nameWidth + initWidth}px`"> + <div :style="`width: ${nameWidth}px`">{{ key }}</div> + <div class="value-col" v-if="showTableValues">{{ data[key][dataLength(data[key])] }}</div> + </div> + </div> </div> </template> @@ -35,34 +40,77 @@ limitations under the License. --> @Component export default class ChartTable extends Vue { @Prop() private data!: any; + @Prop() private item: any; + + private nameWidth: number = 0; + private initWidth: number = 0; private get dataKeys() { const keys = Object.keys(this.data || {}).filter((i: any) => Array.isArray(this.data[i]) && this.data[i].length); return keys; } - private dataLength(param: any[]) { + private get showTableValues() { + return this.item.showTableValues === 'true' || this.item.showTableValues === true ? true : false; + } + + private mounted() { + const chartTable: any = this.$refs.chartTable; + const width = this.showTableValues ? chartTable.offsetWidth / 2 : chartTable.offsetWidth; + this.initWidth = this.showTableValues ? chartTable.offsetWidth / 2 : 0; + this.nameWidth = width - 5; + const drag: any = this.$refs.draggerName; + drag.onmousedown = (event: MouseEvent) => { + const diffX = event.clientX; + const copy = this.nameWidth; + document.onmousemove = (documentEvent) => { + const moveX = documentEvent.clientX - diffX; + this.nameWidth = copy + moveX; + }; + document.onmouseup = () => { + document.onmousemove = null; + document.onmouseup = null; + }; + }; + } + + private dataLength(param: number[]) { return param.length - 1 || 0; } } </script> <style lang="scss" scoped> .rk-chart-table { - table { - width: 100%; - border-top: 1px solid #ccc; - border-right: 1px solid #ccc; - } - tr { - width: 100%; - border: 1px solid #ccc; + height: 100%; + width: 100%; + overflow: auto; + .name { + padding-left: 15px; } - th, - td { + .row { border-left: 1px solid #ccc; - border-bottom: 1px solid #ccc; + height: 20px; + div { + border-right: 1px solid #ccc; + text-align: center; + height: 20px; + line-height: 20px; + display: inline-block; + } + div:last-child { + border-bottom: 1px solid #ccc; + } + div:nth-last-child(2) { + border-bottom: 1px solid #ccc; + } + } + .row:first-child { + div { + border-top: 1px solid #ccc; + } + } + .value-col { width: 50%; - text-align: center; } } </style> diff --git a/src/views/components/dashboard/dashboard-comp.vue b/src/views/components/dashboard/dashboard-comp.vue index ec697cf..d03d57d 100644 --- a/src/views/components/dashboard/dashboard-comp.vue +++ b/src/views/components/dashboard/dashboard-comp.vue @@ -51,8 +51,8 @@ limitations under the License. --> </template> <script lang="ts"> - import { Vue, Component, Watch, Prop } from 'vue-property-decorator'; - import { State, Mutation } from 'vuex-class'; + import { Vue, Component, Prop } from 'vue-property-decorator'; + import { Mutation } from 'vuex-class'; import copy from '@/utils/copy'; @Component diff --git a/src/views/components/dashboard/dashboard-item.vue b/src/views/components/dashboard/dashboard-item.vue index b9730ef..8df84fc 100644 --- a/src/views/components/dashboard/dashboard-item.vue +++ b/src/views/components/dashboard/dashboard-item.vue @@ -26,9 +26,12 @@ limitations under the License. --> <use xlink:href="#lock"></use> </svg> </span> + <span v-show="!rocketGlobal.edit && itemConfig.chartType === 'ChartTable'" @click="copyTable"> + <rk-icon class="r cp" icon="review-list" /> + </span> </div> <div class="rk-dashboard-item-body"> - <div style="height:100%;"> + <div style="height:100%; width:100%"> <component :is="rocketGlobal.edit ? 'ChartEdit' : itemConfig.chartType" ref="chart" @@ -72,6 +75,7 @@ limitations under the License. --> import { CalculationType } from './charts/constant'; import { State as globalState } from '@/store/modules/global'; import { State as optionState } from '@/store/modules/global/selectors'; + import copy from '@/utils/copy'; @Component({ components: { ...charts }, @@ -313,6 +317,18 @@ limitations under the License. --> } } + private copyTable() { + const data: any = {}; + const keys = Object.keys(this.chartSource || {}).filter( + (i: any) => Array.isArray(this.chartSource[i]) && this.chartSource[i].length, + ); + for (const key of keys) { + const index = this.chartSource[key].length - 1 || 0; + data[key] = this.chartSource[key][index]; + } + copy(JSON.stringify(data)); + } + private deleteItem(index: number) { if (this.type === this.pageTypes[0]) { this.DELETE_TOPO_ENDPOINT(index);