diff --git a/packages/grafana-data/src/types/explore.ts b/packages/grafana-data/src/types/explore.ts index 68f9b999d40..62c4fdcf62b 100644 --- a/packages/grafana-data/src/types/explore.ts +++ b/packages/grafana-data/src/types/explore.ts @@ -49,6 +49,7 @@ export interface ExploreUrlState { queries: T[]; range: URLRange; panelsState?: ExplorePanelsState; + compact?: boolean; } export interface ExplorePanelsState extends Partial> { @@ -89,6 +90,7 @@ export interface SplitOpenOptions { range?: TimeRange; panelsState?: ExplorePanelsState; correlationHelperData?: ExploreCorrelationHelperData; + compact?: boolean; } /** diff --git a/public/app/features/explore/Explore.test.tsx b/public/app/features/explore/Explore.test.tsx index c1bcd2a7d97..cb47295d59d 100644 --- a/public/app/features/explore/Explore.test.tsx +++ b/public/app/features/explore/Explore.test.tsx @@ -108,6 +108,8 @@ const dummyProps: Props = { dsToExplore: [], }, changeDatasource: jest.fn(), + compact: false, + changeCompactMode: jest.fn(), }; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 57ef823de4d..a88d594c6d1 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -58,7 +58,7 @@ import { SecondaryActions } from './SecondaryActions'; import TableContainer from './Table/TableContainer'; import { TraceViewContainer } from './TraceView/TraceViewContainer'; import { changeDatasource } from './state/datasource'; -import { changeSize } from './state/explorePane'; +import { changeSize, changeCompactMode } from './state/explorePane'; import { splitOpen } from './state/main'; import { addQueryRow, @@ -182,14 +182,16 @@ export class Explore extends PureComponent { onContentOutlineToogle = () => { store.set(CONTENT_OUTLINE_LOCAL_STORAGE_KEYS.visible, !this.state.contentOutlineVisible); this.setState((state) => { + const newContentOutlineVisible = this.props.compact ? true : !state.contentOutlineVisible; reportInteraction('explore_toolbar_contentoutline_clicked', { item: 'outline', - type: state.contentOutlineVisible ? 'close' : 'open', + type: newContentOutlineVisible ? 'open' : 'close', }); return { - contentOutlineVisible: !state.contentOutlineVisible, + contentOutlineVisible: newContentOutlineVisible, }; }); + this.props.changeCompactMode(this.props.exploreId, false); }; /** @@ -306,9 +308,12 @@ export class Explore extends PureComponent { updateTimeRange({ exploreId, absoluteRange }); }; + /** + * Used for interaction from the visualizations. Will open split view in compact mode. + */ onSplitOpen = (panelType: string) => { return async (options?: SplitOpenOptions) => { - this.props.splitOpen(options); + this.props.splitOpen(options ? { ...options, compact: true } : options); if (options && this.props.datasourceInstance) { const target = (await getDataSourceSrv().get(options.datasourceUid)).type; const source = @@ -573,6 +578,8 @@ export class Explore extends PureComponent { correlationEditorHelperData, showQueryInspector, setShowQueryInspector, + splitted, + compact, } = this.props; const { contentOutlineVisible } = this.state; const styles = getStyles(theme); @@ -614,7 +621,7 @@ export class Explore extends PureComponent { }} >
- {contentOutlineVisible && ( + {contentOutlineVisible && !compact && ( )} { mergeSingleChild={true} > - + {!splitted && } {correlationsBox} - + + this.props.changeCompactMode(this.props.exploreId, false) + } + /> size && updateSplitSize(size)} > - {panes.map(([exploreId]) => { + {panes.map(([exploreId, pane]) => { return ( - + {pane.initialized ? ( + + ) : ( + + )} ); })} diff --git a/public/app/features/explore/QueryRows.test.tsx b/public/app/features/explore/QueryRows.test.tsx index 03989ec2950..cd11f943811 100644 --- a/public/app/features/explore/QueryRows.test.tsx +++ b/public/app/features/explore/QueryRows.test.tsx @@ -79,7 +79,7 @@ describe('Explore QueryRows', () => { render( - + ); @@ -100,7 +100,7 @@ describe('Explore QueryRows', () => { render( - + ); @@ -117,7 +117,7 @@ describe('Explore QueryRows', () => { render( - + ); diff --git a/public/app/features/explore/QueryRows.tsx b/public/app/features/explore/QueryRows.tsx index 084c180544f..7e74a736f8b 100644 --- a/public/app/features/explore/QueryRows.tsx +++ b/public/app/features/explore/QueryRows.tsx @@ -4,6 +4,7 @@ import { useCallback, useMemo } from 'react'; import { CoreApp, getNextRefId } from '@grafana/data'; import { reportInteraction } from '@grafana/runtime'; import { DataQuery, DataSourceRef } from '@grafana/schema'; +import { ExploreItemState } from 'app/types/explore'; import { useDispatch, useSelector } from 'app/types/store'; import { getDatasourceSrv } from '../plugins/datasource_srv'; @@ -16,23 +17,25 @@ import { getExploreItemSelector } from './state/selectors'; interface Props { exploreId: string; + changeCompactMode: (compact: boolean) => void; + isOpen?: boolean; } const makeSelectors = (exploreId: string) => { const exploreItemSelector = getExploreItemSelector(exploreId); return { - getQueries: createSelector(exploreItemSelector, (s) => s!.queries), - getQueryResponse: createSelector(exploreItemSelector, (s) => s!.queryResponse), - getHistory: createSelector(exploreItemSelector, (s) => s!.history), - getEventBridge: createSelector(exploreItemSelector, (s) => s!.eventBridge), + getQueries: createSelector(exploreItemSelector, (s: ExploreItemState | undefined) => s!.queries), + getQueryResponse: createSelector(exploreItemSelector, (s: ExploreItemState | undefined) => s!.queryResponse), + getHistory: createSelector(exploreItemSelector, (s: ExploreItemState | undefined) => s!.history), + getEventBridge: createSelector(exploreItemSelector, (s: ExploreItemState | undefined) => s!.eventBridge), getDatasourceInstanceSettings: createSelector( exploreItemSelector, - (s) => getDatasourceSrv().getInstanceSettings(s!.datasourceInstance?.uid)! + (s: ExploreItemState | undefined) => getDatasourceSrv().getInstanceSettings(s!.datasourceInstance?.uid)! ), }; }; -export const QueryRows = ({ exploreId }: Props) => { +export const QueryRows = ({ exploreId, isOpen, changeCompactMode }: Props) => { const dispatch = useDispatch(); const { getQueries, getDatasourceInstanceSettings, getQueryResponse, getHistory, getEventBridge } = useMemo( () => makeSelectors(exploreId), @@ -86,6 +89,12 @@ export const QueryRows = ({ exploreId }: Props) => { reportInteraction('grafana_query_row_toggle', queryStatus === undefined ? {} : { queryEnabled: queryStatus }); }; + const onQueryOpenChanged = () => { + // Disables compact mode when query is opened. + // Compact mode can also be disabled by opening Content Outline. + changeCompactMode(false); + }; + return ( { onQueryRemoved={onQueryRemoved} onQueryToggled={onQueryToggled} onQueryReplacedFromLibrary={onQueryReplacedFromLibrary} + onQueryOpenChanged={onQueryOpenChanged} data={queryResponse} app={CoreApp.Explore} history={history} eventBus={eventBridge} + isOpen={isOpen} queryRowWrapper={(children, refId) => ( { + Object.entries(urlState.panes).map(([exploreId, { datasource, queries, range, panelsState, compact }]) => { return getPaneDatasource(datasource, queries, orgId).then((paneDatasource) => { return Promise.resolve( // Given the Grafana datasource will always be present, this should always be defined. @@ -64,13 +64,13 @@ export function initializeFromURL( ]; } - return { exploreId, range, panelsState, queries: validQueries, datasource: paneDatasource }; + return { exploreId, compact, range, panelsState, queries: validQueries, datasource: paneDatasource }; }); }); }) ).then(async (panes) => { const initializedPanes = await Promise.all( - panes.map(({ exploreId, range, panelsState, queries, datasource }) => { + panes.map(({ exploreId, range, panelsState, queries, datasource, compact }) => { return dispatch( initializeExplore({ exploreId, @@ -79,6 +79,7 @@ export function initializeFromURL( range: fromURLRange(range), panelsState, eventBridge: new EventBusSrv(), + compact: !!compact, }) ).unwrap(); }) diff --git a/public/app/features/explore/hooks/useStateSync/synchronizer/toURL.ts b/public/app/features/explore/hooks/useStateSync/synchronizer/toURL.ts index 9485eef2446..c746d767048 100644 --- a/public/app/features/explore/hooks/useStateSync/synchronizer/toURL.ts +++ b/public/app/features/explore/hooks/useStateSync/synchronizer/toURL.ts @@ -5,7 +5,7 @@ import { MutableRefObject } from 'react'; import { UrlQueryMap } from '@grafana/data'; import { LocationService } from '@grafana/runtime'; import { changeDatasource } from 'app/features/explore/state/datasource'; -import { changePanelsStateAction } from 'app/features/explore/state/explorePane'; +import { changeCompactModeAction, changePanelsStateAction } from 'app/features/explore/state/explorePane'; import { splitClose, splitOpen } from 'app/features/explore/state/main'; import { runQueries } from 'app/features/explore/state/query'; import { changeRangeAction } from 'app/features/explore/state/time'; @@ -21,6 +21,7 @@ We want to update the URL when: - range is changed - panel state is updated - a datasource change has completed. + - compact mode changes Note: Changing datasource causes a bunch of actions to be dispatched, we want to update the URL only when the change set has completed. This is done by checking if the changeDatasource.pending action @@ -37,6 +38,7 @@ export function syncToURLPredicate(paused: MutableRefObject, action: Ac changeRangeAction.type, changePanelsStateAction.type, changeDatasource.fulfilled.type, + changeCompactModeAction.type, ].includes(action.type) && !paused.current ); } diff --git a/public/app/features/explore/state/explorePane.ts b/public/app/features/explore/state/explorePane.ts index c9fc652279f..8cc977a6717 100644 --- a/public/app/features/explore/state/explorePane.ts +++ b/public/app/features/explore/state/explorePane.ts @@ -46,6 +46,12 @@ export interface ChangeSizePayload { export const changeSizeAction = createAction('explore/changeSize'); +interface ChangeCompactModePayload { + exploreId: string; + compact: boolean; +} +export const changeCompactModeAction = createAction('explore/changeCompactMode'); + /** * Tracks the state of explore panels that gets synced with the url. */ @@ -101,6 +107,7 @@ interface InitializeExplorePayload { range: TimeRange; history: HistoryItem[]; datasourceInstance?: DataSourceApi; + compact: boolean; eventBridge: EventBusExtended; } @@ -127,6 +134,10 @@ export function changeSize(exploreId: string, { width }: { width: number }): Pay return changeSizeAction({ exploreId, width }); } +export function changeCompactMode(exploreId: string, compact: boolean): PayloadAction { + return changeCompactModeAction({ exploreId, compact }); +} + export interface InitializeExploreOptions { exploreId: string; datasource: DataSourceRef | string | undefined; @@ -136,6 +147,7 @@ export interface InitializeExploreOptions { correlationHelperData?: ExploreCorrelationHelperData; position?: number; eventBridge: EventBusExtended; + compact: boolean; } /** @@ -155,6 +167,7 @@ export const initializeExplore = createAsyncThunk( queries, range, panelsState, + compact, correlationHelperData, eventBridge, }: InitializeExploreOptions, @@ -177,6 +190,7 @@ export const initializeExplore = createAsyncThunk( range: getRange(range, getTimeZone(getState().user)), datasourceInstance: instance, history, + compact, eventBridge, }) ); @@ -226,6 +240,11 @@ export const paneReducer = (state: ExploreItemState = makeExplorePaneState(), ac return { ...state, containerWidth }; } + if (changeCompactModeAction.match(action)) { + const compact = action.payload.compact; + return { ...state, compact }; + } + if (changePanelsStateAction.match(action)) { const { panelsState } = action.payload; return { ...state, panelsState }; @@ -244,7 +263,7 @@ export const paneReducer = (state: ExploreItemState = makeExplorePaneState(), ac } if (initializeExploreAction.match(action)) { - const { queries, range, datasourceInstance, history, eventBridge } = action.payload; + const { queries, range, datasourceInstance, history, eventBridge, compact } = action.payload; return { ...state, @@ -258,6 +277,7 @@ export const paneReducer = (state: ExploreItemState = makeExplorePaneState(), ac queryResponse: createEmptyQueryResponse(), cache: [], correlations: [], + compact, }; } diff --git a/public/app/features/explore/state/main.ts b/public/app/features/explore/state/main.ts index d64ec7eca95..9d1304f8808 100644 --- a/public/app/features/explore/state/main.ts +++ b/public/app/features/explore/state/main.ts @@ -96,6 +96,7 @@ export const splitOpen = createAsyncThunk( panelsState: options?.panelsState || originState?.panelsState, correlationHelperData: options?.correlationHelperData, eventBridge: new EventBusSrv(), + compact: !!options?.compact, }) ); diff --git a/public/app/features/explore/state/utils.ts b/public/app/features/explore/state/utils.ts index 4aba4a85f35..89ee58869af 100644 --- a/public/app/features/explore/state/utils.ts +++ b/public/app/features/explore/state/utils.ts @@ -75,6 +75,7 @@ export const makeExplorePaneState = (overrides?: Partial): Exp supplementaryQueries: loadSupplementaryQueries(), panelsState: {}, correlations: undefined, + compact: false, ...overrides, }); diff --git a/public/app/features/query/components/QueryEditorRow.tsx b/public/app/features/query/components/QueryEditorRow.tsx index 5e1a6c845f6..352451481d0 100644 --- a/public/app/features/query/components/QueryEditorRow.tsx +++ b/public/app/features/query/components/QueryEditorRow.tsx @@ -64,9 +64,11 @@ export interface Props { onQueryCopied?: () => void; onQueryRemoved?: () => void; onQueryToggled?: (queryStatus?: boolean | undefined) => void; + onQueryOpenChanged?: (status?: boolean | undefined) => void; onQueryReplacedFromLibrary?: () => void; collapsable?: boolean; hideRefId?: boolean; + isOpen?: boolean; } interface State { @@ -433,7 +435,7 @@ export class QueryEditorRow extends PureComponent extends PureComponent
diff --git a/public/app/features/query/components/QueryEditorRows.tsx b/public/app/features/query/components/QueryEditorRows.tsx index a9b9d9c0186..b1cc3e70ba2 100644 --- a/public/app/features/query/components/QueryEditorRows.tsx +++ b/public/app/features/query/components/QueryEditorRows.tsx @@ -37,9 +37,11 @@ export interface Props { onQueryCopied?: () => void; onQueryRemoved?: () => void; onQueryToggled?: (queryStatus?: boolean | undefined) => void; + onQueryOpenChanged?: (status?: boolean | undefined) => void; onUpdateDatasources?: (datasource: DataSourceRef) => void; onQueryReplacedFromLibrary?: () => void; queryRowWrapper?: (children: ReactNode, refId: string) => ReactNode; + isOpen?: boolean; } export class QueryEditorRows extends PureComponent { @@ -173,8 +175,10 @@ export class QueryEditorRows extends PureComponent { onQueryCopied, onQueryRemoved, onQueryToggled, + onQueryOpenChanged, onQueryReplacedFromLibrary, queryRowWrapper, + isOpen, } = this.props; return ( @@ -206,12 +210,14 @@ export class QueryEditorRows extends PureComponent { onQueryCopied={onQueryCopied} onQueryRemoved={onQueryRemoved} onQueryToggled={onQueryToggled} + onQueryOpenChanged={onQueryOpenChanged} onQueryReplacedFromLibrary={onQueryReplacedFromLibrary} queries={queries} app={app} range={getTimeSrv().timeRange()} history={history} eventBus={eventBus} + isOpen={isOpen} /> ); diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 11ac0ad84d0..8c6912676da 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -229,6 +229,11 @@ export interface ExploreItemState { correlationEditorHelperData?: ExploreCorrelationHelperData; correlations?: CorrelationData[]; + + /** + * If set to true, all query rows will be collapsed initially and the content outline will be hidden + */ + compact: boolean; } export interface ExploreUpdateState { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index a36e09b69c4..bdb0859ba38 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -7006,6 +7006,9 @@ "pro-tip-define-sources-through-configuration-files": " ProTip: You can also define data sources through configuration files. " } }, + "pane": { + "loading-placeholder": "Loading..." + }, "prev": "Prev", "queryless-apps-extensions": { "aria-label-go-queryless": "Go queryless"