KYLIN-5813,improve query history page filter item and model list page button text
diff --git a/kystudio/src/components/common/DropdownFilter/DropdownFilter.vue b/kystudio/src/components/common/DropdownFilter/DropdownFilter.vue
index e6c8c9a..99fbb85 100644
--- a/kystudio/src/components/common/DropdownFilter/DropdownFilter.vue
+++ b/kystudio/src/components/common/DropdownFilter/DropdownFilter.vue
@@ -37,26 +37,56 @@
type: Array,
default: () => []
},
+ options2: {
+ type: Array,
+ default: () => []
+ },
hideArrow: {
type: Boolean
+ },
+ isShowfooter: {
+ type: Boolean,
+ default: true
+ },
+ filterScrollMaxHeight: {
+ type: String,
+ default: '130'
+ },
+ isLoadingData: Boolean,
+ isLoading: Boolean,
+ loadingTips: String,
+ isShowSearchInput: Boolean,
+ filterPlaceholder: String,
+ optionsTitle: String,
+ totalSizeLabel: String,
+ isShowDropDownImme: Boolean,
+ isSelectAllOption: {
+ type: Object,
+ default: null
}
},
locales
})
export default class DropdownFilter extends Vue {
isShowDropDown = false
+ startSec = 0
+ endSec = 10
+ searchValue = ''
+ isAll = false
+ timer = null
get resetValue () {
const { type } = this
switch (type) {
case 'checkbox': return []
+ case 'inputNumber': return [null, null]
default: return null
}
}
get isPopoverType () {
const { type } = this
- return ['checkbox'].includes(type)
+ return ['checkbox', 'inputNumber'].includes(type)
}
get isDatePickerType () {
@@ -84,6 +114,27 @@
}
}
+ handleChangeAll () {
+ this.$emit('handleChangeAll')
+ }
+ saveLatencyRange () {
+ const latencyFrom = this.startSec
+ let latencyTo = null
+ if (this.startSec > this.endSec) {
+ latencyTo = this.endSec = this.startSec
+ } else {
+ latencyTo = this.endSec
+ }
+ this.handleInput([latencyFrom, latencyTo])
+ this.handleToggleDropdown()
+ }
+ resetLatency () {
+ this.startSec = 0
+ this.endSec = 10
+ this.handleClearValue()
+ this.handleToggleDropdown()
+ }
+
handleClearValue () {
this.$emit('input', this.resetValue)
}
@@ -97,28 +148,76 @@
handleToggleDropdown () {
this.handleSetDropdown(!this.isShowDropDown)
+ if (this.isShowDropDown) {
+ this.bindScrollEvent()
+ }
+ }
+
+ removeScrollEvent () {
+ const groupBlocks = this.$refs.$checkBoxGroup.querySelectorAll('.group-block .scroll-content')
+ if (groupBlocks.length) {
+ for (let group of groupBlocks) {
+ group.removeEventListener('scroll', this.addScrollEvent, false)
+ }
+ }
+ }
+ bindScrollEvent () {
+ const groupBlocks = this.$refs.$checkBoxGroup.querySelectorAll('.group-block .scroll-content')
+ console.log(groupBlocks)
+ if (groupBlocks.length) {
+ for (let group of groupBlocks) {
+ group.addEventListener('scroll', this.addScrollEvent, false)
+ }
+ }
}
handlerClickEvent () {
this.$refs.$datePicker.$el.click()
}
+ addScrollEvent (e) {
+ try {
+ const scrollT = e.target.scrollTop
+ if (scrollT > 0) {
+ e.target.parentNode.className = 'group-block is-scrollable-top'
+ } else {
+ e.target.parentNode.className = 'group-block'
+ }
+ let scrollH = e.target.scrollHeight
+ let clientH = e.target.clientHeight
+ if (scrollT + clientH === scrollH) {
+ this.$emit('filter-scroll-bottom')
+ }
+ } catch (e) {
+ console.error(e)
+ }
+ }
+
mounted () {
this.isDatePickerType && this.$slots.default && this.$slots.default.length && this.$slots.default[0].elm.addEventListener('click', this.handlerClickEvent)
+ if (this.isShowDropDownImme) {
+ this.$nextTick(() => {
+ this.handleSetDropdown(true)
+ })
+ }
}
beforeDestroy () {
if (this.isDatePickerType) {
this.$slots.default && this.$slots.default.length && this.$slots.default[0].elm.removeEventListener('click', this.handlerClickEvent)
}
+ if (this.isPopoverType) {
+ this.removeScrollEvent()
+ }
}
- renderCheckboxGroup (h) {
- const { value, options } = this
+ renderCheckboxGroup2 (h) {
+ const { options2, optionsTitle2 } = this
return (
- <el-checkbox-group value={value} onInput={this.handleInput}>
- {options.filter(o => {
+ <div>
+ {optionsTitle2 && <div class="group-title">{ optionsTitle2 }</div>}
+ {options2.filter(o => {
return !o.unavailable
}).map(option => (
<el-checkbox
@@ -128,7 +227,92 @@
{option.renderLabel ? option.renderLabel(h, option) : option.label}
</el-checkbox>
))}
- </el-checkbox-group>
+ <div class="bottom-line"></div>
+ </div>
+ )
+ }
+
+ renderCheckboxGroup (h) {
+ const { value, options, optionsTitle, options2, isSelectAllOption, isLoadingData, isLoading, loadingTips, filterScrollMaxHeight, totalSizeLabel, searchValue } = this
+ return (
+ <div class="filter-content" ref="$checkBoxGroup">
+ {searchValue && (<div class="tatol-size">{ totalSizeLabel }</div>) }
+ <el-checkbox-group value={value} onInput={this.handleInput}>
+ {options2.length > 0 && this.renderCheckboxGroup2(h)}
+ {optionsTitle && <div class="group-title">{ optionsTitle }</div>}
+ {isSelectAllOption && (
+ <div class="select-all-block">
+ <el-checkbox
+ class="select-all-checkbox"
+ indeterminate={isSelectAllOption.indeterminate}
+ onChange={this.handleChangeAll}
+ key={isSelectAllOption.value}
+ label={isSelectAllOption.value}>
+ {isSelectAllOption.renderLabel ? isSelectAllOption.renderLabel(h, isSelectAllOption) : isSelectAllOption.label}
+ </el-checkbox>
+ <span class="select-num-tips">{isSelectAllOption.selectedSize}/{isSelectAllOption.totalSize}</span>
+ </div>
+ )}
+ <div class="group-block">
+ <div class="scroll-content" style={{'max-height': filterScrollMaxHeight + 'px'}}>
+ {options.filter(o => {
+ return !o.unavailable
+ }).map(option => (
+ <el-checkbox
+ class="dropdown-filter-checkbox"
+ key={option.value}
+ label={option.value}>
+ {option.renderLabel ? option.renderLabel(h, option) : option.label}
+ </el-checkbox>
+ ))}
+ {isLoadingData && (
+ <div class="loading-block">
+ {isLoading && <i class="el-ksd-n-icon-spinner-outlined"></i>}
+ {loadingTips && <span class="loading-tips">{ loadingTips }</span>}
+ </div>
+ )}
+ </div>
+ </div>
+ </el-checkbox-group>
+ </div>
+ )
+ }
+
+ renderNoData (h) {
+ const isShowImage = false
+ return (
+ <kylin-empty-data size="small" showImage={isShowImage} content={this.$t('kylinLang.common.noResults')}/>
+ )
+ }
+
+ renderInputNumber (h) {
+ const { value } = this
+ if (value[0] && value[1]) {
+ this.startSec = value[0]
+ this.endSec = value[1]
+ }
+ return (
+ <div class="latency-filter">
+ <div class="latency-filter-pop">
+ <el-input-number
+ size="small"
+ min={0}
+ value={this.startSec}
+ onInput={val1 => (this.startSec = val1)}></el-input-number>
+ <span> S To</span>
+ <el-input-number
+ size="small"
+ min={this.startSec}
+ class="ksd-ml-10"
+ value={this.endSec}
+ onInput={val2 => (this.endSec = val2)}></el-input-number>
+ <span> S</span>
+ </div>
+ <div class="latency-filter-footer">
+ <el-button size="small" onClick={this.resetLatency}>{this.$t('kylinLang.query.clear')}</el-button>
+ <el-button type="primary" onClick={this.saveLatencyRange} size="small">{this.$t('kylinLang.common.save')}</el-button>
+ </div>
+ </div>
)
}
@@ -154,19 +338,32 @@
}
renderFilterInput (h) {
- const { type } = this
+ const { type, options, options2 } = this
switch (type) {
- case 'checkbox': return this.renderCheckboxGroup(h)
+ case 'checkbox': return [...options, ...options2].length ? this.renderCheckboxGroup(h) : this.renderNoData(h)
+ case 'inputNumber': return this.renderInputNumber(h)
default: return null
}
}
+ filterFilters (v) {
+ clearTimeout(this.timer)
+ this.timer = setTimeout(() => {
+ this.searchValue = v
+ this.$emit('filterFilters', v)
+ setTimeout(() => {
+ this.bindScrollEvent()
+ }, 500)
+ }, 400)
+ }
+
renderPopover (h) {
- const { value, placement, width, trigger, isShowDropDown, hideArrow } = this
+ const { value, placement, width, trigger, isShowDropDown, hideArrow, isShowfooter, isShowSearchInput, filterPlaceholder, searchValue } = this
return (
<el-popover
popper-class="dropdown-filter-popper"
+ visible-arrow={!hideArrow}
placement={placement}
width={width}
trigger={trigger}
@@ -176,14 +373,26 @@
{this.$slots.default ? this.$slots.default : value}
{!hideArrow && <i class={['el-icon-arrow-up', isShowDropDown && 'reverse']} />}
</div>
+ {isShowSearchInput && (
+ <div class="search-input">
+ <el-input
+ placeholder={filterPlaceholder}
+ onInput={v => this.filterFilters(v)}
+ value={searchValue}>
+ <i slot="prefix" class="el-input__icon el-icon-search"></i>
+ </el-input>
+ </div>
+ )}
<div class="body">
{this.renderFilterInput(h)}
</div>
- <div class="footer">
- <el-button text type="primary" disabled={!value.length} onClick={this.handleClearValue}>
- {this.$t('clearSelectItems')}
- </el-button>
- </div>
+ {isShowfooter && (
+ <div class="footer">
+ <el-button text type="primary" disabled={!value.length} onClick={this.handleClearValue}>
+ {this.$t('clearSelectItems')}
+ </el-button>
+ </div>
+ )}
</el-popover>
)
}
@@ -219,6 +428,11 @@
.dropdown-filter {
display: inline-block;
font-size: 12px;
+ .el-button--medium .button-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 200px;
+ }
.filter-label {
display: inline-block;
}
@@ -227,15 +441,7 @@
position: relative;
cursor: pointer;
color: @color-text-primary;
- // &:hover,
- // &:hover i {
- // color: @color-primary;
- // }
}
- // .filter-value i {
- // margin-left: 5px;
- // color: #989898;
- // }
.el-icon-arrow-up {
transform: rotate(180deg);
}
@@ -267,20 +473,118 @@
padding: 0;
width: unset !important;
min-width: unset;
+ .search-input {
+ height: 34px;
+ padding: 8px 0;
+ border-bottom: 1px solid @ke-border-divider-color;
+ .el-input__inner {
+ border: none;
+ &:active,
+ &:focus {
+ border: none !important;
+ box-shadow: none !important;
+ }
+ }
+ }
.body {
padding: 10px;
+ position: relative;
+ min-height: 22px;
+ .tatol-size {
+ font-size: 12px;
+ line-height: 18px;
+ text-align: center;
+ margin-bottom: 8px;
+ color: @text-disabled-color;
+ }
+ .bottom-line {
+ border-top: 1px solid @ke-border-divider-color;
+ margin: 0 -10px 12px;
+ }
+ .group-title {
+ font-size: 12px;
+ line-height: 18px;
+ color: @text-disabled-color;
+ margin-bottom: 8px;
+ }
+ .group-block {
+ position: relative;
+ .scroll-content {
+ max-height: 180px;
+ overflow: auto;
+ }
+ &.is-scrollable-top {
+ &::before {
+ content: none;
+ }
+ &::after {
+ content: ' ';
+ position: absolute;
+ top: 0;
+ left: -10px;
+ right: -10px;
+ height: 10px;
+ background: linear-gradient(180deg, rgba(230, 235, 244, 0.8) 0%, rgba(230, 235, 244, 0) 100%);
+ }
+ }
+ }
+ .loading-block {
+ height: 18px;
+ margin-top: 8px;
+ text-align: center;
+ color: @text-placeholder-color;
+ .el-ksd-n-icon-spinner-outlined {
+ font-size: 14px;
+ }
+ .loading-tips {
+ font-size: 12px;
+ line-height: 18px;
+ position: relative;
+ &::after,
+ &::before {
+ content: "";
+ position: absolute;
+ top: 50%;
+ background: @text-placeholder-color;
+ height: 1px;
+ width: 28px;
+ }
+ &::after {
+ right: -36px;
+ }
+ &::before {
+ left: -36px;
+ }
+ }
+ }
}
.footer {
- padding: 0 10px 10px 10px;
+ padding: 12px 10px;
+ border-top: 1px solid @ke-border-divider-color;
}
.el-checkbox {
display: flex;
&:not(:last-child) {
- margin-bottom: 10px;
+ margin-bottom: 8px;
}
.el-checkbox__label {
font-size: 12px;
}
+ .select-all-checkbox {
+ margin-bottom: 4px;
+ }
+ }
+ .select-all-block {
+ display: flex;
+ .select-num-tips {
+ height: 22px;
+ width: 100%;
+ display: inline-block;
+ font-size: 12px;
+ line-height: 18px;
+ text-align: right;
+ color: @text-placeholder-color;
+ }
}
.el-checkbox + .el-checkbox {
margin-left: 0;
diff --git a/kystudio/src/components/query/query_history.vue b/kystudio/src/components/query/query_history.vue
index 5b044e3..c3e0850 100644
--- a/kystudio/src/components/query/query_history.vue
+++ b/kystudio/src/components/query/query_history.vue
@@ -145,6 +145,7 @@
latencyFrom: null,
latencyTo: null,
realization: [],
+ exclude_realization: [],
submitter: [],
server: '',
sql: '',
@@ -183,6 +184,7 @@
latency_from: this.filterData.latencyFrom === null ? '' : this.filterData.latencyFrom,
latency_to: this.filterData.latencyTo === null ? '' : this.filterData.latencyTo,
realization: this.filterData.realization.join(','),
+ exclude_realization: this.filterData.exclude_realization.join(','),
submitter: this.filterData.submitter.join(','),
server: this.filterData.server,
sql: this.filterData.sql,
@@ -220,6 +222,7 @@
latency_from: this.filterData.latencyFrom,
latency_to: this.filterData.latencyTo,
realization: this.filterData.realization,
+ exclude_realization: this.filterData.exclude_realization,
submitter: this.filterData.submitter,
server: this.filterData.server,
sql: this.filterData.sql,
diff --git a/kystudio/src/components/query/query_history_table.vue b/kystudio/src/components/query/query_history_table.vue
index a7bcb42..21e5533 100644
--- a/kystudio/src/components/query/query_history_table.vue
+++ b/kystudio/src/components/query/query_history_table.vue
@@ -1,35 +1,136 @@
<template>
<div id="queryHistoryTable">
- <div class="ksd-title-page ksd-mb-16">{{$t('kylinLang.menu.queryhistory')}}</div>
- <div class="clearfix ksd-mb-10">
- <div class="btn-group ksd-fleft export-btn">
- <el-dropdown
- split-button
- class="ksd-fleft"
- :class="{'is-disabled': !queryHistoryTotalSize}"
- type="primary"
- size="medium"
- id="exportSql"
- btn-icon="el-ksd-icon-export_22"
- placement="bottom-start"
- @click="exportHistory(false)">{{$t('kylinLang.query.export')}}
- <el-dropdown-menu slot="dropdown" class="model-actions-dropdown">
- <el-dropdown-item
- :disabled="!queryHistoryTotalSize"
- @click="exportHistory(true)">
- {{$t('kylinLang.query.exportSql')}}
- </el-dropdown-item>
+ <div class="clearfix ksd-mt-32 ksd-mb-16">
+ <div class="ksd-fleft">
+ <div class="ksd-title-page">{{$t('kylinLang.menu.queryhistory')}}</div>
+ </div>
+ <div class="ksd-fright">
+ <div class="btn-group export-btn">
+ <el-dropdown
+ split-button
+ class="ksd-fleft"
+ :class="{'is-disabled': !queryHistoryTotalSize}"
+ type="primary"
+ size="medium"
+ id="exportSql"
+ btn-icon="el-ksd-icon-export_22"
+ placement="bottom-start"
+ @click="exportHistory(false)">{{$t('kylinLang.query.export')}}
+ <el-dropdown-menu slot="dropdown" class="model-actions-dropdown">
+ <el-dropdown-item
+ :disabled="!queryHistoryTotalSize"
+ @click="exportHistory(true)">
+ {{$t('kylinLang.query.exportSql')}}
+ </el-dropdown-item>
+ </el-dropdown-menu>
+ </el-dropdown>
+ </div>
+ </div>
+ </div>
+
+ <div class="clearfix ksd-mb-16">
+ <div class="table-filters ksd-fleft">
+ <DropdownFilter
+ type="checkbox"
+ trigger="click"
+ :value="realizationFilters"
+ hideArrow
+ @input="v => filterContent(v, 'realization')"
+ @handleChangeAll="handleChangeAll"
+ @filterFilters="(v) => fiterList('loadFilterHitModelsList', v)"
+ @filter-scroll-bottom="scrollBottom"
+ :totalSizeLabel="$t('totalSizeLabel', {num: searchCount})"
+ isShowSearchInput
+ :is-loading-data="isShowLoading && (isFilterItemLoading || paginationRealFilteArr.length === maxFilterAndFilterValues)"
+ :is-loading="isFilterItemLoading"
+ :loading-tips="paginationRealFilteArr.length === maxFilterAndFilterValues ? $t('loadingTips') : ''"
+ :filterPlaceholder="$t('searchAnsweredBy')"
+ :optionsTitle="$t('model')"
+ :isSelectAllOption="allHitModel"
+ :options="paginationRealFilteArr.map(item => ({label: item, value: item}))"
+ :options2="pushdownFilteArr.map(item => ({label: item, value: item}))">
+ <el-button text type="primary" iconr="el-ksd-icon-arrow_down_22">
+ {{$t('kylinLang.query.realization_th')}} {{ realizationLabel }}
+ </el-button>
+ </DropdownFilter>
+ <DropdownFilter
+ type="checkbox"
+ trigger="click"
+ :value="filterData.query_status"
+ hideArrow
+ @input="v => filterContent(v, 'query_status')"
+ :options="[
+ { renderLabel: renderStatusLabel, value: 'SUCCEEDED' },
+ { renderLabel: renderStatusLabel, value: 'FAILED' }
+ ]">
+ <el-button text type="primary" iconr="el-ksd-icon-arrow_down_22">{{$t('kylinLang.query.query_status')}} {{filterData.query_status.length > 1 ? `${$t(filterData.query_status[0])} +${filterData.query_status.length - 1}` : filterData.query_status.join('')}}</el-button>
+ </DropdownFilter>
+ <DropdownFilter
+ type="datetimerange"
+ trigger="click"
+ :value="datetimerange"
+ hideArrow
+ :shortcuts="['lastDay', 'lastWeek', 'lastMonth']"
+ @input="v => handleInputDateRange(v)">
+ <el-button text type="primary" iconr="el-ksd-icon-arrow_down_22">{{$t('kylinLang.query.startTime_th')}} {{selectedRange}}</el-button>
+ </DropdownFilter>
+ <DropdownFilter
+ type="inputNumber"
+ trigger="click"
+ :value="[filterData.latencyFrom, filterData.latencyTo]"
+ hideArrow
+ :isShowDropDownImme="plusFilter.includes('latency_th')"
+ v-if="plusFilter.includes('latency_th')"
+ :isShowfooter="false"
+ @input="v => filterContent(v, 'latency')"
+ :options="queryNodes.map(item => ({label: item, value: item}))">
+ <el-button text type="primary" iconr="el-ksd-icon-arrow_down_22">{{$t('kylinLang.query.latency_th')}} <span v-if="filterData.latencyFrom!==null&&filterData.latencyTo!==null">{{filterData.latencyFrom}}s To {{ filterData.latencyTo }}s</span></el-button>
+ </DropdownFilter>
+ <DropdownFilter
+ type="checkbox"
+ trigger="click"
+ :value="filterData.server"
+ hideArrow
+ :isShowDropDownImme="plusFilter.includes('queryNode')"
+ v-if="plusFilter.includes('queryNode')"
+ @input="v => filterContent(v, 'server')"
+ :options="queryNodes.map(item => ({label: item, value: item}))">
+ <el-button text type="primary" iconr="el-ksd-icon-arrow_down_22">{{$t('kylinLang.query.queryNode')}} {{filterData.server.length > 1 ? `${$t(filterData.server[0])} +${filterData.server.length - 1}` : filterData.server.join('')}}</el-button>
+ </DropdownFilter>
+ <DropdownFilter
+ type="checkbox"
+ trigger="click"
+ :value="filterData.submitter"
+ hideArrow
+ :isShowDropDownImme="plusFilter.includes('submitter')"
+ isShowSearchInput
+ :filterPlaceholder="$t('searchSubmitter')"
+ v-if="queryHistoryFilter.includes('filterActions')&&plusFilter.includes('submitter')"
+ @input="v => filterContent(v, 'submitter')"
+ @filterFilters="(v) => fiterList('loadFilterSubmitterList', v)"
+ :options="submitterFilter.map(item => ({label: item, value: item}))">
+ <el-button text type="primary" iconr="el-ksd-icon-arrow_down_22">{{$t('kylinLang.query.submitter')}} {{filterData.submitter.length > 1 ? `${$t(filterData.submitter[0])} +${filterData.submitter.length - 1}` : filterData.submitter.join('')}}</el-button>
+ </DropdownFilter>
+ <el-dropdown @command="handleCommand" placement="bottom-start" trigger="click" v-if="plusFilter.length < 3 ">
+ <i class="el-dropdown-link el-ksd-n-icon-plus-outlined"></i>
+ <el-dropdown-menu slot="dropdown">
+ <el-dropdown-item command="latency_th" v-if="!plusFilter.includes('latency_th')">{{$t('kylinLang.query.latency_th')}}</el-dropdown-item>
+ <el-dropdown-item command="queryNode" v-if="!plusFilter.includes('queryNode')">{{$t('kylinLang.query.queryNode')}}</el-dropdown-item>
+ <el-dropdown-item command="submitter" v-if="!plusFilter.includes('submitter')">{{$t('kylinLang.query.submitter')}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
+ <div class="actions">
+ <el-button
+ nobg-text
+ class="reset-filters-btn"
+ :disabled="!isHasFilterValue"
+ @click="clearAllTags">{{$t('clearAll')}}</el-button>
+ </div>
</div>
- <div class="ksd-fright ksd-inline searchInput ksd-ml-10">
- <el-input v-model="filterData.sql" v-global-key-event.enter.debounce="onSqlFilterChange" @clear="onSqlFilterChange()" prefix-icon="el-ksd-icon-search_22" :placeholder="$t('searchSQL')" size="medium"></el-input>
+ <div class="ksd-fright ksd-ml-10">
+ <el-input v-model="filterData.sql" class="searchInput" v-global-key-event.enter.debounce="onSqlFilterChange" @clear="onSqlFilterChange()" prefix-icon="el-ksd-icon-search_22" :placeholder="$t('searchSQL')" size="medium"></el-input>
</div>
</div>
- <div class="filter-tags" v-show="filterTags.length">
- <div class="filter-tags-layout"><el-tag closable v-for="(item, index) in filterTags" :key="index" @close="handleClose(item)">{{$t(item.source) + ':'}}{{['query_status', 'realization'].includes(item.key) ? $t(item.label) : item.label}}</el-tag><span class="clear-all-filters" @click="clearAllTags">{{$t('clearAll')}}</span></div>
- <span class="filter-queries-size">{{$t('filteredTotalSize', {totalSize: queryHistoryTotalSize})}}</span>
- </div>
<el-table
:data="queryHistoryData"
v-scroll-shadow
@@ -171,12 +272,12 @@
</div>
</template>
</el-table-column>
- <el-table-column :renderHeader="renderColumn" prop="query_time" width="218">
+ <el-table-column :label="$t('kylinLang.query.startTime_th')" prop="query_time" width="218">
<template slot-scope="props">
{{transToGmtTime(props.row.query_time)}}
</template>
</el-table-column>
- <el-table-column :renderHeader="renderColumn2" prop="duration" align="right" width="120">
+ <el-table-column :label="$t('kylinLang.query.latency_th')" prop="duration" align="right" width="120">
<template slot-scope="props">
<span v-if="props.row.duration < 1000">< 1s</span>
<span v-if="props.row.duration >= 1000">{{props.row.duration / 1000 | fixed(2)}}s</span>
@@ -203,18 +304,7 @@
</template>
</el-table-column>
<el-table-column
- :filters="realFilteArr"
- :filters2="allHitModels"
- :show-search-input="true"
- :filtered-value="filterData.realization"
:label="$t('kylinLang.query.realization_th')"
- filter-icon="el-ksd-icon-filter_22"
- :placeholder="$t('searchAnsweredBy')"
- :emptyFilterText="$t('kylinLang.common.noData')"
- :show-multiple-footer="false"
- :filter-change="(v) => filterContent(v, 'realization')"
- :filter-filters-change="(v) => fiterList('loadFilterHitModelsList', v)"
- customFilterClass="filter-realization"
prop="realizations"
width="250">
<template slot-scope="props">
@@ -233,25 +323,15 @@
</div>
</template>
</el-table-column>
- <el-table-column :filters="statusList.map(item => ({text: $t(item), value: item}))" :filtered-value="filterData.query_status" :label="$t('kylinLang.query.query_status')" filter-icon="el-ksd-icon-filter_22" :show-multiple-footer="false" :filter-change="(v) => filterContent(v, 'query_status')" show-overflow-tooltip prop="query_status" width="130">
+ <el-table-column :label="$t('kylinLang.query.query_status')" show-overflow-tooltip prop="query_status" width="130">
<template slot-scope="scope">
{{$t('kylinLang.query.' + scope.row.query_status)}}
</template>
</el-table-column>
- <el-table-column :filterMultiple="false" :show-all-select-option="false" :filters="queryNodes.map(item => ({text: item, value: item}))" :filtered-value="filterData.server" :label="$t('kylinLang.query.queryNode')" filter-icon="el-ksd-icon-filter_22" :filter-change="(v) => filterContent(v, 'server')" show-overflow-tooltip prop="server" width="145">
+ <el-table-column :label="$t('kylinLang.query.queryNode')" show-overflow-tooltip prop="server" width="145">
</el-table-column>
<el-table-column
:label="$t('kylinLang.query.submitter')"
- :filters="submitterFilter.map(item => ({text: item, value: item}))"
- :show-search-input="true"
- :filtered-value="filterData.submitter"
- filter-icon="el-ksd-icon-filter_22"
- :show-multiple-footer="false"
- :placeholder="$t('searchSubmitter')"
- :emptyFilterText="$t('kylinLang.common.noData')"
- :filter-change="(v) => filterContent(v, 'submitter')"
- :filter-filters-change="(v) => fiterList('loadFilterSubmitterList', v)"
- customFilterClass="filter-submitter"
prop="submitter"
v-if="queryHistoryFilter.includes('filterActions')"
show-overflow-tooltip
@@ -302,10 +382,11 @@
import { mapActions, mapGetters } from 'vuex'
import { Component, Watch } from 'vue-property-decorator'
// import $ from 'jquery'
-import { sqlRowsLimit, sqlStrLenLimit, formatSQLConfig } from '../../config/index'
+import { sqlRowsLimit, sqlStrLenLimit, formatSQLConfig, filterPagesize, maxFilterAndFilterValues } from '../../config/index'
// import { format } from 'sql-formatter'
import IndexDetails from '../studio/StudioModel/ModelList/ModelAggregate/indexDetails'
import Diagnostic from 'components/admin/Diagnostic/index'
+import DropdownFilter from '../common/DropdownFilter/DropdownFilter.vue'
@Component({
name: 'QueryHistoryTable',
props: ['queryHistoryData', 'queryHistoryTotalSize', 'queryNodes', 'filterDirectData', 'isLoadingHistory'],
@@ -331,7 +412,8 @@
},
components: {
IndexDetails,
- Diagnostic
+ Diagnostic,
+ DropdownFilter
},
locales: {
'en': {
@@ -347,7 +429,8 @@
SUCCEEDED: 'SUCCEEDED',
FAILED: 'FAILED',
pushdown: 'Pushdown',
- modelName: 'Model',
+ model: 'Model',
+ modelName: 'All Models',
totalDuration: 'Total Duration',
PREPARATION: 'Preparation',
SQL_TRANSFORMATION: 'SQL transformation',
@@ -364,7 +447,7 @@
SQL_PUSHDOWN_TRANSFORMATION: 'SQL pushdown transformation',
CONSTANT_QUERY: 'Constant query',
HIT_CACHE: 'Cache hit',
- allModels: 'All Models',
+ allModels: 'All',
searchAnsweredBy: 'Search by model name',
searchSubmitter: 'Search by submitter',
aggDetailTitle: 'Aggregate Detail',
@@ -376,7 +459,11 @@
downloadQueryDiagnosticPackage: 'Download Query Diagnostic Package',
queryError: 'Query error.',
viewDetails: 'View Details',
- errorTitle: 'Error Details'
+ errorTitle: 'Error Details',
+ fetchError: 'Can\'t get the result as the record is missing',
+ loadingTips: 'Up to 100 items',
+ totalSizeLabel: '{num} search results',
+ realizationFilterLengthTips: 'Exceed the selection limit. Please clear and reselect'
}
},
filters: {
@@ -386,18 +473,28 @@
}
})
export default class QueryHistoryTable extends Vue {
+ maxFilterAndFilterValues = maxFilterAndFilterValues
datetimerange = ''
startSec = 0
endSec = 10
latencyFilterPopoverVisible = false
realFilteArr = []
+ pushdownFilteArr = []
+ filterHandleChangeAll = false
+ filtePageOffset = 0
+ isShowLoading = false
submitterFilter = []
+ realizationFilters = []
+ plusFilter = []
+ searchCount = 0
+ modelCount = 0
filterData = {
startTimeFrom: null,
startTimeTo: null,
latencyFrom: null,
latencyTo: null,
realization: [],
+ exclude_realization: [],
submitter: [],
server: [],
sql: '',
@@ -428,6 +525,13 @@
this.queryErrorVisible = true
}
+ renderStatusLabel (h, option) {
+ const { value } = option
+ return [
+ <span>{this.$t(value)}</span>
+ ]
+ }
+
@Watch('queryHistoryData')
onQueryHistoryDataChange (val) {
val.forEach(element => {
@@ -456,8 +560,23 @@
return this.isHasFilterValue ? this.$t('kylinLang.common.noResults') : this.$t('kylinLang.common.noData')
}
- get allHitModels () {
- return [{text: this.$t('allModels'), value: 'modelName', icon: 'el-icon-ksd-cube'}]
+ get allHitModel () {
+ return {label: this.$t('allModels'), value: 'modelName', indeterminate: this.filterData.exclude_realization.length > 0, totalSize: this.modelCount, selectedSize: this.filterData.realization.includes('modelName') ? this.modelCount - this.filterData.exclude_realization.length : this.filterData.realization.filter(i => !this.pushdownFilteArr.includes(i)).length}
+ }
+ get realizationLabel () {
+ if (this.filterData.realization.length) {
+ if (this.filterData.realization.includes('modelName')) {
+ if (this.filterData.realization[0] === 'modelName') { // 过滤器第一个选择全部模型, 显示模型第一个名称+数字
+ return this.realizationFilters[0] !== 'modelName' ? `${this.realizationFilters[0]} +${this.modelCount - 1 - this.filterData.exclude_realization.length + this.filterData.realization.length - 1}` : `+${this.modelCount - this.filterData.exclude_realization.length + this.filterData.realization.length - 1}`
+ } else {
+ return `${this.filterData.realization[0]} +${this.filterData.realization.length - 1 - 1 + this.modelCount - this.filterData.exclude_realization.length}`
+ }
+ } else {
+ return this.filterData.realization.length > 1 ? `${this.filterData.realization[0]} +${this.filterData.realization.length - 1}` : this.filterData.realization[0]
+ }
+ } else {
+ return ''
+ }
}
// 排除击中 snapshot 的查询对象
@@ -479,7 +598,7 @@
}
dateRangeChange () {
- if (this.datetimerange) {
+ if (this.datetimerange.length) {
this.filterData.startTimeFrom = new Date(this.datetimerange[0]).getTime()
this.filterData.startTimeTo = new Date(this.datetimerange[1]).getTime()
this.clearDatetimeRange()
@@ -491,6 +610,16 @@
}
}
+ get selectedRange () {
+ if (this.datetimerange && this.datetimerange[0] && this.datetimerange[1]) {
+ return `${this.transToGmtTime(this.filterData.startTimeFrom)} To ${this.transToGmtTime(this.filterData.startTimeTo)}`
+ }
+ return ''
+ }
+ handleCommand (command) {
+ this.plusFilter.push(command)
+ }
+
initFilterData () {
const { startTimeFrom, startTimeTo } = JSON.parse(JSON.stringify(this.filterDirectData))
if (!startTimeFrom || !startTimeTo) return
@@ -507,27 +636,40 @@
async loadFilterHitModelsList (filterValue) {
try {
- const res = await this.fetchHitModelsList({ project: this.currentSelectedProject, model_name: filterValue, page_size: 100 })
+ const res = await this.fetchHitModelsList({ project: this.currentSelectedProject, model_name: filterValue, page_size: maxFilterAndFilterValues })
const data = await handleSuccessAsync(res)
- this.realFilteArr = data.map((d) => {
- if (d === 'HIVE') {
- return { text: d, value: d, icon: 'el-icon-ksd-hive' }
- } else if (d === 'CONSTANTS') {
- return { text: d, value: d, icon: 'el-icon-ksd-contants' }
- } else if (d === 'OBJECT STORAGE') {
- return { text: d, value: d, icon: 'el-icon-ksd-data_source' }
- } else {
- return { text: d, value: d, icon: 'el-icon-ksd-model' }
- }
- })
+ this.pushdownFilteArr = data.engines
+ this.realFilteArr = data.models
+ if (this.filterData.realization.includes('modelName')) {
+ data.models.forEach(m => {
+ if (!this.filterData.exclude_realization.includes(m)) {
+ this.realizationFilters.push(m)
+ }
+ })
+ }
+ this.searchCount = data.search_count
+ this.modelCount = data.total_model_count
} catch (e) {
handleError(e)
}
}
+ get isFilterItemLoading () {
+ return this.paginationRealFilteArr.length < this.realFilteArr.length
+ }
+ get paginationRealFilteArr () {
+ return this.realFilteArr.slice(0, (this.filtePageOffset + 1) * filterPagesize)
+ }
+ scrollBottom () {
+ this.isShowLoading = true
+ setTimeout(() => {
+ this.isFilterItemLoading && this.filtePageOffset++
+ }, 200)
+ }
+
async loadFilterSubmitterList (filterValue) {
try {
- const res = await this.fetchSubmitterList({ project: this.currentSelectedProject, submitter: filterValue, page_size: 100 })
+ const res = await this.fetchSubmitterList({ project: this.currentSelectedProject, submitter: filterValue, page_size: maxFilterAndFilterValues })
this.submitterFilter = await handleSuccessAsync(res)
} catch (e) {
handleError(e)
@@ -774,161 +916,67 @@
if (!realization.valid || realization.indexType === 'Table Snapshot') return
this.$emit('openIndexDialog', realization, rows)
}
- renderColumn (h) {
- if (this.filterData.startTimeFrom && this.filterData.startTimeTo) {
- const startTime = transToGmtTime(this.filterData.startTimeFrom)
- const endTime = transToGmtTime(this.filterData.startTimeTo)
- return (<span onClick={e => (e.stopPropagation())}>
- <span>{this.$t('kylinLang.query.startTime_th')}</span>
- <el-tooltip placement="top">
- <div slot="content">
- <span>
- <i class='el-icon-time'></i>
- <span> {startTime} To {endTime}</span>
- </span>
- </div>
- <el-date-picker
- value={this.datetimerange}
- onInput={this.handleInputDateRange}
- type="datetimerange"
- popper-class="table-filter-datepicker"
- toggle-icon="el-ksd-icon-data_range_old isFilter"
- is-only-icon={true}>
- </el-date-picker>
- </el-tooltip>
- </span>)
- } else {
- return (<span onClick={e => (e.stopPropagation())}>
- <span>{this.$t('kylinLang.query.startTime_th')}</span>
- <el-date-picker
- value={this.datetimerange}
- onInput={this.handleInputDateRange}
- popper-class="table-filter-datepicker"
- type="datetimerange"
- toggle-icon="el-ksd-icon-data_range_old"
- is-only-icon={true}>
- </el-date-picker>
- </span>)
- }
- }
+
handleInputDateRange (val) {
this.datetimerange = val
this.dateRangeChange()
this.filterList()
}
- resetLatency () {
- this.startSec = 0
- this.endSec = 10
- this.filterData.latencyFrom = null
- this.filterData.latencyTo = null
- this.latencyFilterPopoverVisible = false
- this.clearLatencyRange()
- this.filterList()
- }
- saveLatencyRange () {
- this.filterData.latencyFrom = this.startSec
- if (this.startSec > this.endSec) {
- this.filterData.latencyTo = this.endSec = this.startSec
- } else {
- this.filterData.latencyTo = this.endSec
- }
- this.latencyFilterPopoverVisible = false
- this.clearLatencyRange()
- this.filterTags.push({label: `${this.startSec}s To ${this.endSec}s`, source: 'kylinLang.query.latency_th', key: 'latency'})
- this.filterList()
+
+ handleChangeAll () {
+ this.filterHandleChangeAll = true
}
- renderColumn2 (h) {
- if (this.filterData.latencyTo) {
- return (<span>
- <span style="margin-right:5px;">{this.$t('kylinLang.query.latency_th')}</span>
- <el-tooltip placement="top">
- <div slot="content">
- <span>
- <i class='el-icon-time'></i>
- <span> {this.filterData.latencyFrom}s To {this.filterData.latencyTo}s</span>
- </span>
- </div>
- <el-popover
- ref="latencyFilterPopover"
- placement="bottom"
- width="315"
- value={this.latencyFilterPopoverVisible}
- onInput={val => (this.latencyFilterPopoverVisible = val)}>
- <div class="latency-filter-pop">
- <el-input-number
- size="small"
- min={0}
- value={this.startSec}
- onInput={val1 => (this.startSec = val1)}></el-input-number>
- <span> S To</span>
- <el-input-number
- size="small"
- min={this.startSec}
- class="ksd-ml-10"
- value={this.endSec}
- onInput={val2 => (this.endSec = val2)}></el-input-number>
- <span> S</span>
- </div>
- <div class="latency-filter-footer">
- <el-button size="small" onClick={this.resetLatency}>{this.$t('kylinLang.query.clear')}</el-button>
- <el-button type="primary" onClick={this.saveLatencyRange} size="small">{this.$t('kylinLang.common.save')}</el-button>
- </div>
- <i class="el-ksd-icon-data_range_old isFilter" onClick={e => (e.stopPropagation())} slot="reference"></i>
- </el-popover>
- </el-tooltip>
- </span>)
- } else {
- return (<span>
- <span style="margin-right:5px;">{this.$t('kylinLang.query.latency_th')}</span>
- <el-popover
- ref="latencyFilterPopover"
- placement="bottom"
- width="315"
- value={this.latencyFilterPopoverVisible}
- onInput={val => (this.latencyFilterPopoverVisible = val)}>
- <div class="latency-filter-pop">
- <el-input-number
- size="small"
- value={this.startSec}
- min={0}
- onInput={val1 => (this.startSec = val1)}></el-input-number>
- <span> S To</span>
- <el-input-number
- size="small"
- class="ksd-ml-10"
- value={this.endSec}
- min={this.startSec}
- onInput={val2 => (this.endSec = val2)}></el-input-number>
- <span> S</span>
- </div>
- <div class="latency-filter-footer">
- <el-button size="small" onClick={this.resetLatency}>{this.$t('kylinLang.query.clear')}</el-button>
- <el-button type="primary" onClick={this.saveLatencyRange} size="small">{this.$t('kylinLang.common.save')}</el-button>
- </div>
- <i class="el-ksd-icon-data_range_old" onClick={e => (e.stopPropagation())} slot="reference"></i>
- </el-popover>
- </span>)
- }
- }
// 查询状态过滤回调函数
filterContent (val, type) {
- const maps = {
- realization: 'kylinLang.query.answered_by',
- query_status: 'taskStatus',
- server: 'kylinLang.query.queryNode',
- submitter: 'kylinLang.query.submitter'
+ if (type === 'latency') {
+ this.filterData['latencyFrom'] = val[0]
+ this.filterData['latencyTo'] = val[1]
+ this.filterList()
+ } else if (type === 'realization') {
+ setTimeout(() => {
+ if (val.includes('modelName') && this.filterHandleChangeAll) { // 选择全部模型
+ this.realizationFilters = [...this.realFilteArr, ...val]
+ this.filterData.realization = [...val]
+ } else if (!val.includes('modelName') && this.filterHandleChangeAll) { // 取消全部模型
+ this.realizationFilters = [...this.filterData.realization].filter(i => i !== 'modelName')
+ this.filterData.realization = [...this.filterData.realization].filter(i => i !== 'modelName')
+ this.filterData.exclude_realization = []
+ } else {
+ if (val.includes('modelName')) { // 操作其他选项时,有全选模型,模型名称的进入反选数组
+ this.realizationFilters = [...val]
+ this.filterData.realization = [...val].filter(i => this.pushdownFilteArr.includes(i) || i === 'modelName')
+ this.filterData.exclude_realization = this.filterData.exclude_realization.filter(i => !this.realizationFilters.includes(i)) // 全选情况下,手动勾选的模型是取消反选的操作
+ this.filterData.exclude_realization = Array.from(new Set([...this.filterData.exclude_realization, ...this.realFilteArr.filter(i => !val.includes(i))]))
+ } else {
+ this.realizationFilters = [...val]
+ this.filterData.realization = [...val]
+ this.filterData.exclude_realization = []
+ }
+ }
+ if (this.filterData.realization.length > maxFilterAndFilterValues || this.filterData.exclude_realization.length > maxFilterAndFilterValues) {
+ this.$message({
+ message: this.$t('realizationFilterLengthTips'),
+ type: 'warning',
+ duration: 10000,
+ showClose: true
+ })
+ if (val.length > maxFilterAndFilterValues) {
+ this.realizationFilters = val.slice(0, val.length - 1)
+ this.filterData.realization = val.slice(0, val.length - 1)
+ } else {
+ this.realizationFilters.push(this.realFilteArr.filter(i => !val.includes(i))[0])
+ this.filterData.exclude_realization = this.filterData.exclude_realization.slice(0, this.filterData.exclude_realization.length - 1)
+ }
+ return
+ }
+ this.filterList()
+ })
+ } else {
+ this.filterData[type] = val
+ this.filterList()
}
-
- this.filterTags = this.filterTags.filter((item, index) => item.key !== type || item.key === type && val.includes(item.label))
- const list = this.filterTags.filter(it => it.key === type).map(it => it.label)
- val.length && val.forEach(item => {
- if (!list.includes(item)) {
- this.filterTags.push({label: item === 'modelName' ? 'allModels' : item, source: maps[type], key: type})
- }
- })
- this.filterData[type] = val
- this.filterList()
+ this.filterHandleChangeAll = false
}
// 删除单个筛选条件
handleClose (tag) {
@@ -958,12 +1006,14 @@
clearAllTags () {
this.filterData.query_status.splice(0, this.filterData.query_status.length)
this.filterData.realization.splice(0, this.filterData.realization.length)
+ this.filterData.exclude_realization.splice(0, this.filterData.exclude_realization.length)
this.filterData.server.splice(0, this.filterData.server.length)
this.filterData.submitter.splice(0, this.filterData.submitter.length)
this.filterData.latencyFrom = null
this.filterData.latencyTo = null
this.datetimerange = ''
this.filterTags = []
+ this.realizationFilters = []
this.dateRangeChange()
this.filterList()
}
@@ -1041,6 +1091,24 @@
background: @table-stripe-color;
}
} */
+ .actions {
+ line-height: 22px;
+ // border-right: 1px solid @ke-border-divider-color;
+ margin: 6px 8px 0 0;
+ padding-right: 4px;
+ height: 22px;
+ display: inline-block;
+ .reset-filters-btn.is-disabled {
+ i {
+ cursor: not-allowed;
+ }
+ }
+ }
+ .table-filters {
+ >.dropdown-filter {
+ margin-left: -8px;
+ }
+ }
.el-table__expanded-cell {
padding: 24px;
.copy-btn {
@@ -1367,7 +1435,7 @@
}
}
.latency-filter-footer {
- border-top: 1px solid @line-split-color;
+ border-top: 1px solid @ke-border-divider-color;
padding: 10px 10px 0;
margin: 10px -10px 0;
text-align: right;
@@ -1427,17 +1495,20 @@
color: @text-title-color;
}
}
- .filter-realization, .filter-submitter {
+ .filter-submitter {
.el-checkbox-group {
max-height: 205px;
overflow: auto;
}
i {
- margin-right: 5px;
+ margin-right: 4px;
color: @text-normal-color;
}
.el-checkbox__input.is-checked+.el-checkbox__label i {
color: @base-color;
}
}
+ .filter-realization i {
+ margin-right: 4px;
+ }
</style>
diff --git a/kystudio/src/components/studio/StudioModel/ModelList/index.vue b/kystudio/src/components/studio/StudioModel/ModelList/index.vue
index cbaea53..7d457f5 100644
--- a/kystudio/src/components/studio/StudioModel/ModelList/index.vue
+++ b/kystudio/src/components/studio/StudioModel/ModelList/index.vue
@@ -72,12 +72,10 @@
</DropdownFilter>
<div class="actions">
<el-button
- text
- type="primary"
- icon="el-ksd-icon-resure_22"
+ nobg-text
class="reset-filters-btn"
:disabled="isResetFilterDisabled"
- @click="handleResetFilters">{{$t('reset')}}</el-button>
+ @click="handleResetFilters">{{$t('clearAll')}}</el-button>
</div>
</div>
<div class="ksd-fright">
@@ -1134,19 +1132,24 @@
.el-tabs__content {
overflow: initial;
}
+ .actions {
+ line-height: 22px;
+ // border-right: 1px solid @ke-border-divider-color;
+ margin: 6px 8px 0 0;
+ padding-right: 4px;
+ height: 22px;
+ display: inline-block;
+ .reset-filters-btn.is-disabled {
+ i {
+ cursor: not-allowed;
+ }
+ }
+ }
.table-filters {
margin-bottom: 8px;
>.dropdown-filter {
margin-left: -8px;
}
- .actions {
- float: right;
- .reset-filters-btn.is-disabled {
- i {
- cursor: not-allowed;
- }
- }
- }
}
.last-modified {
font-size: 12px;
diff --git a/kystudio/src/config/index.js b/kystudio/src/config/index.js
index dd5eac9..7a98627 100644
--- a/kystudio/src/config/index.js
+++ b/kystudio/src/config/index.js
@@ -5,6 +5,8 @@
let baseUrl
let regexApiUrl
+let filterPagesize = 5
+
let pageCount = 10
let bigPageCount = 20
let pageSizes = [10, 20, 50, 100]
@@ -31,6 +33,7 @@
apiUrl,
baseUrl,
regexApiUrl,
+ filterPagesize,
pageCount,
bigPageCount,
pageSizes,
diff --git a/kystudio/src/service/datasource.js b/kystudio/src/service/datasource.js
index b1112c1..6ae2130 100644
--- a/kystudio/src/service/datasource.js
+++ b/kystudio/src/service/datasource.js
@@ -122,7 +122,7 @@
return Vue.resource(`${apiUrl}access/${projectId}/all`).get(para)
},
getHistoryList: (para) => {
- return Vue.resource(apiUrl + 'query/history_queries{?realization}{&query_status}{&submitter}').get(para)
+ return Vue.resource(apiUrl + 'query/history_queries{?realization}{&query_status}{&submitter}{&exclude_realization}').get(para)
},
loadOnlineQueryNodes: (para) => {
return Vue.resource(apiUrl + 'query/servers').get(para)