Explore: Limit number of columns shown in Explore table (#98726)
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useToggle } from 'react-use';
|
||||
|
||||
@@ -10,21 +9,13 @@ import {
|
||||
SplitOpen,
|
||||
LoadingState,
|
||||
ThresholdsConfig,
|
||||
GrafanaTheme2,
|
||||
TimeRange,
|
||||
} from '@grafana/data';
|
||||
import {
|
||||
GraphThresholdsStyleConfig,
|
||||
PanelChrome,
|
||||
PanelChromeProps,
|
||||
Icon,
|
||||
Button,
|
||||
useStyles2,
|
||||
Tooltip,
|
||||
} from '@grafana/ui';
|
||||
import { GraphThresholdsStyleConfig, PanelChrome, PanelChromeProps } from '@grafana/ui';
|
||||
import { t, Trans } from 'app/core/internationalization';
|
||||
import { ExploreGraphStyle } from 'app/types';
|
||||
|
||||
import { LimitedDataDisclaimer } from '../LimitedDataDisclaimer';
|
||||
import { storeGraphStyle } from '../state/utils';
|
||||
|
||||
import { ExploreGraph } from './ExploreGraph';
|
||||
@@ -65,7 +56,6 @@ export const GraphContainer = ({
|
||||
}: Props) => {
|
||||
const [showAllSeries, toggleShowAllSeries] = useToggle(false);
|
||||
const [graphStyle, setGraphStyle] = useState(loadGraphStyle);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const onGraphStyleChange = useCallback((graphStyle: ExploreGraphStyle) => {
|
||||
storeGraphStyle(graphStyle);
|
||||
@@ -81,24 +71,20 @@ export const GraphContainer = ({
|
||||
title={t('graph.container.title', 'Graph')}
|
||||
titleItems={[
|
||||
!showAllSeries && MAX_NUMBER_OF_TIME_SERIES < data.length && (
|
||||
<div key="disclaimer" className={styles.timeSeriesDisclaimer}>
|
||||
<span className={styles.warningMessage}>
|
||||
<Icon name="exclamation-triangle" aria-hidden="true" />
|
||||
<LimitedDataDisclaimer
|
||||
key="disclaimer"
|
||||
toggleShowAllSeries={toggleShowAllSeries}
|
||||
info={
|
||||
<Trans i18nKey={'graph.container.show-only-series'}>
|
||||
Showing only {{ MAX_NUMBER_OF_TIME_SERIES }} series
|
||||
</Trans>
|
||||
</span>
|
||||
<Tooltip
|
||||
content={t(
|
||||
'graph.container.content',
|
||||
'Rendering too many series in a single panel may impact performance and make data harder to read. Consider refining your queries.'
|
||||
)}
|
||||
>
|
||||
<Button variant="secondary" size="sm" onClick={toggleShowAllSeries}>
|
||||
<Trans i18nKey={'graph.container.show-all-series'}>Show all {{ length: data.length }}</Trans>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
}
|
||||
buttonLabel={<Trans i18nKey={'graph.container.show-all-series'}>Show all {{ length: data.length }}</Trans>}
|
||||
tooltip={t(
|
||||
'graph.container.content',
|
||||
'Rendering too many series in a single panel may impact performance and make data harder to read. Consider refining your queries.'
|
||||
)}
|
||||
/>
|
||||
),
|
||||
].filter(Boolean)}
|
||||
width={width}
|
||||
@@ -127,19 +113,3 @@ export const GraphContainer = ({
|
||||
</PanelChrome>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
timeSeriesDisclaimer: css({
|
||||
label: 'time-series-disclaimer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(1),
|
||||
}),
|
||||
warningMessage: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(0.5),
|
||||
color: theme.colors.warning.main,
|
||||
fontSize: theme.typography.bodySmall.fontSize,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Button, Icon, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
|
||||
type Props = {
|
||||
toggleShowAllSeries: () => void;
|
||||
info: React.ReactNode;
|
||||
tooltip: string;
|
||||
buttonLabel: React.ReactNode;
|
||||
};
|
||||
|
||||
export function LimitedDataDisclaimer(props: Props) {
|
||||
const { toggleShowAllSeries, info, tooltip, buttonLabel } = props;
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<div key="disclaimer" className={styles.disclaimer}>
|
||||
<span className={styles.warningMessage}>
|
||||
<Icon name="exclamation-triangle" aria-hidden="true" />
|
||||
{info}
|
||||
</span>
|
||||
|
||||
<Tooltip content={tooltip}>
|
||||
<Button variant="secondary" size="sm" onClick={toggleShowAllSeries}>
|
||||
{buttonLabel}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
disclaimer: css({
|
||||
label: 'series-disclaimer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(1),
|
||||
}),
|
||||
warningMessage: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(0.5),
|
||||
color: theme.colors.warning.main,
|
||||
fontSize: theme.typography.bodySmall.fontSize,
|
||||
}),
|
||||
});
|
||||
@@ -7,7 +7,7 @@ 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 } from 'app/core/internationalization';
|
||||
import { t, Trans } from 'app/core/internationalization';
|
||||
import {
|
||||
hasDeprecatedParentRowIndex,
|
||||
migrateFromParentRowIndexToNestedFrames,
|
||||
@@ -15,10 +15,13 @@ import {
|
||||
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;
|
||||
@@ -40,8 +43,13 @@ function mapStateToProps(state: StoreState, { exploreId }: TableContainerProps)
|
||||
const connector = connect(mapStateToProps, {});
|
||||
|
||||
type Props = TableContainerProps & ConnectedProps<typeof connector>;
|
||||
type State = {
|
||||
showAll: boolean;
|
||||
};
|
||||
|
||||
export class TableContainer extends PureComponent<Props, State> {
|
||||
state = { showAll: false };
|
||||
|
||||
export class TableContainer extends PureComponent<Props> {
|
||||
hasSubFrames = (data: DataFrame) => data.fields.some((f) => f.type === FieldType.nestedFrames);
|
||||
|
||||
getTableHeight(rowCount: number, hasSubFrames: boolean) {
|
||||
@@ -64,16 +72,35 @@ export class TableContainer extends PureComponent<Props> {
|
||||
: 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,
|
||||
@@ -104,6 +131,22 @@ export class TableContainer extends PureComponent<Props> {
|
||||
<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={
|
||||
'Showing too many columns in a single table may impact performance and make data harder to read. Consider refining your queries.'
|
||||
}
|
||||
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}
|
||||
|
||||
@@ -1303,10 +1303,7 @@ export const queryReducer = (state: ExploreItemState, action: AnyAction): Explor
|
||||
return state;
|
||||
};
|
||||
|
||||
export const processQueryResponse = (
|
||||
state: ExploreItemState,
|
||||
action: PayloadAction<QueryEndedPayload>
|
||||
): ExploreItemState => {
|
||||
const processQueryResponse = (state: ExploreItemState, action: PayloadAction<QueryEndedPayload>): ExploreItemState => {
|
||||
const { response } = action.payload;
|
||||
const {
|
||||
request,
|
||||
|
||||
@@ -3216,6 +3216,12 @@
|
||||
"url-column-header": "Snapshot url",
|
||||
"view-button": "View"
|
||||
},
|
||||
"table": {
|
||||
"container": {
|
||||
"show-all-series": "Show all columns",
|
||||
"show-only-series": "Showing only {{MAX_NUMBER_OF_COLUMNS}} columns"
|
||||
}
|
||||
},
|
||||
"tag-filter": {
|
||||
"clear-button": "Clear tags",
|
||||
"loading": "Loading...",
|
||||
|
||||
@@ -3216,6 +3216,12 @@
|
||||
"url-column-header": "Ŝʼnäpşĥőŧ ūřľ",
|
||||
"view-button": "Vįęŵ"
|
||||
},
|
||||
"table": {
|
||||
"container": {
|
||||
"show-all-series": "Ŝĥőŵ äľľ čőľūmʼnş",
|
||||
"show-only-series": "Ŝĥőŵįʼnģ őʼnľy {{MAX_NUMBER_OF_COLUMNS}} čőľūmʼnş"
|
||||
}
|
||||
},
|
||||
"tag-filter": {
|
||||
"clear-button": "Cľęäř ŧäģş",
|
||||
"loading": "Ŀőäđįʼnģ...",
|
||||
|
||||
Reference in New Issue
Block a user