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 && ( -
- - - - - -
+ } + 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 ( +
+ + + + + + +
+ ); +} + +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ģ...",