index.tsx 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955
  1. /*
  2. * <<
  3. * Davinci
  4. * ==
  5. * Copyright (C) 2016 - 2017 EDP
  6. * ==
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. * >>
  19. */
  20. import React from 'react'
  21. import { findDOMNode } from 'react-dom'
  22. import classnames from 'classnames'
  23. import { IChartProps } from '../'
  24. import { IChartStyles, IPaginationParams } from '../../Widget'
  25. import { ITableHeaderConfig } from 'containers/Widget/components/Config/Table'
  26. import { ResizeCallbackData } from 'libs/react-resizable'
  27. import { Table as AntTable, Tooltip, Icon } from 'antd'
  28. import { TableProps, ColumnProps, SorterResult } from 'antd/lib/table'
  29. import { PaginationConfig } from 'antd/lib/pagination/Pagination'
  30. import PaginationWithoutTotal from 'components/PaginationWithoutTotal'
  31. import { ViewModelTypes } from 'containers/View/constants'
  32. import { TABLE_PAGE_SIZES } from 'app/globalConstants'
  33. import { getFieldAlias } from 'containers/Widget/components/Config/Field'
  34. import { decodeMetricName } from 'containers/Widget/components/util'
  35. import Styles from './Table.less'
  36. import { hasProperty } from 'utils/util'
  37. import {
  38. findChildConfig,
  39. traverseConfig,
  40. computeCellWidth,
  41. getDataColumnWidth,
  42. getTableCellValueRange,
  43. getCellSpanMap
  44. } from './util'
  45. import { MapAntSortOrder } from './constants'
  46. import { FieldSortTypes } from '../../Config/Sort'
  47. import { tableComponents } from './components'
  48. import { resizeTableColumns } from './components/HeadCell'
  49. interface IMapTableHeaderConfig {
  50. [key: string]: ITableHeaderConfig
  51. }
  52. interface ISelectItemCellProps {
  53. index: number
  54. value: string
  55. key: string
  56. }
  57. interface ISelectItemsCell {
  58. [propName: string]: ISelectItemCellProps[]
  59. }
  60. interface ISelectItems {
  61. group: string[]
  62. cell: ISelectItemsCell
  63. }
  64. interface ITableStates {
  65. chartStyles: IChartStyles
  66. data: object[]
  67. width: number
  68. pagination: IPaginationParams
  69. currentSorter: { column: string; direction: FieldSortTypes }
  70. tableColumns: Array<ColumnProps<any>>
  71. mapTableHeaderConfig: IMapTableHeaderConfig
  72. containerWidthRatio: number
  73. tablePagination: {
  74. current: number
  75. pageSize: number
  76. simple: boolean
  77. total: number
  78. }
  79. selectedRow: object[]
  80. tableBodyHeight: number
  81. selectItems: ISelectItems
  82. }
  83. export class Table extends React.PureComponent<IChartProps, ITableStates> {
  84. private static HeaderSorterWidth = 0
  85. public state: Readonly<ITableStates> = {
  86. chartStyles: null,
  87. data: null,
  88. width: 0,
  89. pagination: null,
  90. currentSorter: null,
  91. tableColumns: [],
  92. mapTableHeaderConfig: {},
  93. containerWidthRatio: 1,
  94. tablePagination: {
  95. current: void 0,
  96. pageSize: void 0,
  97. simple: false,
  98. total: void 0
  99. },
  100. tableBodyHeight: 0,
  101. selectedRow: [],
  102. selectItems: {
  103. group: [],
  104. cell: {}
  105. }
  106. }
  107. private table = React.createRef<AntTable<any>>()
  108. private handleResize = (idx: number, containerWidthRatio: number) => (
  109. _,
  110. { size }: ResizeCallbackData
  111. ) => {
  112. const nextColumns = resizeTableColumns(
  113. this.state.tableColumns,
  114. idx,
  115. size.width,
  116. containerWidthRatio
  117. )
  118. this.setState({ tableColumns: nextColumns })
  119. }
  120. private tableChange = (
  121. pagination: PaginationConfig,
  122. _,
  123. sorter: SorterResult<object>
  124. ) => {
  125. const nextCurrentSorter: ITableStates['currentSorter'] = sorter.field
  126. ? { column: sorter.field, direction: MapAntSortOrder[sorter.order] }
  127. : null
  128. this.setState({ currentSorter: nextCurrentSorter })
  129. const { current, pageSize } = pagination
  130. this.refreshTable(current, pageSize, nextCurrentSorter)
  131. }
  132. private refreshTable = (
  133. current: number,
  134. pageSize: number,
  135. sorter?: ITableStates['currentSorter']
  136. ) => {
  137. const { tablePagination } = this.state
  138. if (pageSize !== tablePagination.pageSize) {
  139. current = 1
  140. }
  141. const { onPaginationChange } = this.props
  142. onPaginationChange(current, pageSize, sorter)
  143. }
  144. private basePagination: PaginationConfig = {
  145. pageSizeOptions: TABLE_PAGE_SIZES.map((s) => s.toString()),
  146. showQuickJumper: true,
  147. showSizeChanger: true,
  148. showTotal: (total: number) => `共${total}条`
  149. }
  150. public componentDidMount() {
  151. const { headerFixed, withPaging } = this.props.chartStyles.table
  152. this.adjustTableCell(headerFixed, withPaging)
  153. }
  154. public componentDidUpdate() {
  155. const { headerFixed, withPaging } = this.props.chartStyles.table
  156. this.adjustTableCell(
  157. headerFixed,
  158. withPaging,
  159. this.state.tablePagination.total
  160. )
  161. }
  162. private adjustTableCell(
  163. headerFixed: boolean,
  164. withPaging: boolean,
  165. dataTotal?: number
  166. ) {
  167. const tableDom = findDOMNode(this.table.current) as Element
  168. const excludeElems = []
  169. let paginationMargin = 0
  170. let paginationWithoutTotalHeight = 0
  171. if (headerFixed) {
  172. excludeElems.push('.ant-table-thead')
  173. }
  174. if (withPaging) {
  175. excludeElems.push('.ant-pagination.ant-table-pagination')
  176. paginationMargin = 32
  177. if (dataTotal === -1) {
  178. paginationWithoutTotalHeight = 45
  179. }
  180. }
  181. const excludeElemsHeight = excludeElems.reduce((acc, exp) => {
  182. const elem = tableDom.querySelector(exp)
  183. return acc + (elem ? elem.getBoundingClientRect().height : 0)
  184. }, paginationMargin)
  185. const tableBodyHeight =
  186. this.props.height - excludeElemsHeight - paginationWithoutTotalHeight
  187. this.setState({
  188. tableBodyHeight
  189. })
  190. }
  191. public static getDerivedStateFromProps(
  192. nextProps: IChartProps,
  193. prevState: ITableStates
  194. ) {
  195. const { chartStyles, data, width } = nextProps
  196. if (
  197. chartStyles !== prevState.chartStyles ||
  198. data !== prevState.data ||
  199. width !== prevState.width
  200. ) {
  201. const {
  202. tableColumns,
  203. mapTableHeaderConfig,
  204. containerWidthRatio
  205. } = getTableColumns(nextProps)
  206. const tablePagination = getPaginationOptions(nextProps)
  207. return {
  208. tableColumns,
  209. mapTableHeaderConfig,
  210. containerWidthRatio,
  211. tablePagination,
  212. chartStyles,
  213. data,
  214. width
  215. }
  216. }
  217. return { chartStyles, data, width }
  218. }
  219. private adjustTableColumns(
  220. tableColumns: Array<ColumnProps<any>>,
  221. mapTableHeaderConfig: IMapTableHeaderConfig,
  222. containerWidthRatio: number
  223. ) {
  224. traverseConfig<ColumnProps<any>>(
  225. tableColumns,
  226. 'children',
  227. (column, idx, siblings) => {
  228. const canResize = siblings === tableColumns
  229. column.onHeaderCell = (col) => ({
  230. width: col.width,
  231. onResize: canResize && this.handleResize(idx, containerWidthRatio),
  232. config: mapTableHeaderConfig[column.key]
  233. })
  234. }
  235. )
  236. return tableColumns
  237. }
  238. private getRowKey = (_, idx: number) => idx.toString()
  239. private getTableScroll(
  240. columns: Array<ColumnProps<any>>,
  241. containerWidth: number,
  242. headerFixed: boolean,
  243. tableBodyHeght: number
  244. ) {
  245. const scroll: TableProps<any>['scroll'] = {}
  246. const columnsTotalWidth = columns.reduce(
  247. (acc, c) => acc + (c.width as number),
  248. 0
  249. )
  250. if (columnsTotalWidth > containerWidth) {
  251. scroll.x = Math.max(columnsTotalWidth, containerWidth)
  252. }
  253. if (headerFixed) {
  254. scroll.y = tableBodyHeght
  255. }
  256. return scroll
  257. }
  258. private isSameObj(
  259. prevObj: object,
  260. nextObj: object,
  261. isSourceData?: boolean
  262. ): boolean {
  263. let isb = void 0
  264. const clonePrevObj = { ...prevObj }
  265. const cloneNextObj = { ...nextObj }
  266. if (isSourceData === true) {
  267. delete clonePrevObj['key']
  268. delete clonePrevObj['value']
  269. delete cloneNextObj['key']
  270. delete cloneNextObj['value']
  271. }
  272. for (const attr in clonePrevObj) {
  273. if (
  274. clonePrevObj[attr] !== undefined &&
  275. clonePrevObj[attr] === cloneNextObj[attr]
  276. ) {
  277. isb = true
  278. } else {
  279. isb = false
  280. break
  281. }
  282. }
  283. return isb
  284. }
  285. private matchAttrInBrackets(attr: string) {
  286. const re = /\(\S+\)/
  287. const key = re.test(attr) ? attr.match(/\((\S+)\)/)[1] : attr
  288. return key
  289. }
  290. private rowClick = (record, row, event) => {
  291. const {
  292. getDataDrillDetail,
  293. onCheckTableInteract,
  294. onDoInteract
  295. } = this.props
  296. let selectedRow = [...this.state.selectedRow]
  297. let filterObj = void 0
  298. if (event.target && event.target.innerHTML) {
  299. for (const attr in record) {
  300. if (record[attr].toString() === event.target.innerText) {
  301. const key = this.matchAttrInBrackets(attr)
  302. filterObj = {
  303. key,
  304. value: event.target.innerText
  305. }
  306. }
  307. }
  308. }
  309. const recordConcatFilter = {
  310. ...record,
  311. ...filterObj
  312. }
  313. const isInteractiveChart = onCheckTableInteract && onCheckTableInteract()
  314. if (isInteractiveChart && onDoInteract) {
  315. selectedRow = [recordConcatFilter]
  316. } else {
  317. if (selectedRow.length === 0) {
  318. selectedRow.push(recordConcatFilter)
  319. } else {
  320. const isb = selectedRow.some((sr) =>
  321. this.isSameObj(sr, recordConcatFilter, true)
  322. )
  323. if (isb) {
  324. for (let index = 0, l = selectedRow.length; index < l; index++) {
  325. if (this.isSameObj(selectedRow[index], recordConcatFilter, true)) {
  326. selectedRow.splice(index, 1)
  327. break
  328. }
  329. }
  330. } else {
  331. selectedRow.push(recordConcatFilter)
  332. }
  333. }
  334. }
  335. this.setState(
  336. {
  337. selectedRow
  338. },
  339. () => {
  340. const sourceData = Object.values(this.state.selectedRow)
  341. const isInteractiveChart =
  342. onCheckTableInteract && onCheckTableInteract()
  343. if (isInteractiveChart && onDoInteract) {
  344. const triggerData = sourceData
  345. onDoInteract(triggerData)
  346. }
  347. }
  348. )
  349. }
  350. private asyncEmitDrillDetail() {
  351. const { getDataDrillDetail } = this.props
  352. setTimeout(() => {
  353. if (this.props.getDataDrillDetail) {
  354. const sourceData = this.combineFilter()
  355. const sourceGroup = this.combineGroups()
  356. const brushed = [{ 0: Object.values(sourceData) }]
  357. getDataDrillDetail(
  358. JSON.stringify({
  359. filterObj: sourceData,
  360. brushed,
  361. sourceData,
  362. sourceGroup
  363. })
  364. )
  365. }
  366. }, 500)
  367. }
  368. private combineGroups() {
  369. const { group } = this.state.selectItems
  370. return group
  371. }
  372. private combineFilter() {
  373. const { cell } = this.state.selectItems
  374. return Object.keys(cell).reduce((iteratee, target) => {
  375. iteratee = iteratee.concat(cell[target])
  376. return iteratee
  377. }, [])
  378. }
  379. private getTableStyle(headerFixed: boolean, tableBodyHeght: number) {
  380. const tableStyle: React.CSSProperties = {}
  381. if (!headerFixed) {
  382. tableStyle.height = tableBodyHeght
  383. tableStyle.overflowY = 'auto'
  384. }
  385. return tableStyle
  386. }
  387. private filterSameNeighbourSibings = (arr, targetIndex) => {
  388. let s = targetIndex
  389. let e = targetIndex
  390. let flag = -1
  391. const orgIndex = targetIndex
  392. do {
  393. const target = arr[targetIndex]
  394. if (flag === -1 && targetIndex > 0 && arr[targetIndex - 1] === target) {
  395. s = targetIndex -= 1
  396. } else if (flag === 1 && arr[targetIndex + 1] === target) {
  397. e = targetIndex += 1
  398. } else if (flag === -1) {
  399. flag = 1
  400. targetIndex = orgIndex
  401. } else {
  402. break
  403. }
  404. } while (targetIndex > -1 && targetIndex < arr.length)
  405. return { s, e }
  406. }
  407. private coustomFilter(array, column, index) {
  408. const nativeIndex = array.reduce(
  409. (a, b, c) => (b.index === index ? c : a),
  410. 0
  411. )
  412. const columns = array.map((a) => a[column])
  413. const { s: start, e: end } = this.filterSameNeighbourSibings(
  414. columns,
  415. nativeIndex
  416. )
  417. return array.filter(
  418. (arr) =>
  419. arr['index'] < array[start]['index'] || arr.index > array[end]['index']
  420. )
  421. }
  422. private collectCell = (target, index, dataIndex: string) => (event) => {
  423. const { group, cell } = this.state.selectItems
  424. const { data } = this.props
  425. const groupName = this.matchAttrInBrackets(dataIndex)
  426. if (this.isValueModelType(groupName)) {
  427. return
  428. }
  429. if (group.includes(dataIndex)) {
  430. group.forEach((g, i) => (g === dataIndex ? group.splice(i, 1) : void 0))
  431. const setKeyArray = data.map((obj: { key: number }, index) => ({
  432. ...obj,
  433. index: obj.key || index,
  434. key: groupName,
  435. value: obj[dataIndex]
  436. }))
  437. cell[dataIndex] = this.coustomFilter(setKeyArray, groupName, index)
  438. } else {
  439. const sourceCol = cell[dataIndex]
  440. const currentValue = {
  441. ...target,
  442. index,
  443. key: groupName,
  444. value: target[dataIndex]
  445. }
  446. if (sourceCol && sourceCol.length) {
  447. const isb = sourceCol.some((col) => col.index === index)
  448. if (isb) {
  449. cell[dataIndex] = this.coustomFilter(
  450. cell[dataIndex],
  451. groupName,
  452. index
  453. )
  454. } else {
  455. sourceCol.push(currentValue)
  456. }
  457. } else {
  458. cell[dataIndex] = [currentValue]
  459. }
  460. }
  461. this.setState(
  462. {
  463. selectItems: { ...this.state.selectItems }
  464. },
  465. () => {
  466. this.asyncEmitDrillDetail()
  467. }
  468. )
  469. }
  470. private collectGroups = (target, dataIndex) => (event) => {
  471. const groupName = this.matchAttrInBrackets(dataIndex)
  472. if (this.isValueModelType(groupName)) {
  473. return
  474. }
  475. const { group, cell } = this.state.selectItems
  476. if (group.includes(dataIndex)) {
  477. group.forEach((a, index) => {
  478. if (a === dataIndex) {
  479. group.splice(index, 1)
  480. }
  481. })
  482. } else {
  483. group.push(dataIndex)
  484. }
  485. delete cell[dataIndex]
  486. this.setState(
  487. {
  488. selectItems: { ...this.state.selectItems }
  489. },
  490. () => {
  491. this.asyncEmitDrillDetail()
  492. }
  493. )
  494. }
  495. private onCellClassName = (target, index, dataIndex) => {
  496. const { group, cell } = this.state.selectItems
  497. let result = ''
  498. Object.keys(cell).forEach((key) => {
  499. if (dataIndex === key) {
  500. cell[key].forEach((ck) => {
  501. if (index === ck.index) {
  502. result = Styles.select
  503. }
  504. })
  505. }
  506. })
  507. if (group && group.includes(dataIndex)) {
  508. result = Styles.select
  509. }
  510. return result
  511. }
  512. private isValueModelType = (modelName) => {
  513. const target = this.getModelTypecollectByModel()
  514. const result = hasProperty(target, modelName as never)
  515. return typeof result !== 'boolean' ? result === ViewModelTypes.Value : false
  516. }
  517. private getModelTypecollectByModel = () => {
  518. const { model } = this.props
  519. return Object.keys(model).reduce((iteratee, target) => {
  520. iteratee[target] = hasProperty(model[target], 'modelType')
  521. return iteratee
  522. }, {})
  523. }
  524. private onHeadCellClassName = (target, dataIndex) => {
  525. const { group } = this.state.selectItems
  526. if (group && group.includes(dataIndex)) {
  527. return Styles.select
  528. }
  529. return ''
  530. }
  531. private loop = (col) => {
  532. if (col && col.dataIndex) {
  533. return {
  534. ...col,
  535. onHeaderCell: (target) => {
  536. return {
  537. ...col.onHeaderCell(target),
  538. className: this.onHeadCellClassName(target, col.dataIndex),
  539. onClick: this.collectGroups(target, col.dataIndex)
  540. }
  541. },
  542. onCell: (target, index) => {
  543. // fix index in pagination
  544. return {
  545. ...col.onCell(target, index),
  546. className: this.onCellClassName(target, index, col.dataIndex),
  547. onClick: this.collectCell(target, index, col.dataIndex)
  548. }
  549. }
  550. }
  551. } else {
  552. return { ...col }
  553. }
  554. }
  555. private enhancerColumns = (column) => {
  556. const columns = column.map((col) => {
  557. if (col.children && col.children.length) {
  558. return {
  559. ...this.loop(col),
  560. children: this.enhancerColumns(col.children)
  561. }
  562. }
  563. return this.loop(col)
  564. })
  565. return columns
  566. }
  567. private getEnhancerColumn = (column) => {
  568. return this.enhancerColumns(column)
  569. }
  570. public render() {
  571. const { data, chartStyles, width } = this.props
  572. const { headerFixed, bordered, withPaging, size } = chartStyles.table
  573. const {
  574. tablePagination,
  575. tableColumns,
  576. tableBodyHeight,
  577. mapTableHeaderConfig,
  578. containerWidthRatio
  579. } = this.state
  580. const adjustedTableColumns = this.adjustTableColumns(
  581. tableColumns,
  582. mapTableHeaderConfig,
  583. containerWidthRatio
  584. )
  585. const getEnhancerColumn = this.getEnhancerColumn(adjustedTableColumns)
  586. const paginationConfig: PaginationConfig = {
  587. ...this.basePagination,
  588. ...tablePagination
  589. }
  590. const scroll = this.getTableScroll(
  591. adjustedTableColumns,
  592. width,
  593. headerFixed,
  594. tableBodyHeight
  595. )
  596. const style = this.getTableStyle(headerFixed, tableBodyHeight)
  597. const paginationWithoutTotal =
  598. withPaging && tablePagination.total === -1 ? (
  599. <PaginationWithoutTotal
  600. dataLength={data.length}
  601. size={'small' as any}
  602. {...paginationConfig}
  603. />
  604. ) : null
  605. const tableCls = classnames({
  606. [Styles.table]: true,
  607. [Styles.noBorder]: bordered !== undefined && !bordered
  608. })
  609. return (
  610. <>
  611. <AntTable
  612. style={style}
  613. className={tableCls}
  614. ref={this.table}
  615. size={size}
  616. dataSource={data}
  617. rowKey={this.getRowKey}
  618. components={tableComponents}
  619. columns={getEnhancerColumn}
  620. // columns={adjustedTableColumns}
  621. pagination={
  622. withPaging && tablePagination.total !== -1
  623. ? paginationConfig
  624. : false
  625. }
  626. scroll={scroll}
  627. bordered={bordered}
  628. onRowClick={this.rowClick}
  629. onChange={this.tableChange}
  630. />
  631. {paginationWithoutTotal}
  632. </>
  633. )
  634. }
  635. }
  636. export default Table
  637. function getTableColumns(props: IChartProps) {
  638. const { chartStyles, width } = props
  639. if (!chartStyles.table) {
  640. return {
  641. tableColumns: [],
  642. mapTableHeaderConfig: {}
  643. }
  644. }
  645. const { cols, rows, metrics, data, queryVariables } = props
  646. const {
  647. headerConfig,
  648. columnsConfig,
  649. autoMergeCell,
  650. leftFixedColumns,
  651. rightFixedColumns,
  652. withNoAggregators
  653. } = chartStyles.table
  654. const tableColumns: Array<ColumnProps<any>> = []
  655. const mapTableHeaderConfig: IMapTableHeaderConfig = {}
  656. const fixedColumnInfo: { [key: string]: number } = {}
  657. const dimensions = cols.concat(rows)
  658. const cellSpanMap = getCellSpanMap(data, dimensions)
  659. let calculatedTotalWidth = 0
  660. let fixedTotalWidth = 0
  661. dimensions.forEach((dimension) => {
  662. const { name, field, format } = dimension
  663. const headerText = getFieldAlias(field, queryVariables || {}) || name
  664. const column: ColumnProps<any> = {
  665. key: name,
  666. title:
  667. field && field.desc ? (
  668. <>
  669. {headerText}
  670. <Tooltip title={field.desc} placement="top">
  671. <Icon className={Styles.headerIcon} type="info-circle" />
  672. </Tooltip>
  673. </>
  674. ) : (
  675. headerText
  676. ),
  677. dataIndex: name
  678. }
  679. if (autoMergeCell) {
  680. column.render = (text, _, idx) => {
  681. // dimension cells needs merge
  682. const rowSpan = cellSpanMap[name][idx]
  683. return rowSpan === 1 ? text : { children: text, props: { rowSpan } }
  684. }
  685. }
  686. let headerConfigItem: ITableHeaderConfig = null
  687. findChildConfig(headerConfig, 'headerName', 'children', name, (config) => {
  688. headerConfigItem = config
  689. })
  690. const columnConfigItem = columnsConfig.find(
  691. (cfg) => cfg.columnName === name
  692. )
  693. const isFixed =
  694. columnConfigItem &&
  695. columnConfigItem.style &&
  696. columnConfigItem.style.inflexible
  697. if (isFixed) {
  698. column.width = fixedColumnInfo[column.key] = columnConfigItem.style.width
  699. fixedTotalWidth += column.width
  700. } else {
  701. column.width = getDataColumnWidth(name, columnConfigItem, format, data)
  702. let headerWidth = computeCellWidth(
  703. headerConfigItem && headerConfigItem.style,
  704. headerText
  705. )
  706. if (dimension.field?.desc) {
  707. headerWidth += 14 + 16
  708. }
  709. if (columnConfigItem?.sort) {
  710. headerWidth += 30
  711. }
  712. column.width = Math.max(+column.width, headerWidth)
  713. column.width = Math.max(+column.width, headerWidth)
  714. }
  715. calculatedTotalWidth += column.width
  716. if (columnConfigItem) {
  717. column.sorter = columnConfigItem.sort
  718. }
  719. mapTableHeaderConfig[name] = headerConfigItem
  720. column.onCell = (record) => ({
  721. config: columnConfigItem,
  722. format,
  723. cellVal: record[name],
  724. cellValRange: null
  725. })
  726. tableColumns.push(column)
  727. })
  728. metrics.forEach((metric) => {
  729. const { name, field, format, agg } = metric
  730. let expression = decodeMetricName(name)
  731. if (!withNoAggregators) {
  732. expression = `${agg}(${expression})`
  733. }
  734. const headerText = getFieldAlias(field, queryVariables || {}) || expression
  735. const column: ColumnProps<any> = {
  736. key: name,
  737. title:
  738. field && field.desc ? (
  739. <>
  740. {headerText}
  741. <Tooltip title={field.desc} placement="top">
  742. <Icon className={Styles.headerIcon} type="info-circle" />
  743. </Tooltip>
  744. </>
  745. ) : (
  746. headerText
  747. ),
  748. dataIndex: expression
  749. }
  750. let headerConfigItem: ITableHeaderConfig = null
  751. findChildConfig(headerConfig, 'headerName', 'children', name, (config) => {
  752. headerConfigItem = config
  753. })
  754. const columnConfigItem = columnsConfig.find(
  755. (cfg) => cfg.columnName === name
  756. )
  757. const isFixed =
  758. columnConfigItem &&
  759. columnConfigItem.style &&
  760. columnConfigItem.style.inflexible
  761. if (isFixed) {
  762. column.width = fixedColumnInfo[column.key] = columnConfigItem.style.width
  763. fixedTotalWidth += column.width
  764. } else {
  765. column.width = getDataColumnWidth(
  766. expression,
  767. columnConfigItem,
  768. format,
  769. data
  770. )
  771. let headerWidth = computeCellWidth(
  772. headerConfigItem && headerConfigItem.style,
  773. headerText
  774. )
  775. if (metric.field?.desc) {
  776. headerWidth += 14 + 16
  777. }
  778. column.width = Math.max(+column.width, headerWidth)
  779. }
  780. calculatedTotalWidth += column.width
  781. if (columnConfigItem) {
  782. column.sorter = columnConfigItem.sort
  783. }
  784. mapTableHeaderConfig[name] = headerConfigItem
  785. column.onCell = (record) => ({
  786. config: columnConfigItem,
  787. format,
  788. cellVal: record[expression],
  789. cellValRange: getTableCellValueRange(data, expression, columnConfigItem)
  790. })
  791. tableColumns.push(column)
  792. })
  793. // adjust column width
  794. const flexibleTotalWidth = calculatedTotalWidth - fixedTotalWidth
  795. const flexibleContainerWidth = width - fixedTotalWidth
  796. const containerWidthRatio =
  797. flexibleTotalWidth < flexibleContainerWidth
  798. ? flexibleContainerWidth / flexibleTotalWidth
  799. : 1
  800. tableColumns.forEach((column) => {
  801. if (fixedColumnInfo[column.key] === void 0) {
  802. // Math.floor to avoid creating float column width value and scrollbar showing
  803. // not use Math.ceil because it will exceed the container width in total
  804. column.width = Math.floor(containerWidthRatio * Number(column.width))
  805. }
  806. })
  807. const groupedColumns: Array<ColumnProps<any>> = []
  808. traverseConfig<ITableHeaderConfig>(
  809. headerConfig,
  810. 'children',
  811. (currentConfig) => {
  812. const { key, isGroup, headerName, style } = currentConfig
  813. if (!isGroup) {
  814. return
  815. }
  816. const childrenConfig = currentConfig.children.filter(
  817. ({ isGroup, key, headerName }) =>
  818. (!isGroup &&
  819. tableColumns.findIndex((col) => col.key === headerName) >= 0) ||
  820. (isGroup && groupedColumns.findIndex((col) => col.key === key) >= 0)
  821. )
  822. if (!childrenConfig.length) {
  823. return
  824. }
  825. const groupedColumn: ColumnProps<any> = {
  826. key,
  827. title: headerName,
  828. width: 0,
  829. children: []
  830. }
  831. mapTableHeaderConfig[key] = currentConfig
  832. childrenConfig.sort((cfg1, cfg2) => {
  833. if (cfg1.isGroup || cfg2.isGroup) {
  834. return 0
  835. }
  836. const cfg1Idx = tableColumns.findIndex(
  837. (column) => column.key === cfg1.headerName
  838. )
  839. const cfg2Idx = tableColumns.findIndex(
  840. (column) => column.key === cfg2.headerName
  841. )
  842. return cfg1Idx - cfg2Idx
  843. })
  844. let insertIdx = Infinity
  845. childrenConfig.forEach(({ isGroup, key, headerName }) => {
  846. const columnIdx = tableColumns.findIndex((column) =>
  847. column.children ? column.key === key : column.key === headerName
  848. )
  849. insertIdx = Math.min(insertIdx, columnIdx)
  850. groupedColumn.children.push(tableColumns[columnIdx])
  851. groupedColumn.width =
  852. +groupedColumn.width + +tableColumns[columnIdx].width
  853. tableColumns.splice(columnIdx, 1)
  854. })
  855. tableColumns.splice(insertIdx, 0, groupedColumn)
  856. groupedColumns.push(groupedColumn)
  857. }
  858. )
  859. tableColumns.forEach((column) => {
  860. const name = (column.children && column.children.length
  861. ? column.title
  862. : column.dataIndex) as string
  863. if (leftFixedColumns.includes(name)) {
  864. column.fixed = 'left'
  865. }
  866. if (rightFixedColumns.includes(name)) {
  867. column.fixed = 'right'
  868. }
  869. })
  870. return { tableColumns, mapTableHeaderConfig, containerWidthRatio }
  871. }
  872. function getPaginationOptions(props: IChartProps) {
  873. const { chartStyles, width, pagination } = props
  874. // fixme
  875. let pageNo = void 0
  876. let pageSize = void 0
  877. let totalCount = void 0
  878. if (pagination) {
  879. pageNo = pagination.pageNo
  880. pageSize = pagination.pageSize
  881. totalCount = pagination.totalCount
  882. }
  883. // const { pageNo, pageSize, totalCount } = pagination
  884. const { pageSize: initialPageSize } = chartStyles.table
  885. const paginationOptions: ITableStates['tablePagination'] = {
  886. current: pageNo,
  887. pageSize: pageSize || +initialPageSize,
  888. total: totalCount,
  889. simple: width <= 768
  890. }
  891. return paginationOptions
  892. }