form.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. <template>
  2. <a-card :title="title" size="small" class="monitor-form" :class="{ 'hideBody': !panelShow }">
  3. <div slot="extra">
  4. <a-icon type="delete" v-if="showDelete" @click="remove" class="mr-2 remove-icon" />
  5. <a-icon :type="panelShow ? 'up' : 'down'" @click="toggle" />
  6. </div>
  7. <a-form
  8. v-show="panelShow"
  9. v-bind="formItemLayout"
  10. :form="form.fc">
  11. <a-form-item :label="$t('monitor.monitor_metric')" class="mb-0">
  12. <metric
  13. :form="form"
  14. :decorators="decorators"
  15. :res_type_measurements="res_type_measurements"
  16. :res_types="res_types"
  17. :loading="metricLoading"
  18. @metricChange="getMetricInfo"
  19. @metricClear="resetChart" />
  20. </a-form-item>
  21. <a-form-item :label="$t('monitor.monitor_filters')">
  22. <filters
  23. :form="form"
  24. ref="filtersRef"
  25. :decorators="decorators.filters"
  26. :init-filters="initFilters"
  27. @remove="$nextTick(toParams)"
  28. :loading="metricInfoLoading"
  29. :metricInfo="metricInfo"
  30. @tagValuesChange="tagValuesChange" />
  31. </a-form-item>
  32. <a-form-item :label="$t('monitor.monitor_group')">
  33. <base-select
  34. v-decorator="decorators.group_by"
  35. :options="groupbyOpts"
  36. @change="groupbyChange"
  37. class="w-100"
  38. :select-props="{ mode: 'multiple', placeholder: $t('monitor.text_114'), allowClear: true }" />
  39. </a-form-item>
  40. <a-form-item :label="$t('monitor.monitor_function')">
  41. <base-select
  42. v-decorator="decorators.function"
  43. :options="functionOpts"
  44. class="w-100"
  45. :select-props="{ placeholder: $t('monitor.text_115'), allowClear: allowClearGroupFunction }" />
  46. </a-form-item>
  47. <a-form-item :label="$t('monitor.monitor_result_function')">
  48. <base-select
  49. v-decorator="decorators.result_function"
  50. :options="resultFunctionOpts"
  51. class="w-100"
  52. :select-props="{ placeholder: $t('monitor.text_115'), allowClear: true }" />
  53. </a-form-item>
  54. <a-form-item v-if="form.fd.result_function === 'percentile'" :label="$t('monitor.monitor_percentile')">
  55. <a-input-number :min="1" :max="99" v-decorator="decorators.percentile" placeholder="1~99" />
  56. </a-form-item>
  57. <a-form-item :label="$t('common.name')" v-if="!queryOnly">
  58. <a-input v-decorator="decorators.name" :placeholder="$t('common.placeholder')" />
  59. </a-form-item>
  60. </a-form>
  61. </a-card>
  62. </template>
  63. <script>
  64. import _ from 'lodash'
  65. import * as R from 'ramda'
  66. import Metric from '@Monitor/sections/Metric'
  67. import Filters from '@Monitor/sections/Filters'
  68. import { metric_zh } from '@Monitor/constants'
  69. import { resolveValueChangeField } from '@/utils/common/ant'
  70. import { uuid, getRequestT } from '@/utils/utils'
  71. export default {
  72. name: 'ExplorerForm',
  73. components: {
  74. Metric,
  75. Filters,
  76. },
  77. props: {
  78. panel: {
  79. type: Object,
  80. default: () => ({}),
  81. },
  82. queryOnly: {
  83. type: Boolean,
  84. default: true,
  85. },
  86. formItemLayout: {
  87. type: Object,
  88. default: () => ({
  89. wrapperCol: {
  90. span: 21,
  91. },
  92. labelCol: {
  93. span: 3,
  94. },
  95. }),
  96. },
  97. showDelete: {
  98. type: Boolean,
  99. default: false,
  100. },
  101. defaultPanelShow: {
  102. type: Boolean,
  103. },
  104. timeRangeParams: {
  105. type: Object,
  106. default: () => ({}),
  107. },
  108. extraParams: {
  109. type: Object,
  110. default: () => ({}),
  111. },
  112. },
  113. data () {
  114. const initialValue = {}
  115. const initFilters = []
  116. const initTagOperators = {}
  117. if (this.panel && ((this.panel.common_alert_metric_details && this.panel.common_alert_metric_details.length > 0) || (this.panel?.setting && this.panel.setting.conditions))) {
  118. const f = this.panel?.common_alert_metric_details?.[0] || this.panel?.setting?.conditions?.[0]?.query?.model
  119. const q = _.get(this.panel, 'settings.conditions[0].query.model') || this.panel?.setting?.conditions?.[0]?.query?.model
  120. const r = _.get(this.panel, 'settings.conditions[0].query.result_reducer') || this.panel?.setting?.conditions?.[0]?.query?.result_reducer
  121. initialValue.name = this.panel.name || this.panel.panel_name || ''
  122. initialValue.res_type = f.res_type || this.panel?.common_alert_metric_details?.[0]?.res_type
  123. initialValue.metric_key = f.measurement || this.panel?.common_alert_metric_details?.[0]?.measurement
  124. initialValue.metric_value = f.field
  125. initialValue.tags = {}
  126. if (q) {
  127. if (q.group_by) {
  128. const _g = q.group_by.filter((g) => { return g.type === 'tag' && g.params && g.params[0] })
  129. if (_g.length) {
  130. initialValue.group_by = _g.map(item => {
  131. return item.params[0]
  132. })
  133. }
  134. }
  135. if (q.tags) {
  136. q.tags.map((tag) => {
  137. const key = uuid()
  138. initialValue.tags[key] = tag
  139. initFilters.push({ key: key, tagValueOpts: [] })
  140. initTagOperators[key] = tag.operator
  141. })
  142. }
  143. initialValue.function = ''
  144. if (q.select && q.select.length > 0 && q.select[0].length > 1) {
  145. const s = q.select[0]
  146. const _t = s[s.length - 1].type
  147. if (_t) initialValue.function = _t.toUpperCase()
  148. }
  149. }
  150. if (r) {
  151. initialValue.result_function = r.type
  152. if (r.params && r.params.length) {
  153. if (r.params[0] === 50) {
  154. initialValue.result_function = 'p50'
  155. } else if (r.params[0] === 95) {
  156. initialValue.result_function = 'p95'
  157. } else {
  158. initialValue.percentile = r.params[0]
  159. }
  160. }
  161. }
  162. }
  163. const getkey = (index, key, defaultValue) => {
  164. if (initialValue.tags && initialValue.tags[index] && initialValue.tags[index][key]) {
  165. if (key === 'value') {
  166. const v = initialValue.tags[index][key]
  167. if (v.startsWith('/^') && v.endsWith('$/')) {
  168. const values = v.replace('/^', '').replace('$/', '').split('|').map(item => {
  169. let k = item
  170. if (k.startsWith('^')) k = k.replace('^', '')
  171. if (k.endsWith('$')) k = k.replace('$', '')
  172. return k
  173. })
  174. return values
  175. } else {
  176. return [v]
  177. }
  178. }
  179. if (key === 'operator') {
  180. const v = initialValue.tags[index][key]
  181. if (v === '=') return '=~'
  182. if (v === '!=') return '!~'
  183. }
  184. return initialValue.tags[index][key]
  185. } else {
  186. return defaultValue
  187. }
  188. }
  189. const decorators = {
  190. metric_res_type: [
  191. 'metric_res_type',
  192. {
  193. initialValue: initialValue.res_type,
  194. rules: [
  195. { required: true, message: this.$t('common.select') },
  196. ],
  197. },
  198. ],
  199. metric_key: [
  200. 'metric_key',
  201. {
  202. initialValue: initialValue.metric_key,
  203. rules: [
  204. { required: true, message: this.$t('common.select') },
  205. ],
  206. },
  207. ],
  208. metric_value: [
  209. 'metric_value',
  210. {
  211. initialValue: initialValue.metric_value,
  212. rules: [
  213. { required: true, message: this.$t('common.select') },
  214. ],
  215. },
  216. ],
  217. filters: {
  218. tagCondition: i => [
  219. `tagConditions[${i}]`,
  220. {
  221. initialValue: getkey(i, 'condition', 'AND'),
  222. rules: [
  223. { required: true, message: this.$t('common.select') },
  224. ],
  225. },
  226. ],
  227. tagKey: i => [
  228. `tagKeys[${i}]`,
  229. {
  230. initialValue: getkey(i, 'key', ''),
  231. rules: [
  232. // { required: true, message: this.$t('common.select') },
  233. ],
  234. },
  235. ],
  236. tagOperator: i => [
  237. `tagOperators[${i}]`,
  238. {
  239. initialValue: getkey(i, 'operator', '=~'),
  240. rules: [
  241. { required: true, message: this.$t('common.select') },
  242. ],
  243. },
  244. ],
  245. tagValue: i => [
  246. `tagValues[${i}]`,
  247. {
  248. initialValue: getkey(i, 'value', []),
  249. rules: [
  250. // { required: true, message: this.$t('common.select') },
  251. ],
  252. },
  253. ],
  254. },
  255. group_by: [
  256. 'group_by',
  257. {
  258. initialValue: initialValue.group_by || [],
  259. },
  260. ],
  261. function: [
  262. 'function',
  263. {
  264. initialValue: initialValue.function,
  265. },
  266. ],
  267. result_function: [
  268. 'result_function',
  269. {
  270. initialValue: initialValue.result_function,
  271. },
  272. ],
  273. percentile: [
  274. 'percentile',
  275. {
  276. initialValue: initialValue.percentile,
  277. rules: [{ required: true, message: this.$t('common.tips.input', [this.$t('monitor.monitor_percentile')]) }],
  278. },
  279. ],
  280. }
  281. if (!this.queryOnly) {
  282. decorators.name = [
  283. 'name',
  284. {
  285. initialValue: initialValue.name || '',
  286. rules: [
  287. { required: true, message: `${this.$t('common.placeholder')}${this.$t('common.name')}` },
  288. ],
  289. },
  290. ]
  291. }
  292. return {
  293. prevName: initialValue.name,
  294. form: {
  295. fc: this.$form.createForm(this, {
  296. onValuesChange: this.onValuesChange,
  297. }),
  298. fd: {
  299. result_function: initialValue.result_function,
  300. tagOperators: initTagOperators,
  301. },
  302. },
  303. decorators: decorators,
  304. initFilters: initFilters,
  305. groupbyOpts: [],
  306. functionOpts: [],
  307. resultFunctionOpts: [
  308. {
  309. key: 'p50',
  310. label: 'P50',
  311. },
  312. {
  313. key: 'p95',
  314. label: 'P95',
  315. },
  316. {
  317. key: 'min',
  318. label: 'MIN',
  319. },
  320. {
  321. key: 'max',
  322. label: 'MAX',
  323. },
  324. {
  325. key: 'avg',
  326. label: 'MEAN',
  327. },
  328. {
  329. key: 'sum',
  330. label: 'SUM',
  331. },
  332. {
  333. key: 'count',
  334. label: 'COUNT',
  335. },
  336. {
  337. key: 'percentile',
  338. label: 'PERCENTILE',
  339. },
  340. ],
  341. metricKeyOpts: [],
  342. metricInfo: {},
  343. metricKeyItem: {},
  344. mertricItem: {},
  345. panelShow: this.defaultPanelShow,
  346. oldParams: {},
  347. oldResParams: {},
  348. metricLoading: false,
  349. metricInfoLoading: false,
  350. res_type_measurements: {},
  351. res_types: [],
  352. allowClearGroupFunction: true,
  353. }
  354. },
  355. computed: {
  356. title () {
  357. if (!this.panelShow && this.form.fd.metric_key) {
  358. return this.getTitle()
  359. }
  360. return this.$t('monitor.monitor_fill_filters')
  361. },
  362. },
  363. watch: {
  364. defaultPanelShow (val) {
  365. this.panelShow = val
  366. },
  367. timeRangeParams () {
  368. this.getMeasurement()
  369. },
  370. },
  371. created () {
  372. this.getMeasurement()
  373. },
  374. methods: {
  375. getTitle () {
  376. let padding = ' '
  377. if (this.$store.getters.setting.language === 'zh-CN') {
  378. padding = ''
  379. }
  380. let label = this.metricKeyItem && this.metricKeyItem.metric_res_type ? this.$t(`dictionary.${this.metricKeyItem.metric_res_type}`) + padding : ''
  381. label += this.metricKeyItem && this.metricKeyItem.label ? this.metricKeyItem.label : '-'
  382. const metricLabel = _.get(this.mertricItem, 'description.display_name')
  383. const metricName = _.get(this.mertricItem, 'description.name')
  384. if (metricLabel) {
  385. label += `(${metric_zh[metricLabel] ? metric_zh[metricLabel] + ' ' + metricName : metricLabel + ' ' + metricName})`
  386. }
  387. return label
  388. },
  389. resetChart () {
  390. this.$emit('resetChart')
  391. this.$refs.filtersRef.reset()
  392. },
  393. remove () {
  394. this.$emit('remove')
  395. },
  396. groupbyChange (a) {
  397. if (!a || a.length === 0) {
  398. this.form.fc.setFieldsValue({
  399. [this.decorators.function[0]]: '',
  400. })
  401. this.allowClearGroupFunction = true
  402. } else {
  403. this.allowClearGroupFunction = false
  404. if (this.functionOpts && this.functionOpts.length) {
  405. const mean = this.functionOpts.find(val => val.key.toLowerCase() === 'mean')
  406. this.form.fc.setFieldsValue({
  407. [this.decorators.function[0]]: mean.key,
  408. })
  409. }
  410. }
  411. },
  412. onValuesChange (props, values) {
  413. const newField = resolveValueChangeField(values)
  414. R.forEachObjIndexed((item, key) => {
  415. if (!['tagValues'].includes(key)) {
  416. if (R.is(Object, this.form.fd[key]) && R.is(Object, item)) {
  417. this.$set(this.form.fd, key, { ...this.form.fd[key], ...item })
  418. } else {
  419. this.$set(this.form.fd, key, item)
  420. }
  421. }
  422. }, newField)
  423. const changedKeys = Object.keys(values)
  424. if (changedKeys.length === 1 && changedKeys[0] === 'name') {
  425. this.$emit('nameChange', this.form.fd.name)
  426. return
  427. }
  428. if (changedKeys.length === 1 && changedKeys[0] === 'tagValues') {
  429. return
  430. }
  431. this.$nextTick(this.toParams)
  432. if ((values.hasOwnProperty('metric_key') && !values.metric_key) || (values.hasOwnProperty('metric_value') && !values.metric_value)) {
  433. this.resetChart()
  434. }
  435. },
  436. tagValuesChange (item) {
  437. this.$nextTick(this.toParams)
  438. const values = this.form.fc.getFieldsValue()
  439. if ((values.hasOwnProperty('metric_key') && !values.metric_key) || (values.hasOwnProperty('metric_value') && !values.metric_value)) {
  440. this.resetChart()
  441. }
  442. },
  443. async getMeasurement () {
  444. try {
  445. this.metricLoading = true
  446. const params = { scope: this.$store.getters.scope, ...this.timeRangeParams, ...this.extraParams }
  447. const { data: { res_type_measurements, res_types } } = await new this.$Manager('unifiedmonitors', 'v1').get({ id: 'measurements', params })
  448. this.res_type_measurements = res_type_measurements
  449. this.res_types = res_types
  450. this.metricLoading = false
  451. } catch (error) {
  452. this.metricLoading = false
  453. throw error
  454. }
  455. },
  456. async getMetricInfo ({ metricKey, mertric, mertricItem, metricKeyItem }) {
  457. try {
  458. this.metricKeyItem = metricKeyItem || {}
  459. this.mertricItem = mertricItem || {}
  460. const title = this.getTitle()
  461. if (this.prevName === '' || this.form.fd.name === this.prevName) {
  462. this.form.fc.setFieldsValue({ name: title })
  463. this.prevName = title
  464. }
  465. this.$emit('mertricItemChange', { ...mertricItem, title: title, metricKeyItem })
  466. const params = {
  467. $t: getRequestT(),
  468. database: metricKeyItem && metricKeyItem.database ? metricKeyItem.database : 'telegraf',
  469. measurement: metricKey,
  470. field: mertric,
  471. scope: this.$store.getters.scope,
  472. ...this.timeRangeParams,
  473. ...this.extraParams,
  474. }
  475. this.metricInfoLoading = true
  476. const { data } = await new this.$Manager('unifiedmonitors', 'v1').get({ id: 'metric-measurement', params })
  477. this.metricInfo = data
  478. if (R.is(Array, this.metricInfo.tag_key)) {
  479. this.groupbyOpts = this.metricInfo.tag_key.map(v => ({ key: v, label: v }))
  480. }
  481. const Aggregations = _.get(this.metricInfo, 'func.field_opt_value.Aggregations')
  482. if (R.is(Array, Aggregations)) {
  483. this.functionOpts = Aggregations.map(v => ({ key: v, label: this.$te(`monitor.func.${v}`) ? this.$t(`monitor.func.${v}`) : v }))
  484. }
  485. const { group_by: groupBy, function: func } = this.form.fc.getFieldsValue([this.decorators.group_by[0], this.decorators.function[0]])
  486. const resetFields = {}
  487. const resetGroupBy = groupBy.filter(key => (this.metricInfo?.tag_key || []).includes(key))
  488. resetFields[this.decorators.group_by[0]] = resetGroupBy
  489. if (!~(Aggregations || []).indexOf(func)) {
  490. resetFields[this.decorators.function[0]] = undefined
  491. }
  492. this.form.fc.setFieldsValue(resetFields)
  493. this.metricInfoLoading = false
  494. } catch (error) {
  495. this.metricInfo.tag_key = []
  496. this.metricInfo.func = {}
  497. this.metricInfoLoading = false
  498. throw error
  499. }
  500. },
  501. toggle () {
  502. this.panelShow = !this.panelShow
  503. },
  504. toParams (ignoreEmit) {
  505. const fd = this.form.fc.getFieldsValue()
  506. const params = {
  507. database: this.metricKeyItem && this.metricKeyItem.database ? this.metricKeyItem.database : 'telegraf',
  508. }
  509. const resParams = {}
  510. const tags = []
  511. if (!this.queryOnly) {
  512. let formErr
  513. this.form.fc.validateFieldsAndScroll({ scroll: { alignWithTop: true, offsetTop: 100 } }, (err, values) => { if (err) { formErr = err } })
  514. if (formErr) return
  515. params.name = fd.name
  516. }
  517. if (fd.metric_key) params.measurement = fd.metric_key
  518. if (fd.metric_value) params.select = [[{ type: 'field', params: [fd.metric_value] }]]
  519. if (R.is(Object, fd.tagValues)) {
  520. R.forEachObjIndexed((value, key) => {
  521. let val = value
  522. if ((fd.tagOperators[key] === '=~' || fd.tagOperators[key] === '!~') && val && val.length) {
  523. val = `/${val.map(v => `^${v}$`).join('|')}/`
  524. } else {
  525. val = R.is(Array, val) ? (val.length ? val[0] : '') : val
  526. }
  527. if (val) {
  528. const tag = {
  529. key: fd.tagKeys[key],
  530. value: val,
  531. operator: fd.tagOperators[key],
  532. }
  533. if (fd.tagConditions && fd.tagConditions[key]) {
  534. tag.condition = fd.tagConditions[key]
  535. }
  536. tags.push(tag)
  537. }
  538. }, fd.tagValues)
  539. }
  540. if (tags.length) {
  541. params.tags = tags
  542. }
  543. if ((R.is(Array, fd.group_by) && fd.group_by.length)) {
  544. params.group_by = fd.group_by.map(group_by => {
  545. return { type: 'tag', params: [group_by] }
  546. })
  547. }
  548. if (fd.result_function) {
  549. resParams.type = fd.result_function
  550. if (fd.result_function === 'percentile') {
  551. if (fd.percentile) {
  552. resParams.params = [Math.max(1, Math.min(fd.percentile, 99))]
  553. } else {
  554. delete resParams.params
  555. delete resParams.type
  556. }
  557. } else if (fd.result_function === 'p50') {
  558. resParams.type = 'percentile'
  559. resParams.params = [50]
  560. } else if (fd.result_function === 'p95') {
  561. resParams.type = 'percentile'
  562. resParams.params = [95]
  563. }
  564. }
  565. if (!fd.metric_key || !fd.metric_value) {
  566. this.oldParams = params
  567. return params
  568. }
  569. if (R.is(String, fd.function) && fd.function.length) {
  570. params.select[0].push({ type: fd.function.toLowerCase(), params: [] })
  571. }
  572. if (R.equals(this.oldParams, params) && R.equals(this.oldResParams, resParams)) return params
  573. this.$emit('paramsChange', params, resParams)
  574. this.oldResParams = resParams
  575. this.oldParams = params // 记录为上一次 params
  576. },
  577. },
  578. }
  579. </script>
  580. <style lang="less" scoped>
  581. @import '../../../../src/styles/less/theme';
  582. .monitor-form {
  583. &.hideBody ::v-deep .ant-card-body {
  584. padding: 0 !important;
  585. }
  586. .remove-icon {
  587. transition: color 0.1s ease-in;
  588. &:hover {
  589. color: @error-color;
  590. }
  591. }
  592. // 让 label 和 wrapper 基于父元素宽度,而不是固定的 span
  593. ::v-deep .ant-form-item {
  594. display: flex;
  595. flex-wrap: nowrap;
  596. align-items: flex-start;
  597. }
  598. ::v-deep .ant-form-item-label,
  599. ::v-deep .ant-form-item-label > label {
  600. width: 150px !important;
  601. flex: 0 0 150px !important;
  602. padding-right: 8px;
  603. }
  604. // // 覆盖 Ant Design 的栅格类
  605. ::v-deep .ant-col {
  606. width: auto !important;
  607. max-width: none !important;
  608. }
  609. ::v-deep .ant-form-item-control-wrapper {
  610. flex: 1 1 auto;
  611. width: auto !important;
  612. min-width: 0;
  613. max-width: 100%;
  614. }
  615. ::v-deep .ant-form-item-control {
  616. width: 100%;
  617. }
  618. }
  619. </style>