kystudio/src/components/studio/StudioModel/AddMeasure/index.vue (1,076 lines of code) (raw):

<template> <el-dialog append-to-body width="480px" limited-area :title="$t(measureTitle)" :visible="true" top="5%" class="add-measure-modal" :close-on-press-escape="false" :close-on-click-modal="false" @close="handleHide(false)"> <el-form :model="measure" class="add-measure" label-position="top" :rules="rules" ref="measureForm"> <el-form-item :label="$t('name')" prop="name"> <div> <el-input class="measures-width measure-name-input" size="medium" v-model.trim="measure.name" :placeholder="$t('kylinLang.common.nameFormatValidTip2')"></el-input> </div> </el-form-item> <el-form-item :label="$t('expression')" prop="expression"> <el-select :popper-append-to-body="false" class="measures-width measure-expression-select" popper-class="js_measure-expression" size="medium" v-model="measure.expression" @change="changeExpression"> <el-option v-for="item in expressionsConfigs" :key="item.value" :label="item.label" :value="item.value"> </el-option> </el-select> <template v-if="measure.expression ==='SUM_LC'"> <div class="ksd-title-label-mini ksd-mtb-8">{{$t('semiAdditive')}}</div> <div class="semi-additive-desc ksd-fs-12"> <ol> <li><i class="point">•</i>{{$t('semiAdditiveMsg1')}}</li> <li><i class="point">•</i>{{$t('semiAdditiveMsg2')}}</li> <li><i class="point">•</i>{{$t('semiAdditiveMsg3')}}</li> </ol> </div> </template> </el-form-item> <el-form-item v-if="measure.expression === 'TOP_N'|| measure.expression === 'PERCENTILE_APPROX' || measure.expression === 'COUNT_DISTINCT'" :label="$t('return_type')" > <el-select :popper-append-to-body="false" size="medium" v-model="measure.return_type" class="measures-width" @change="changeReturnType"> <el-option v-for="(item, index) in getSelectDataType" :key="index" :label="item.name" :value="item.value"> </el-option> </el-select> </el-form-item> <div class="ksd-fs-16 ksd-mb-6 value-label" v-if="measure.expression === 'TOP_N'"> {{$t('paramValue')}} </div> <el-form-item :class="{'is-error': corrColumnError || (ccValidateError&&ccVisible), 'cc-item': ccVisible, 'corr-item': measure.expression ==='CORR'}" prop="parameterValue.value" key="parameterItem"> <span slot="label" class="withIconLabel"><span>{{isOrderBy}}</span><el-tooltip effect="dark" placement="right" v-if="measure.expression ==='CORR'"><span slot="content" v-html="$t('corrTips')"></span><i class="el-ksd-icon-more_info_16 icon ksd-ml-2"></i></el-tooltip><el-tooltip effect="dark" placement="right" v-if="measure.expression ==='SUM_LC'"><span slot="content" v-html="$t('sumLcTips')"></span><i class="el-ksd-icon-more_info_16 icon ksd-ml-2"></i></el-tooltip> </span> <el-tag type="info" class="measures-width" v-if="measure.expression === 'SUM(constant)' || measure.expression === 'COUNT(constant)'">1</el-tag> <div class="measure-flex-row" v-else> <div class="flex-item"> <el-select class="parameter-select" popper-class="js_parameter-select" :class="{ 'measures-addCC': measure.expression !== 'COUNT_DISTINCT' && measure.expression !== 'TOP_N', 'measures-width': measure.expression === 'COUNT_DISTINCT' || measure.expression === 'TOP_N', 'error-tip': showMutipleColumnsTip, 'error-measure': handlerErrorTip(measure)}" size="medium" v-model="measure.parameterValue.value" :placeholder="$t('kylinLang.common.pleaseSelectOrSearch')" filterable @change="(v) => changeParamValue(v, measure)" :disabled="isEdit"> <i slot="prefix" class="el-input__icon el-ksd-icon-search_22" v-if="!measure.parameterValue.value"></i> <el-option-group key="column" :label="$t('columns')"> <el-option v-for="(item, index) in getParameterValue" :key="index" :label="item.name" :value="item.name"> <el-tooltip :content="item.name" effect="dark" placement="top"><span>{{item.name.split('.')[item.name.split('.').length - 1] | omit(30, '...')}}</span></el-tooltip> <span class="ky-option-sub-info">{{item.datatype.toLocaleLowerCase()}}</span> </el-option> </el-option-group> <el-option-group key="ccolumn" :label="$t('ccolumns')" v-if="getCCGroups().length || newCCList.length"> <el-option v-for="item in getCCGroups()" :key="item.guid" :label="item.tableAlias + '.' + item.columnName" :value="item.tableAlias + '.' + item.columnName"> <el-tooltip :content="`${item.tableAlias}.${item.columnName}`" effect="dark" placement="top"><span>{{item.columnName | omit(30, '...')}}</span></el-tooltip> <span class="ky-option-sub-info">{{item.datatype.toLocaleLowerCase()}}</span> </el-option> <el-option v-for="item in newCCList" :key="item.table_guid" :label="item.tableAlias + '.' + item.columnName" :value="item.tableAlias + '.' + item.columnName"> <el-tooltip :content="`${item.tableAlias}.${item.columnName}`" effect="dark" placement="top"><span>{{item.columnName | omit(30, '...')}}</span></el-tooltip> <span class="ky-option-sub-info">{{item.datatype.toLocaleLowerCase()}}</span> </el-option> </el-option-group> </el-select> <common-tip :content="$t('addCCTip')"><el-button size="medium" v-if="measure.expression !== 'COUNT_DISTINCT' && measure.expression !== 'TOP_N'" icon="el-ksd-icon-auto_computed_column_old" type="primary" plain @click="newCC" class="ksd-ml-6" :disabled="(isEdit && ccVisible) || !!isHybridModel"></el-button></common-tip> </div> <el-button type="primary" size="mini" icon="el-icon-ksd-add_2" plain circle v-if="measure.expression === 'COUNT_DISTINCT'&&measure.return_type!=='bitmap'" class="ksd-ml-10" @click="addNewProperty"></el-button> </div> <p class="sync-comment-tip" v-if="showSync">{{$t('syncCommentTip1', {comment: columnComment(measure.parameterValue.value)})}}<span class="sync-content" @click="handleSyncComment">{{$t('syncContent')}}</span></p> </el-form-item> <CCEditForm ref="ccEditForm" class="ksd-mb-8" :class="{'error-tips': corrColumnError}" key="ccEditForm" v-if="ccVisible" @checkSuccess="saveCC" @delSuccess="delCC" :hideCancel="isEditMeasure" :isEditMeasureCC="!isEdit" source="createMeasure" :ccDesc="ccObject" :modelInstance="modelInstance" @resetSubmitLoading="resetSubmitType" @saveError="resetSubmitType"></CCEditForm> <div class="error-tips" v-if="corrColumnError">{{$t('corrColDatatypeError')}}</div> <el-form-item :label="isGroupBy" v-if="(measure.expression === 'COUNT_DISTINCT' || measure.expression === 'TOP_N')&&measure.convertedColumns.length>0" prop="convertedColumns[0].value" :rules="rules.convertedColValidate" key="topNItem" :class="{'measure-column-multiple': measure.expression === 'COUNT_DISTINCT'}"> <div class="measure-flex-row" v-for="(column, index) in measure.convertedColumns" :key="index" :class="{'ksd-mt-10': !isGroupBy || (isGroupBy && index > 0)}"> <div class="flex-item"> <el-select :class="['measures-width', {'error-tip': showMutipleColumnsTip || column.sameIndex}]" size="medium" v-model="column.value" :placeholder="$t('kylinLang.common.pleaseSelectOrSearch')" filterable @change="changeConColParamValue(column.value, index)"> <i slot="prefix" class="el-input__icon el-ksd-icon-search_22" v-if="!column.value"></i> <el-option v-for="(item, index) in getParameterValue2" :key="index" :label="item.name" :value="item.name"> <el-tooltip :content="item.name" effect="dark" placement="top"><span>{{item.name | omit(30, '...')}}</span></el-tooltip> <span class="ky-option-sub-info">{{item.datatype}}</span> </el-option> <el-option-group key="ccolumn" :label="$t('ccolumns')" v-if="measure.expression === 'TOP_N' && getCCGroups(isGroupBy).length"> <el-option v-for="item in getCCGroups(isGroupBy)" :key="item.guid" :label="item.tableAlias + '.' + item.columnName" :value="item.tableAlias + '.' + item.columnName"> <el-tooltip :content="`${item.tableAlias}.${item.columnName}`" effect="dark" placement="top"><span>{{item.columnName | omit(30, '...')}}</span></el-tooltip> <span class="ky-option-sub-info">{{item.datatype.toLocaleLowerCase()}}</span> </el-option> </el-option-group> </el-select> <p class="error-text" v-if="column.sameIndex">{{$t('duplicateColumns')}}</p> </div> <el-button type="primary" plain icon="el-icon-ksd-add_2" size="mini" v-if="measure.expression === 'TOP_N' && index == 0" circle @click="addNewProperty" class="ksd-ml-10"></el-button><el-button type="primary" icon="el-icon-minus" size="mini" circle @click="deleteProperty(index)" class="del-pro" :class="[measure.expression === 'COUNT_DISTINCT' ? 'ksd-ml-10' : 'ksd-ml-5', {'del-margin-more': measure.expression === 'TOP_N' && index > 0}]" :disabled="measure.expression === 'TOP_N' && measure.convertedColumns.length == 1"></el-button> </div> </el-form-item> <template v-if="measure.expression ==='CORR'"> <el-form-item :class="{'is-error': corrColumnError || (ccValidateError&&ccVisible2), 'cc-item': ccVisible2}" prop="convertedColumns[0].value" :rules="rules.convertedColValidate" key="corrItem"> <div> <el-select class="measures-addCC" size="medium" v-model="measure.convertedColumns[0].value" :placeholder="$t('kylinLang.common.pleaseSelectOrSearch')" filterable @change="(v) => changeCORRParamValue(v, measure)" :disabled="isCCEdit2"> <i slot="prefix" class="el-input__icon el-ksd-icon-search_22" v-if="!measure.convertedColumns[0].value"></i> <el-option-group key="column" :label="$t('columns')"> <el-option v-for="(item, index) in getParameterValue" :key="index" :label="item.name" :value="item.name"> <el-tooltip :content="item.name" effect="dark" placement="top"><span>{{item.name.split('.')[item.name.split('.').length - 1] | omit(30, '...')}}</span></el-tooltip> <span class="ky-option-sub-info">{{item.datatype.toLocaleLowerCase()}}</span> </el-option> </el-option-group> <el-option-group key="ccolumn" :label="$t('ccolumns')" v-if="getCCGroups().length || newCCList.length"> <el-option v-for="item in getCCGroups()" :key="item.guid" :label="item.tableAlias + '.' + item.columnName" :value="item.tableAlias + '.' + item.columnName"> <el-tooltip :content="`${item.tableAlias}.${item.columnName}`" effect="dark" placement="top"><span>{{item.columnName | omit(30, '...')}}</span></el-tooltip> <span class="ky-option-sub-info">{{item.datatype.toLocaleLowerCase()}}</span> </el-option> <el-option v-for="item in newCCList" :key="item.table_guid" :label="item.tableAlias + '.' + item.columnName" :value="item.tableAlias + '.' + item.columnName"> <el-tooltip :content="`${item.tableAlias}.${item.columnName}`" effect="dark" placement="top"><span>{{item.columnName | omit(30, '...')}}</span></el-tooltip> <span class="ky-option-sub-info">{{item.datatype.toLocaleLowerCase()}}</span> </el-option> </el-option-group> </el-select> <common-tip :content="$t('addCCTip')"><el-button size="medium" icon="el-ksd-icon-auto_computed_column_old" type="primary" plain class="ksd-ml-6" @click="newCC2" :disabled="(isCCEdit2 && ccVisible2) || !!isHybridModel"></el-button></common-tip> </div> </el-form-item> <CCEditForm class="ksd-mb-8" :class="{'error-tips': corrColumnError}" key="corrEditForm" ref="corrEditForm" v-if="ccVisible2" @checkSuccess="saveCC2" @delSuccess="delCC2" :hideCancel="isEditMeasure" :isEditMeasureCC="!isCCEdit2" source="createMeasure" :ccDesc="ccObject2" :modelInstance="modelInstance" @resetSubmitLoading="resetSubmitType" @saveError="resetSubmitType"></CCEditForm> <div class="error-tips" v-if="corrColumnError">{{$t('corrColDatatypeError')}}</div> </template> <template v-if="measure.expression ==='SUM_LC'"> <el-form-item prop="convertedColumns[0].value" :rules="rules.convertedColValidate" key="SUM_LCItem"> <span slot="label" class="withIconLabel"><span>{{$t('timeDim')}}</span><el-tooltip effect="dark" placement="right"><span slot="content" v-html="$t('timeDimTips')"></span><i class="el-ksd-icon-more_info_16 icon ksd-ml-2"></i></el-tooltip> </span> <el-select class="measures-addCC" size="medium" v-model="measure.convertedColumns[0].value" :placeholder="$t('kylinLang.common.pleaseSelectOrSearch')" filterable> <i slot="prefix" class="el-input__icon el-ksd-icon-search_22" v-if="!measure.convertedColumns[0].value"></i> <el-option-group key="column" :label="$t('columns')"> <el-option v-for="(item, index) in getParameterValue2" :key="index" :label="item.name" :value="item.name"> <el-tooltip :content="item.name" effect="dark" placement="top"><span>{{item.name.split('.')[item.name.split('.').length - 1] | omit(30, '...')}}</span></el-tooltip> <span class="ky-option-sub-info">{{item.datatype.toLocaleLowerCase()}}</span> </el-option> </el-option-group> <el-option-group key="ccolumn" :label="$t('ccolumns')" v-if="getCCGroups(null, true).length || newCCList.length"> <el-option v-for="item in getCCGroups(null, true)" :key="item.guid" :label="item.tableAlias + '.' + item.columnName" :value="item.tableAlias + '.' + item.columnName"> <el-tooltip :content="`${item.tableAlias}.${item.columnName}`" effect="dark" placement="top"><span>{{item.columnName | omit(30, '...')}}</span></el-tooltip> <span class="ky-option-sub-info">{{item.datatype.toLocaleLowerCase()}}</span> </el-option> <el-option v-for="item in newCCList" :key="item.table_guid" :label="item.tableAlias + '.' + item.columnName" :value="item.tableAlias + '.' + item.columnName"> <el-tooltip :content="`${item.tableAlias}.${item.columnName}`" effect="dark" placement="top"><span>{{item.columnName | omit(30, '...')}}</span></el-tooltip> <span class="ky-option-sub-info">{{item.datatype.toLocaleLowerCase()}}</span> </el-option> </el-option-group> </el-select> <common-tip :content="$t('addCCTip')"><el-button size="medium" icon="el-ksd-icon-auto_computed_column_old" type="primary" plain class="ksd-ml-6" @click="newCC2" :disabled="(isCCEdit2 && ccVisible2) || !!isHybridModel"></el-button></common-tip> </el-form-item> <CCEditForm class="ksd-mb-8" :class="{'error-tips': corrColumnError}" key="corrEditForm" ref="corrEditForm" v-if="ccVisible2" @checkSuccess="saveCC2" @delSuccess="delCC2" :hideCancel="isEditMeasure" :isEditMeasureCC="!isCCEdit2" source="createMeasure" :ccDesc="ccObject2" :modelInstance="modelInstance" @resetSubmitLoading="resetSubmitType" @saveError="resetSubmitType"></CCEditForm> <el-form-item :label="$t('AggreFunc')"> <el-input value="LastChild" disabled></el-input> </el-form-item> </template> <el-form-item :label="$t('comment')" prop="comment"> <div> <el-input class="measures-width measure-comment-input" size="medium" v-model.trim="measure.comment" :placeholder="$t('kylinLang.common.pleaseInput')"></el-input> </div> </el-form-item> </el-form> <el-alert :title="measureError" v-if="measureError" class="ksd-mt-16" type="error" show-icon></el-alert> <span slot="footer" class="dialog-footer ky-no-br-space"> <el-button plain size="medium" @click="handleHide(false)">{{$t('kylinLang.common.cancel')}}</el-button> <el-button type="primary" size="medium" @click="checkMeasure" :disabled="sameGroupBy()" :loading="loadingSubmit">{{$t('kylinLang.common.submit')}}</el-button> </span> </el-dialog> </template> <script> import Vue from 'vue' import { Component, Watch } from 'vue-property-decorator' import { measuresDataType, measureSumAndTopNDataType, measurePercenDataType, measuresDateTimeDataType } from '../../../../config' import { objectClone, sampleGuid, indexOfObjWithSomeKey, handleSuccessAsync } from '../../../../util/index' import { measureNameRegex } from 'config' import CCEditForm from '../ComputedColumnForm/ccform.vue' import { mapGetters, mapActions, mapState } from 'vuex' import $ from 'jquery' @Component({ props: { isEditMeasure: { type: Boolean, default: false }, measureObj: { type: Object, default () { return {} } }, modelInstance: { type: Object }, isHybridModel: { type: [Boolean, String] } }, computed: { ...mapGetters([ 'dimMeasNameMaxLength' ]), ...mapState({ enableCheckName: state => state.system.enableCheckName }) }, methods: { // 后台接口请求 ...mapActions({ checkInternalMeasure: 'CHECK_INTERNAL_MEASURE' }) }, components: { CCEditForm }, locales: { 'en': { requiredName: 'The measure name is required.', name: 'Name', comment: 'Comment', expression: 'Function', return_type: 'Function Parameter', paramValue: 'Column', nameReuse: 'The name of measure can\'t be duplicated within the same model', requiredCCName: 'The column name is required.', requiredreturn_type: 'The function parameter is required.', requiredExpress: 'The function is required.', columns: 'Columns', ccolumns: 'Computed Columns', requiredParamValue: 'The column is Required.', addCCTip: 'Create Computed Column', editMeasureTitle: 'Edit Measure', addMeasureTitle: 'Add Measure', semiAdditive: 'Semi-additive(Beta)', semiAdditiveMsg1: 'Support Last Child semi-additive measure.For example, when a bank uses account transaction record data to count savings account balances, the normal summation is used for the non-time dimension, and for the time dimension, the value of the last record needs to be taken. Please refer to product manual for details.', semiAdditiveMsg2: 'Two parameters need to be set.Column 1 represents aggregation value and Column 2 represents time dimension.', semiAdditiveMsg3: 'When query,you need to use SUM_LC(column1,column2) function in SQL.', timeDim: 'Time Dimension', AggreFunc: 'Semi-Additive Function', requiredTimeColumn: 'Time Dimension is required.', sameColumn: 'Column has been defined as a measure by the same function', selectMutipleColumnsTip: 'The {expression} function supports only one column when the function parameter is {params}.', createCCMeasureTips: 'This column’s type is {datatype}. It couldn’t be referenced by the selected function {expression}.', duplicateColumns: 'Same Statement', noProvideDecimalType: 'PERCENTILE function does not support columns with {datatype} data type.', syncCommentTip1: 'This column’s comment is "{comment}", ', syncContent: 'sync to the comment', corrTips: '* The supported column data types are: bigint, integer, tinyint, smallint, decimal, double and float.<br/>* If the data type of one of the columns is decimal, the other one\'s also needs to be decimal.', corrColDatatypeError: 'The data type of one of the columns is decimal, the other one\'s also needs to be decimal.', nameValid: 'Only supports Chinese or English characters, numbers, spaces and symbol(_ -()%?.)', sumLcTips: 'The first column is used for aggregation and it supports all numeric data types.', timeDimTips: 'Time dimension supports Timestamp,INT,BIGINT, String,Varchar and Char' } } }) export default class AddMeasure extends Vue { measureVisible = false reuseColumn = '' measureTitle = '' measure = { name: '', expression: 'SUM(column)', parameterValue: {type: 'column', value: '', table_guid: null}, convertedColumns: [], return_type: '', comment: '', nameBackUp: '' } syncComment = false ccObject = null ccObject2 = null isEdit = false isCCEdit2 = false ccVisible = false ccVisible2 = false ccGroups = [] newCCList = [] allTableColumns = [] topNTypes = [ {name: 'Top 10', value: 'topn(10)'}, {name: 'Top 100', value: 'topn(100)'}, {name: 'Top 1000', value: 'topn(1000)'} ] distinctDataTypes = [ {name: 'Error Rate < 9.75%', value: 'hllc(10)'}, {name: 'Error Rate < 4.88%', value: 'hllc(12)'}, {name: 'Error Rate < 2.44%', value: 'hllc(14)'}, {name: 'Error Rate < 1.72%', value: 'hllc(15)'}, {name: 'Error Rate < 1.22%', value: 'hllc(16)'}, {name: 'Precisely', value: 'bitmap'} ] percentileTypes = [ {name: 'percentile(100)', value: 'percentile(100)'}, {name: 'percentile(1000)', value: 'percentile(1000)'}, {name: 'percentile(10000)', value: 'percentile(10000)'} ] integerType = ['bigint', 'int', 'integer', 'smallint', 'tinyint'] floatType = ['decimal', 'double', 'float'] otherType = ['binary', 'boolean', 'char', 'date', 'string', 'timestamp', 'varchar'] showMutipleColumnsTip = false ccValidateError = false loadingSubmit = false corrColumnError = false measureError = '' get expressionsConfigs () { if (!this.modelInstance) return [] const config = [ {label: 'SUM (column)', value: 'SUM(column)'}, {label: 'SUM (constant)', value: 'SUM(constant)'}, {label: 'SUM_LC (column1, column2)', value: 'SUM_LC'}, {label: 'MIN', value: 'MIN'}, {label: 'MAX', value: 'MAX'}, {label: 'TOP_N', value: 'TOP_N'}, {label: 'COUNT (column)', value: 'COUNT(column)'}, // {label: 'COUNT (constant)', value: 'COUNT(constant)'}, 去除 count(constant) 函数,默认添加 count_all 度量 {label: 'COUNT_DISTINCT', value: 'COUNT_DISTINCT'}, {label: 'CORR (column1, column2)', value: 'CORR'}, {label: 'PERCENTILE_APPROX', value: 'PERCENTILE_APPROX'}, {label: 'COLLECT_SET', value: 'COLLECT_SET'} ] if (this.isHybridModel) { return config.filter(item => item.value !== 'CORR') } else { return config } } get rules () { return { name: [ { required: true, message: this.$t('requiredName'), trigger: 'blur' }, { validator: this.validateName, trigger: 'blur' } ], expression: [{ required: true, message: this.$t('requiredExpress'), trigger: 'change' }], 'parameterValue.value': [ { required: true, message: this.$t('requiredParamValue'), trigger: 'change' }, { validator: this.checkColumn } ], convertedColValidate: [{ required: true, message: this.$t('requiredParamValue'), trigger: 'blur, change' }, { validator: this.checkColumn }] } } get ccRules () { return { name: [{ required: true, message: this.$t('requiredCCName'), trigger: 'blur' }], return_type: [{ required: true, message: this.$t('requiredreturn_type'), trigger: 'change' }], expression: [{ required: true, message: this.$t('requiredExpress'), trigger: 'change' }] } } getCCGroups (isGroupBy, isSumlc) { if (this.ccGroups.length) { if (this.measure.expression === 'SUM(column)' || (this.measure.expression === 'SUM_LC' && !isSumlc) || this.measure.expression === 'CORR') { return this.ccGroups.filter(it => measureSumAndTopNDataType.includes(it.datatype.toLocaleLowerCase().match(/^(\w+)\(?/)[1])) } else if (this.measure.expression === 'TOP_N') { if (isGroupBy && isGroupBy === 'Group by') { return this.ccGroups.filter(it => measuresDataType.includes(it.datatype.toLocaleLowerCase().match(/^(\w+)\(?/)[1])) } else { return this.ccGroups.filter(it => measureSumAndTopNDataType.includes(it.datatype.toLocaleLowerCase().match(/^(\w+)\(?/)[1])) } } else if (this.measure.expression === 'PERCENTILE_APPROX') { return this.ccGroups.filter(item => measurePercenDataType.includes(item.datatype.toLocaleLowerCase().match(/^(\w+)\(?/)[1])) } else if (this.measure.expression === 'SUM_LC' && isSumlc) { return this.ccGroups.filter(it => measuresDateTimeDataType.includes(it.datatype.toLocaleLowerCase().match(/^(\w+)\(?/)[1])) } else { return this.ccGroups } } return [] } get flattenLookupTables () { return this.modelInstance?.anti_flatten_lookups ?? [] } get showSync () { if (!this.measure.parameterValue.value || this.ccVisible || ['SUM(constant)', 'COUNT_DISTINCT'].includes(this.measure.expression)) return false return this.columnComment(this.measure.parameterValue.value) } // 获取列的注释 columnComment (value) { const alias = value.split('.')[0] const nTable = this.modelInstance.getTableByAlias(alias) const currentColumnInfo = nTable.columns.filter(item => item.full_colname === value) return currentColumnInfo.length ? currentColumnInfo[0].comment || '' : '' } sameGroupBy () { return this.measure.expression === 'TOP_N' && this.measure.convertedColumns.filter(it => it.sameIndex).length > 0 } validateName (rule, value, callback) { if (!value) { callback(new Error(this.$t('requiredName'))) } else if (value.length > this.dimMeasNameMaxLength) { callback(new Error(this.$t('kylinLang.common.nameMaxLen', {len: this.dimMeasNameMaxLength}))) } else { if (!measureNameRegex.test(value)) { callback(new Error(this.$t('nameValid'))) } if (this.modelInstance.all_measures.length) { let isResuse = false for (let i = 0; i < this.modelInstance.all_measures.length; i++) { /* if (this.modelInstance.all_measures[i].name.toLocaleUpperCase() === this.measure.name.toLocaleUpperCase() && this.measure.guid !== this.modelInstance.all_measures[i].guid) { isResuse = true break } */ // 大小写敏感 if (this.modelInstance.all_measures[i].name === this.measure.name && this.measure.guid !== this.modelInstance.all_measures[i].guid) { isResuse = true break } } if (isResuse) { callback(new Error(this.$t('nameReuse'))) } else { callback() } } else { callback() } } } checkColumn (rule, value, callback) { // TOP_N, SUM_LC 度量无需判断列是否已经被使用过了 // CORR 度量需要判断两个列都选择重复后报错 if (this.measure.expression === 'TOP_N' || this.measure.expression === 'SUM_LC' || this.measure.expression === 'CORR' && (!this.measure.parameterValue.value || !this.measure.convertedColumns[0].value)) { callback() return } if (!this.modelInstance.checkSameEditMeasureColumn(this.measure)) { callback(new Error(this.$t('sameColumn'))) } else { callback() } } // 取消之前的约定,不用再统一传大写 /* upperCaseName () { this.measure.name = this.measure.name.toLocaleUpperCase() } */ changeExpression () { this.measure.return_type = '' this.measure.parameterValue.value = '' this.measure.comment = '' this.ccVisible2 = false this.ccVisible = false this.isEdit = false this.isCCEdit2 = false this.showMutipleColumnsTip = false this.measureError = '' this.$refs['measureForm'].clearValidate('parameterValue.value') this.$refs['measureForm'].clearValidate('convertedColumns[0].value') this.corrColumnError = '' if (this.measure.expression === 'SUM(constant)' || this.measure.expression === 'COUNT(constant)') { this.measure.parameterValue.type = 'constant' this.measure.parameterValue.value = '1' } else { this.measure.parameterValue.type = 'column' this.measure.parameterValue.value = '' } if (this.measure.expression === 'COUNT_DISTINCT') { this.measure.return_type = 'hllc(10)' } if (this.measure.expression === 'PERCENTILE_APPROX') { this.measure.return_type = 'percentile(100)' } if (this.measure.expression === 'TOP_N') { this.measure.return_type = 'topn(100)' } if (['CORR', 'TOP_N', 'SUM_LC'].indexOf(this.measure.expression) >= 0) { this.measure.convertedColumns = [{type: 'column', value: '', table_guid: null}] } else { this.measure.convertedColumns = [] } } changeReturnType () { this.showMutipleColumnsTip = false if (this.measure.expression === 'COUNT_DISTINCT') { this.measure.convertedColumns = [] this.measure.parameterValue.value = '' } } addNewProperty () { const GroupBy = {type: 'column', value: '', table_guid: null} this.measure.convertedColumns.push(GroupBy) } deleteProperty (index) { this.measure.convertedColumns.splice(index, 1) this.checkSameGroupByColumns() } getCCObj (value) { const measureNamed = value.split('.') const alias = measureNamed[0] const column = measureNamed[1] const ccObj = this.modelInstance.getCCObj(alias, column) return ccObj } changeParamValue (value, measure) { this.$refs['measureForm'] && measure.expression === 'CORR' && this.$refs['measureForm'].clearValidate('convertedColumns[0].value') const alias = value.split('.')[0] const nTable = this.modelInstance.getTableByAlias(alias) measure.parameterValue.table_guid = nTable && nTable.guid // const currentColumnInfo = nTable.columns.filter(item => item.full_colname === value) const ccObj = this.getCCObj(value) if (ccObj) { this.ccObject = ccObj this.ccVisible = true this.isEdit = false } else { this.ccVisible = false this.isEdit = false } // measure.comment = currentColumnInfo.length ? currentColumnInfo[0].comment || '' : '' measure.expression === 'SUM(column)' && (measure.return_type = '') this.syncComment = false this.corrColumnError = false this.measureError = '' this.$nextTick(() => { this.$refs['measureForm'] && measure.expression === 'CORR' && measure.convertedColumns[0].value && this.$refs['measureForm'].validateField('convertedColumns[0].value') }) } changeConColParamValue (value, index) { const alias = value.split('.')[0] const nTable = this.modelInstance.getTableByAlias(alias) this.measure.convertedColumns[index].table_guid = nTable && nTable.guid this.checkSameGroupByColumns() } changeCORRParamValue (value, measure) { this.$refs['measureForm'] && this.$refs['measureForm'].clearValidate('parameterValue.value') const alias = value.split('.')[0] const nTable = this.modelInstance.getTableByAlias(alias) measure.convertedColumns[0].table_guid = nTable && nTable.guid const ccObj = this.getCCObj(value) if (ccObj) { this.ccObject2 = ccObj this.ccVisible2 = true this.isCCEdit2 = false } else { this.ccVisible2 = false this.isCCEdit2 = false } this.corrColumnError = false this.measureError = '' this.$nextTick(() => { this.$refs['measureForm'] && this.$refs['measureForm'].validateField('parameterValue.value') }) } checkSameGroupByColumns () { if (this.measure.expression === 'TOP_N') { const columnList = this.measure.convertedColumns.map(it => it.value) this.measure.convertedColumns.forEach((item, index, self) => { item.sameIndex = columnList.indexOf(item.value) !== columnList.lastIndexOf(item.value) }) } } resetCCVisble () { this.isEdit = false this.isCCEdit2 = false this.ccVisible = false this.ccVisible2 = false this.ccObject = null this.ccObject2 = null this.ccValidateError = false this.corrColumnError = false } newCC () { this.resetCCVisble() this.measure.parameterValue.value = '' this.measure.return_type = '' this.isEdit = true this.ccVisible = true } newCC2 () { this.resetCCVisble() this.measure.convertedColumns[0].value = '' this.isCCEdit2 = true this.ccVisible2 = true } saveCC (cc) { this.ccObject = cc this.measure.parameterValue.value = cc.tableAlias + '.' + cc.columnName // this.isEdit = false this.checkNewCC(cc, 'ccEditForm') } saveCC2 (cc) { this.measure.convertedColumns[0].value = cc.tableAlias + '.' + cc.columnName this.ccObject2 = cc // this.isCCEdit2 = false this.checkNewCC(cc, 'corrEditForm') } checkNewCC (cc, ref) { this.$nextTick(() => { if (!measureSumAndTopNDataType.includes(cc.datatype.match(/^(\w+)\(?/)[1].toLocaleLowerCase()) && (['SUM(column)', 'CORR'].includes(this.measure.expression))) { this.$refs[ref] && (this.$refs[ref].isEdit = true) this.$refs[ref] && (this.$refs[ref].errorMsg = this.$t('createCCMeasureTips', {expression: this.measure.expression, datatype: cc.datatype})) this.ccValidateError = true } else if (!measurePercenDataType.includes(cc.datatype.toLocaleLowerCase()) && this.measure.expression === 'PERCENTILE_APPROX') { this.$refs[ref] && (this.$refs[ref].isEdit = true) this.$refs[ref] && (this.$refs[ref].errorMsg = this.$t('noProvideDecimalType', {datatype: cc.datatype.match(/^(\w+)\(?/)[1].toLocaleLowerCase()})) this.ccValidateError = true } else { this.$refs[ref] && (this.$refs[ref].isEdit = false) this.ccValidateError = false } if (!this.ccValidateError) { cc.name = cc.tableAlias + '.' + cc.columnName const index = indexOfObjWithSomeKey(this.newCCList, 'name', cc.name) index === -1 && this.newCCList.push(cc) } }) } delCC (cc) { this.measure.parameterValue.value = '' this.ccVisible = false this.isEdit = false this.corrColumnError = false } delCC2 (cc) { this.measure.convertedColumns[0].value = '' this.ccVisible2 = false this.isCCEdit2 = false this.corrColumnError = false } get isOrderBy () { if (this.measure.expression === 'TOP_N') { return 'Order/ Sum by' } else { return this.$t('paramValue') } } get isGroupBy () { if (this.measure.expression === 'TOP_N') { return 'Group by' } else { return '' } } get getSelectDataType () { if (this.measure.expression === 'TOP_N') { return this.topNTypes } if (this.measure.expression === 'COUNT_DISTINCT') { return this.distinctDataTypes } if (this.measure.expression === 'PERCENTILE_APPROX') { return this.percentileTypes } } get getParameterValue () { let targetColumns = [] let filterType = [] if (this.allTableColumns) { if (['SUM(column)', 'SUM_LC', 'TOP_N', 'CORR'].includes(this.measure.expression)) { filterType = measureSumAndTopNDataType } else if (this.measure.expression === 'PERCENTILE_APPROX') { filterType = measurePercenDataType } else { filterType = measuresDataType } $.each(this.allTableColumns, (index, column) => { const returnRegex = new RegExp('(\\w+)(?:\\((\\w+?)(?:\\,(\\w+?))?\\))?') const returnValue = returnRegex.exec(column.datatype) if (filterType.indexOf(returnValue[1]) >= 0 && !this.flattenLookupTables.includes(column.table_alias)) { const columnObj = {name: column.table_alias + '.' + column.name, datatype: column.datatype} targetColumns.push(columnObj) } }) } return targetColumns } // 支持measure的任意类型 get getParameterValue2 () { let targetColumns = [] let filterType = measuresDataType if (this.measure.expression === 'SUM_LC') { filterType = measuresDateTimeDataType } $.each(this.allTableColumns, (index, column) => { const returnRegex = new RegExp('(\\w+)(?:\\((\\w+?)(?:\\,(\\w+?))?\\))?') const returnValue = returnRegex.exec(column.datatype) if (filterType.indexOf(returnValue[1]) >= 0 && !this.flattenLookupTables.includes(column.table_alias)) { const columnObj = {name: column.table_alias + '.' + column.name, datatype: column.datatype} targetColumns.push(columnObj) } }) return targetColumns } handleHide (isSubmit, measure, isEdit, fromSearch) { this.measureVisible = false this.ccValidateError = false this.loadingSubmit = false this.newCCList = [] this.$emit('closeAddMeasureDia', { isEdit: isEdit, fromSearch: fromSearch, data: measure, isSubmit: isSubmit }) this.$refs['measureForm'].resetFields() } saveCCColumn () { this.newCCList.forEach(c => { this.modelInstance.addCC(c) }) } async checkInterMea (measure, cclist) { this.measureError = '' return this.modelInstance.generateMetadata().then(async (data) => { try { // 新建measure的模型功校验measure接口使用 let resData = objectClone(data) if (!this.isEditMeasure) { resData.simplified_measures.push(measure) } else { const name = this.measureObj.name const index = resData.simplified_measures.findIndex(it => it.name === name) index >= 0 && (resData.simplified_measures.splice(index, 1, measure)) } cclist.length > 0 && (resData.computed_columns = [...resData.computed_columns, ...cclist]) const res = await this.checkInternalMeasure(resData) const checkRespData = await handleSuccessAsync(res) return checkRespData } catch (res) { this.measureError = res.data.msg return false } }) } async checkMeasure () { try { this.loadingSubmit = true // 创建measure 和 cc 整合,提交的时候统一检测 cc 是否符合规范,不再分看执行 // if (this.ccVisible && this.isEdit) { // // this.$refs.ccEditForm && await this.$refs.ccEditForm.addCC() // if (this.ccValidateError) { // this.loadingSubmit = false // return // } // } this.$refs.measureForm.validate(async (valid) => { if (valid) { // 老数据有多列但函数参数仅支持单列,此时提示错误信息 let message = '' this.loadingSubmit = false if (this.measure.expression === 'COUNT_DISTINCT' && this.measure.return_type === 'bitmap' && this.measure.convertedColumns.length) { this.showMutipleColumnsTip = true message = this.$t('selectMutipleColumnsTip', {expression: this.measure.expression, params: 'Precisely'}) } if (this.showMutipleColumnsTip) { this.$message({ type: 'error', message }) return } this.corrColumnError = false if (this.measure.expression === 'CORR') { const corr1Datatype = this.getDatatype(this.measure.parameterValue.value).toLocaleLowerCase().indexOf('decimal') >= 0 const corr2Datatype = this.getDatatype(this.measure.convertedColumns[0].value).toLocaleLowerCase().indexOf('decimal') >= 0 if ((corr1Datatype && !corr2Datatype) || (!corr1Datatype && corr2Datatype)) { this.corrColumnError = true return } } const measureClone = objectClone(this.measure) // 判断该操作是否属于搜索入口进来 let isFromSearchAciton = measureClone.fromSearch if (measureClone.expression.indexOf('SUM(') !== -1) { measureClone.expression = 'SUM' } if (measureClone.expression.indexOf('COUNT(constant)') !== -1 || measureClone.expression.indexOf('COUNT(column)') !== -1) { measureClone.expression = 'COUNT' } measureClone.convertedColumns.unshift(measureClone.parameterValue) measureClone.parameter_value = measureClone.convertedColumns delete measureClone.parameterValue delete measureClone.convertedColumns delete measureClone.fromSearch if (!this.isEditMeasure && measureClone.expression === 'SUM') { const name = measureClone.parameter_value.length ? measureClone.parameter_value[0].value : '' if (name) { const currentMeasure = [...this.allTableColumns, ...this.ccGroups].filter(it => it.full_colname === name) if (currentMeasure.length && !measureSumAndTopNDataType.includes(currentMeasure[0].datatype.match(/^(\w+)\(?/)[1].toLocaleLowerCase())) { this.$message({ type: 'error', closeOtherMessages: true, message: this.$t('createCCMeasureTips', {expression: measureClone.expression, datatype: currentMeasure[0].datatype}) }) return } } } // await this.checkInterMea(measureClone, this.newCCList) if (this.measureError) { return } this.saveCCColumn() let action = this.isEditMeasure ? 'editMeasure' : 'addMeasure' this.modelInstance[action](measureClone).then(() => { // this.resetMeasure() this.handleHide(true, measureClone, this.isEditMeasure, isFromSearchAciton) }) } else { this.loadingSubmit = false } }) } catch (e) { this.loadingSubmit = false } } getDatatype (colName) { const columns = [...this.getParameterValue, ...this.getCCGroups(), ...this.newCCList] const index = indexOfObjWithSomeKey(columns, 'name', colName) if (index >= 0) { return columns[index].datatype } } resetSubmitType () { this.loadingSubmit = false } resetMeasure () { this.measure = { name: '', expression: 'SUM(column)', parameterValue: {type: 'column', value: '', table_guid: null}, convertedColumns: [], return_type: '', comment: '', nameBackUp: '' } this.syncComment = false this.ccVisible = false this.isEdit = false this.ccVisible2 = false this.isCCEdit2 = false this.showMutipleColumnsTip = false this.measureError = '' } initExpression () { let measureObj = objectClone(this.measureObj) if (measureObj.parameter_value && measureObj.parameter_value.length) { measureObj.parameterValue = measureObj.parameter_value[0] measureObj.convertedColumns = measureObj.parameter_value.length > 1 ? measureObj.parameter_value.splice(1, measureObj.parameter_value.length - 1) : [] delete measureObj.parameter_value if (measureObj.parameterValue.type === 'column') { this.changeParamValue(measureObj.parameterValue.value, measureObj) } if (measureObj.expression === 'CORR' && measureObj.convertedColumns.length) { this.changeCORRParamValue(measureObj.convertedColumns[0].value, measureObj) } } if (measureObj.expression === 'SUM' || measureObj.expression === 'COUNT') { measureObj.expression = `${measureObj.expression}(${measureObj.parameterValue.type})` } this.measure = { ...measureObj } if (!this.isEditMeasure) { this.measure.guid = sampleGuid() } } created () { this.measureTitle = this.isEditMeasure ? 'editMeasureTitle' : 'addMeasureTitle' this.allTableColumns = this.modelInstance && this.modelInstance.getTableColumns() this.ccGroups = this.modelInstance.computed_columns.map(c => { c.name = c.tableAlias + '.' + c.columnName c.full_colname = c.tableAlias + '.' + c.columnName return c }) this.initExpression() } // @Watch('isShow') // onShowChange (val) { // this.measureVisible = val // if (this.measureVisible) { // this.measureTitle = this.isEditMeasure ? 'editMeasureTitle' : 'addMeasureTitle' // this.allTableColumns = this.modelInstance && this.modelInstance.getTableColumns() // this.ccGroups = this.modelInstance.computed_columns.map(c => { // c.name = c.tableAlias + '.' + c.columnName // return c // }) // this.initExpression() // } else { // this.resetMeasure() // } // } @Watch('measure.convertedColumns') onConvertedColumnsChange (val) { if (this.measure.expression === 'COUNT_DISTINCT' && this.measure.return_type === 'bitmap' && !val.length) { this.showMutipleColumnsTip = false } } // SUM 或 PERCENTILE_APPROX 度量不适用 varchar 列 handlerErrorTip (m) { return (m.expression.indexOf('SUM') >= 0 || m.expression === 'PERCENTILE_APPROX') && m.return_type && m.return_type.indexOf('varchar') >= 0 } handleSyncComment () { // if (!this.measure.parameterValue.table_guid) return // const expr = this.measure.expression.replace(/\(\w+\)$/, '') // if (!this.syncComment) { this.$set(this.measure, 'comment', this.columnComment(this.measure.parameterValue.value)) // this.measure.comment = this.columnComment(this.measure.parameterValue.value) // this.measure.nameBackUp = this.measure.name // this.measure.name = `${this.measure.comment.slice(0, 100)}${expr.toLocaleUpperCase() === 'SUM' ? '' : '_' + expr}` // } // else { // this.measure.name = this.measure.nameBackUp // } // this.syncComment = !this.syncComment } } </script> <style lang="less"> @import '../../../../assets/styles/variables.less'; .add-measure-modal { .el-dialog { width: 500px\0 !important; } } .add-measure { .semi-additive-desc { background: @ke-background-color-base-1; padding: 10px; box-sizing: border-box; line-height: 1.2; .point { margin-right: 5px; } } .el-form-item.cc-item, .el-form-item.is-error { margin-bottom: 8px; } .corr-item { margin-bottom: 16px; } .error-tips { color: @ke-color-danger; font-size: 12px; margin-bottom: 24px; } .measure-flex-row { display: flex; align-items: flex-start; .flex-item { flex-shrink: 1; width: 100%; .error-tip { input { border: 1px solid @error-color-1; } } } .error-text { font-size: 12px; color: @error-color-1; line-height: 1; } .el-button { margin-top: 5px; } } .tip_box { position: relative; bottom: 2px; } .sync-comment-tip { word-break: break-all; } .sync-content { color: @base-color; cursor: pointer; } .error-measure { .el-input__inner { border: 1px solid @error-color-1; } } .measures-width { width: 100%; } .measures-addCC { width: 379px; } .del-margin-more { margin-left: 37px !important; } .value-label { color: @text-title-color; } .el-button.is-disabled, .el-button--primary.is-plain.is-disabled { background-color: @grey-4; color: @line-border-color; .el-icon-minus { cursor: not-allowed; } :hover { background-color: @grey-4; color: @line-border-color; } } .measure-column-multiple { margin-top: -10px; } } .el-select-group__wrap { .el-select-dropdown__item { padding-left: 10px; } .el-select-group__title { padding-left: 10px; } &:not(:last-child) { &::after { left: 10px; right: 10px; } } } </style>