TablePanel.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import { css } from '@emotion/css';
  2. import React, { Component } from 'react';
  3. import {
  4. DataFrame,
  5. FieldMatcherID,
  6. getDataSourceRef,
  7. getFrameDisplayName,
  8. PanelProps,
  9. SelectableValue,
  10. } from '@grafana/data';
  11. import { PanelDataErrorView } from '@grafana/runtime';
  12. import { Select, Table } from '@grafana/ui';
  13. import { FilterItem, TableSortByFieldState } from '@grafana/ui/src/components/Table/types';
  14. import { config } from 'app/core/config';
  15. import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
  16. import { getDashboardSrv } from '../../../features/dashboard/services/DashboardSrv';
  17. import { applyFilterFromTable } from '../../../features/variables/adhoc/actions';
  18. import { dispatch } from '../../../store/store';
  19. import { getFooterCells } from './footer';
  20. import { PanelOptions } from './models.gen';
  21. interface Props extends PanelProps<PanelOptions> {}
  22. export class TablePanel extends Component<Props> {
  23. constructor(props: Props) {
  24. super(props);
  25. }
  26. onColumnResize = (fieldDisplayName: string, width: number) => {
  27. const { fieldConfig } = this.props;
  28. const { overrides } = fieldConfig;
  29. const matcherId = FieldMatcherID.byName;
  30. const propId = 'custom.width';
  31. // look for existing override
  32. const override = overrides.find((o) => o.matcher.id === matcherId && o.matcher.options === fieldDisplayName);
  33. if (override) {
  34. // look for existing property
  35. const property = override.properties.find((prop) => prop.id === propId);
  36. if (property) {
  37. property.value = width;
  38. } else {
  39. override.properties.push({ id: propId, value: width });
  40. }
  41. } else {
  42. overrides.push({
  43. matcher: { id: matcherId, options: fieldDisplayName },
  44. properties: [{ id: propId, value: width }],
  45. });
  46. }
  47. this.props.onFieldConfigChange({
  48. ...fieldConfig,
  49. overrides,
  50. });
  51. };
  52. onSortByChange = (sortBy: TableSortByFieldState[]) => {
  53. this.props.onOptionsChange({
  54. ...this.props.options,
  55. sortBy,
  56. });
  57. };
  58. onChangeTableSelection = (val: SelectableValue<number>) => {
  59. this.props.onOptionsChange({
  60. ...this.props.options,
  61. frameIndex: val.value || 0,
  62. });
  63. // Force a redraw -- but no need to re-query
  64. this.forceUpdate();
  65. };
  66. onCellFilterAdded = (filter: FilterItem) => {
  67. const { key, value, operator } = filter;
  68. const panelModel = getDashboardSrv().getCurrent()?.getPanelById(this.props.id);
  69. if (!panelModel) {
  70. return;
  71. }
  72. // When the datasource is null/undefined (for a default datasource), we use getInstanceSettings
  73. // to find the real datasource ref for the default datasource.
  74. const datasourceInstance = getDatasourceSrv().getInstanceSettings(panelModel.datasource);
  75. const datasourceRef = datasourceInstance && getDataSourceRef(datasourceInstance);
  76. if (!datasourceRef) {
  77. return;
  78. }
  79. dispatch(applyFilterFromTable({ datasource: datasourceRef, key, operator, value }));
  80. };
  81. renderTable(frame: DataFrame, width: number, height: number) {
  82. const { options } = this.props;
  83. const footerValues = options.footer?.show ? getFooterCells(frame, options.footer) : undefined;
  84. return (
  85. <Table
  86. height={height}
  87. width={width}
  88. data={frame}
  89. noHeader={!options.showHeader}
  90. showTypeIcons={options.showTypeIcons}
  91. resizable={true}
  92. initialSortBy={options.sortBy}
  93. onSortByChange={this.onSortByChange}
  94. onColumnResize={this.onColumnResize}
  95. onCellFilterAdded={this.onCellFilterAdded}
  96. footerValues={footerValues}
  97. enablePagination={options.footer?.enablePagination}
  98. />
  99. );
  100. }
  101. getCurrentFrameIndex(frames: DataFrame[], options: PanelOptions) {
  102. return options.frameIndex > 0 && options.frameIndex < frames.length ? options.frameIndex : 0;
  103. }
  104. render() {
  105. const { data, height, width, options, fieldConfig, id } = this.props;
  106. const frames = data.series;
  107. const count = frames?.length;
  108. const hasFields = frames[0]?.fields.length;
  109. if (!count || !hasFields) {
  110. return <PanelDataErrorView panelId={id} fieldConfig={fieldConfig} data={data} />;
  111. }
  112. if (count > 1) {
  113. const inputHeight = config.theme.spacing.formInputHeight;
  114. const padding = 8 * 2;
  115. const currentIndex = this.getCurrentFrameIndex(frames, options);
  116. const names = frames.map((frame, index) => {
  117. return {
  118. label: getFrameDisplayName(frame),
  119. value: index,
  120. };
  121. });
  122. return (
  123. <div className={tableStyles.wrapper}>
  124. {this.renderTable(data.series[currentIndex], width, height - inputHeight - padding)}
  125. <div className={tableStyles.selectWrapper}>
  126. <Select options={names} value={names[currentIndex]} onChange={this.onChangeTableSelection} />
  127. </div>
  128. </div>
  129. );
  130. }
  131. return this.renderTable(data.series[0], width, height);
  132. }
  133. }
  134. const tableStyles = {
  135. wrapper: css`
  136. display: flex;
  137. flex-direction: column;
  138. justify-content: space-between;
  139. height: 100%;
  140. `,
  141. noData: css`
  142. display: flex;
  143. flex-direction: column;
  144. align-items: center;
  145. justify-content: center;
  146. height: 100%;
  147. `,
  148. selectWrapper: css`
  149. padding: 8px;
  150. `,
  151. };