The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/features/explore/Table/TableContainer.tsx

177 lines
6.2 KiB

import { css } from '@emotion/css';
import { PureComponent } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { applyFieldOverrides, SplitOpen, DataFrame, LoadingState, FieldType } from '@grafana/data';
import { getTemplateSrv } from '@grafana/runtime';
import { TimeZone } from '@grafana/schema';
import { Table, AdHocFilterItem, PanelChrome, withTheme2, Themeable2 } from '@grafana/ui';
import { config } from 'app/core/config';
import { t, Trans } from 'app/core/internationalization';
import {
hasDeprecatedParentRowIndex,
migrateFromParentRowIndexToNestedFrames,
} from 'app/plugins/panel/table/migrations';
import { StoreState } from 'app/types';
import { ExploreItemState } from 'app/types/explore';
import { LimitedDataDisclaimer } from '../LimitedDataDisclaimer';
import { MetaInfoText } from '../MetaInfoText';
import { selectIsWaitingForData } from '../state/query';
import { exploreDataLinkPostProcessorFactory } from '../utils/links';
const MAX_NUMBER_OF_COLUMNS = 20;
interface TableContainerProps extends Themeable2 {
ariaLabel?: string;
exploreId: string;
width: number;
timeZone: TimeZone;
onCellFilterAdded?: (filter: AdHocFilterItem) => void;
splitOpenFn: SplitOpen;
}
function mapStateToProps(state: StoreState, { exploreId }: TableContainerProps) {
const explore = state.explore;
const item: ExploreItemState = explore.panes[exploreId]!;
const { tableResult, range } = item;
const loadingInState = selectIsWaitingForData(exploreId);
const loading = tableResult && tableResult.length > 0 ? false : loadingInState;
return { loading, tableResult, range };
}
const connector = connect(mapStateToProps, {});
type Props = TableContainerProps & ConnectedProps<typeof connector>;
type State = {
showAll: boolean;
};
export class TableContainer extends PureComponent<Props, State> {
state = { showAll: false };
hasSubFrames = (data: DataFrame) => data.fields.some((f) => f.type === FieldType.nestedFrames);
getTableHeight(rowCount: number, hasSubFrames: boolean) {
if (rowCount === 0) {
return 200;
}
// tries to estimate table height, with a min of 300 and a max of 600
// if there are multiple tables, there is no min
return Math.min(600, Math.max(rowCount * 36, hasSubFrames ? 300 : 0) + 40 + 46);
}
getTableTitle(dataFrames: DataFrame[] | null, data: DataFrame, i: number) {
let name = data.name;
if (!name && (dataFrames?.length ?? 0) > 1) {
name = data.refId || `${i}`;
}
return name
? t('explore.table.title-with-name', 'Table - {{name}}', { name, interpolation: { escapeValue: false } })
: t('explore.table.title', 'Table');
}
showAll() {
this.setState({
showAll: true,
});
}
render() {
const { loading, onCellFilterAdded, tableResult, width, splitOpenFn, range, ariaLabel, timeZone, theme } =
this.props;
const { showAll } = this.state;
let dataFrames = hasDeprecatedParentRowIndex(tableResult)
? migrateFromParentRowIndexToNestedFrames(tableResult)
: tableResult;
const dataLinkPostProcessor = exploreDataLinkPostProcessorFactory(splitOpenFn, range);
let dataLimited = false;
if (dataFrames?.length) {
dataFrames = dataFrames.map((frame) => {
frame.fields.forEach((field, index) => {
const hidden = showAll ? false : index >= MAX_NUMBER_OF_COLUMNS;
field.config.custom = { hidden };
dataLimited = dataLimited || hidden;
});
return frame;
});
dataFrames = applyFieldOverrides({
data: dataFrames,
timeZone,
theme: config.theme2,
replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()),
fieldConfig: {
defaults: {},
overrides: [],
},
dataLinkPostProcessor,
});
}
const frames = dataFrames?.filter(
(frame: DataFrame | undefined): frame is DataFrame => !!frame && frame.length !== 0
);
return (
<>
{frames && frames.length === 0 && (
<PanelChrome title={t('explore.table.title', 'Table')} width={width} height={200}>
{() => <MetaInfoText metaItems={[{ value: t('explore.table.no-data', '0 series returned') }]} />}
</PanelChrome>
)}
{frames && frames.length > 0 && (
<div className={css({ display: 'flex', flexDirection: 'column', gap: theme.spacing(1) })}>
{frames.map((data, i) => (
<PanelChrome
key={data.refId || `table-${i}`}
title={this.getTableTitle(dataFrames, data, i)}
titleItems={[
!showAll && dataLimited && (
<LimitedDataDisclaimer
toggleShowAllSeries={() => this.showAll()}
info={
<Trans i18nKey={'table.container.show-only-series'}>
Showing only {{ MAX_NUMBER_OF_COLUMNS }} columns
</Trans>
}
tooltip={
<Trans i18nKey={'table.container.show-all-series-tooltip'}>
Showing too many columns in a single table may impact performance and make data harder to
read. Consider refining your queries.
</Trans>
}
buttonLabel={<Trans i18nKey={'table.container.show-all-series'}>Show all columns</Trans>}
/>
),
]}
width={width}
height={this.getTableHeight(data.length, this.hasSubFrames(data))}
loadingState={loading ? LoadingState.Loading : undefined}
>
{(innerWidth, innerHeight) => (
<Table
ariaLabel={ariaLabel}
data={data}
width={innerWidth}
height={innerHeight}
onCellFilterAdded={onCellFilterAdded}
/>
)}
</PanelChrome>
))}
</div>
)}
</>
);
}
}
export const TableContainerWithTheme = withTheme2(TableContainer);
export default withTheme2(connector(TableContainer));