diff --git a/public/app/features/explore/Graph/GraphContainer.tsx b/public/app/features/explore/Graph/GraphContainer.tsx
index c0a07faf61f..2f90d13c2b2 100644
--- a/public/app/features/explore/Graph/GraphContainer.tsx
+++ b/public/app/features/explore/Graph/GraphContainer.tsx
@@ -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 && (
-
-
-
+
Showing only {{ MAX_NUMBER_OF_TIME_SERIES }} series
-
-
-
-
-
+ }
+ buttonLabel={Show all {{ length: data.length }}}
+ 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 = ({
);
};
-
-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,
- }),
-});
diff --git a/public/app/features/explore/LimitedDataDisclaimer.tsx b/public/app/features/explore/LimitedDataDisclaimer.tsx
new file mode 100644
index 00000000000..b81beba8a86
--- /dev/null
+++ b/public/app/features/explore/LimitedDataDisclaimer.tsx
@@ -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 (
+
+
+
+ {info}
+
+
+
+
+
+
+ );
+}
+
+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,
+ }),
+});
diff --git a/public/app/features/explore/Table/TableContainer.tsx b/public/app/features/explore/Table/TableContainer.tsx
index c94149dbefb..2db2b902b35 100644
--- a/public/app/features/explore/Table/TableContainer.tsx
+++ b/public/app/features/explore/Table/TableContainer.tsx
@@ -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;
+type State = {
+ showAll: boolean;
+};
+
+export class TableContainer extends PureComponent {
+ state = { showAll: false };
-export class TableContainer extends PureComponent {
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 {
: 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 {
this.showAll()}
+ info={
+
+ Showing only {{ MAX_NUMBER_OF_COLUMNS }} columns
+
+ }
+ tooltip={
+ 'Showing too many columns in a single table may impact performance and make data harder to read. Consider refining your queries.'
+ }
+ buttonLabel={Show all columns}
+ />
+ ),
+ ]}
width={width}
height={this.getTableHeight(data.length, this.hasSubFrames(data))}
loadingState={loading ? LoadingState.Loading : undefined}
diff --git a/public/app/features/explore/state/query.ts b/public/app/features/explore/state/query.ts
index 0a55121970d..88181538dcd 100644
--- a/public/app/features/explore/state/query.ts
+++ b/public/app/features/explore/state/query.ts
@@ -1303,10 +1303,7 @@ export const queryReducer = (state: ExploreItemState, action: AnyAction): Explor
return state;
};
-export const processQueryResponse = (
- state: ExploreItemState,
- action: PayloadAction
-): ExploreItemState => {
+const processQueryResponse = (state: ExploreItemState, action: PayloadAction): ExploreItemState => {
const { response } = action.payload;
const {
request,
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index 7645b538e9b..4135c2899fd 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -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...",
diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json
index 7df520bca8f..bfd929d3ac1 100644
--- a/public/locales/pseudo-LOCALE/grafana.json
+++ b/public/locales/pseudo-LOCALE/grafana.json
@@ -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ģ...",