diff --git a/public/app/features/explore/ExploreToolbar.tsx b/public/app/features/explore/ExploreToolbar.tsx index 7d1726b5596..8d49a6792ed 100644 --- a/public/app/features/explore/ExploreToolbar.tsx +++ b/public/app/features/explore/ExploreToolbar.tsx @@ -6,28 +6,23 @@ import classNames from 'classnames'; import { css } from 'emotion'; import { ExploreId, ExploreItemState } from 'app/types/explore'; -import { Icon, IconButton, LegacyForms, SetInterval, Tooltip } from '@grafana/ui'; -import { DataQuery, DataSourceInstanceSettings, RawTimeRange, TimeRange, TimeZone } from '@grafana/data'; +import { Icon, IconButton, SetInterval, Tooltip } from '@grafana/ui'; +import { DataSourceInstanceSettings, RawTimeRange, TimeRange, TimeZone } from '@grafana/data'; import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; import { StoreState } from 'app/types/store'; import { createAndCopyShortLink } from 'app/core/utils/shortLinks'; import { changeDatasource } from './state/datasource'; import { splitClose, splitOpen } from './state/main'; import { syncTimes, changeRefreshInterval } from './state/time'; -import { updateLocation } from 'app/core/actions'; import { getTimeZone } from '../profile/state/selectors'; import { updateTimeZoneForSession } from '../profile/state/reducers'; -import { getDashboardSrv } from '../dashboard/services/DashboardSrv'; -import kbn from '../../core/utils/kbn'; import { ExploreTimeControls } from './ExploreTimeControls'; import { LiveTailButton } from './LiveTailButton'; import { ResponsiveButton } from './ResponsiveButton'; import { RunButton } from './RunButton'; import { LiveTailControls } from './useLiveTailControls'; -import { setDashboardQueriesToUpdateOnLoad } from '../dashboard/state/reducers'; import { cancelQueries, clearQueries, runQueries } from './state/query'; - -const { ButtonSelect } = LegacyForms; +import ReturnToDashboardButton from './ReturnToDashboardButton'; const getStyles = memoizeOne(() => { return { @@ -56,8 +51,6 @@ interface StateProps { hasLiveOption: boolean; isLive: boolean; isPaused: boolean; - originPanelId?: number | null; - queries: DataQuery[]; datasourceLoading?: boolean | null; containerWidth: number; datasourceName?: string; @@ -72,8 +65,6 @@ interface DispatchProps { split: typeof splitOpen; syncTimes: typeof syncTimes; changeRefreshInterval: typeof changeRefreshInterval; - updateLocation: typeof updateLocation; - setDashboardQueriesToUpdateOnLoad: typeof setDashboardQueriesToUpdateOnLoad; onChangeTimeZone: typeof updateTimeZoneForSession; } @@ -106,40 +97,6 @@ export class UnConnectedExploreToolbar extends PureComponent { syncTimes(exploreId); }; - returnToPanel = async ({ withChanges = false } = {}) => { - const { originPanelId, queries } = this.props; - - const dashboardSrv = getDashboardSrv(); - const dash = dashboardSrv.getCurrent(); - const titleSlug = kbn.slugifyForUrl(dash.title); - - if (withChanges) { - this.props.setDashboardQueriesToUpdateOnLoad({ - panelId: originPanelId!, - queries: this.cleanQueries(queries), - }); - } - - const query: any = {}; - - if (withChanges || dash.panelInEdit) { - query.editPanel = originPanelId; - } else if (dash.panelInView) { - query.viewPanel = originPanelId; - } - - this.props.updateLocation({ path: `/d/${dash.uid}/:${titleSlug}`, query }); - }; - - // Remove explore specific parameters from queries - private cleanQueries(queries: DataQuery[]) { - return queries.map((query: DataQuery & { context?: string }) => { - delete query.context; - delete query.key; - return query; - }); - } - render() { const { datasourceMissing, @@ -156,18 +113,11 @@ export class UnConnectedExploreToolbar extends PureComponent { hasLiveOption, isLive, isPaused, - originPanelId, containerWidth, onChangeTimeZone, } = this.props; const styles = getStyles(); - const originDashboardIsEditable = originPanelId && Number.isInteger(originPanelId); - const panelReturnClasses = classNames('btn', 'navbar-button', { - 'btn--radius-right-0': originDashboardIsEditable, - 'navbar-button navbar-button--border-right-0': originDashboardIsEditable, - }); - const showSmallDataSourcePicker = (splitted ? containerWidth < 700 : containerWidth < 800) || false; const showSmallTimePicker = splitted || containerWidth < 1210; @@ -213,25 +163,7 @@ export class UnConnectedExploreToolbar extends PureComponent { ) : null} - - {originPanelId && Number.isInteger(originPanelId) && !splitted && ( -
- - - - {originDashboardIsEditable && ( - this.returnToPanel({ withChanges: true })} - maxMenuHeight={380} - /> - )} -
- )} - + {exploreId === 'left' && !splitted ? (
>) => { + const defaultProps = { + originPanelId: 1, + splitted: false, + exploreId: ExploreId.left, + queries: [], + updateLocation: jest.fn(), + setDashboardQueriesToUpdateOnLoad: jest.fn(), + }; + + return Object.assign(defaultProps, propsOverride) as ComponentProps; +}; + +describe('ReturnToDashboardButton', () => { + it('should render 2 buttons if originPanelId is provided', () => { + render(); + expect(screen.getAllByTestId(/returnButton/i)).toHaveLength(2); + }); + it('should not render any button if originPanelId is not provided', () => { + render(); + expect(screen.queryByTestId(/returnButton/i)).toBeNull(); + }); + it('should not render any button if split view', () => { + render(); + expect(screen.queryByTestId(/returnButton/i)).toBeNull(); + }); + it('should show option to return to dashboard with changes', () => { + render(); + const returnWithChangesButton = screen.getByTestId('returnButtonWithChanges'); + const selectButton = returnWithChangesButton.querySelector('.select-button'); + if (selectButton) { + fireEvent.click(selectButton); + } + expect(screen.getAllByText('Return to panel with changes')).toHaveLength(1); + }); +}); diff --git a/public/app/features/explore/ReturnToDashboardButton.tsx b/public/app/features/explore/ReturnToDashboardButton.tsx new file mode 100644 index 00000000000..724169e482c --- /dev/null +++ b/public/app/features/explore/ReturnToDashboardButton.tsx @@ -0,0 +1,118 @@ +import React, { FC } from 'react'; +import classNames from 'classnames'; +import { connect } from 'react-redux'; +import { hot } from 'react-hot-loader'; +import { Icon, Tooltip, LegacyForms } from '@grafana/ui'; +import { DataQuery } from '@grafana/data'; + +import kbn from '../../core/utils/kbn'; +import { getDashboardSrv } from '../dashboard/services/DashboardSrv'; +import { StoreState } from 'app/types'; +import { ExploreId } from 'app/types/explore'; +import { updateLocation } from 'app/core/actions'; +import { setDashboardQueriesToUpdateOnLoad } from '../dashboard/state/reducers'; + +const { ButtonSelect } = LegacyForms; + +interface Props { + exploreId: ExploreId; + splitted: boolean; + queries: DataQuery[]; + originPanelId?: number | null; + updateLocation: typeof updateLocation; + setDashboardQueriesToUpdateOnLoad: typeof setDashboardQueriesToUpdateOnLoad; +} + +export const UnconnectedReturnToDashboardButton: FC = ({ + originPanelId, + updateLocation, + setDashboardQueriesToUpdateOnLoad, + queries, + splitted, +}) => { + const withOriginId = originPanelId && Number.isInteger(originPanelId); + + // If in split mode, or no origin id, escape early and return null + if (splitted || !withOriginId) { + return null; + } + + const panelReturnClasses = classNames('btn', 'navbar-button', { + 'btn--radius-right-0': withOriginId, + 'navbar-button navbar-button--border-right-0': withOriginId, + }); + + const cleanQueries = (queries: DataQuery[]) => { + return queries.map((query: DataQuery & { context?: string }) => { + delete query.context; + delete query.key; + return query; + }); + }; + + const returnToPanel = async ({ withChanges = false } = {}) => { + const dashboardSrv = getDashboardSrv(); + const dash = dashboardSrv.getCurrent(); + const titleSlug = kbn.slugifyForUrl(dash.title); + + if (withChanges) { + setDashboardQueriesToUpdateOnLoad({ + panelId: originPanelId!, + queries: cleanQueries(queries), + }); + } + + const query: any = {}; + + if (withChanges || dash.panelInEdit) { + query.editPanel = originPanelId; + } else if (dash.panelInView) { + query.viewPanel = originPanelId; + } + + updateLocation({ path: `/d/${dash.uid}/:${titleSlug}`, query }); + }; + + return ( +
+ + + +
+ returnToPanel({ withChanges: true })} + maxMenuHeight={380} + /> +
+
+ ); +}; + +function mapStateToProps(state: StoreState, { exploreId }: { exploreId: ExploreId }) { + const explore = state.explore; + const splitted = state.explore.split; + const { datasourceInstance, queries, originPanelId } = explore[exploreId]; + + return { + exploreId, + datasourceInstance, + queries, + originPanelId, + splitted, + }; +} + +const mapDispatchToProps = { + updateLocation, + setDashboardQueriesToUpdateOnLoad, +}; +export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(UnconnectedReturnToDashboardButton)); diff --git a/public/app/features/explore/state/explorePane.ts b/public/app/features/explore/state/explorePane.ts index 2e155bacf86..c4bb995cd4a 100644 --- a/public/app/features/explore/state/explorePane.ts +++ b/public/app/features/explore/state/explorePane.ts @@ -1,5 +1,5 @@ import { AnyAction } from 'redux'; - +import { isEqual } from 'lodash'; import { getQueryKeys } from 'app/core/utils/explore'; import { ExploreId, ExploreItemState } from 'app/types/explore'; import { queryReducer } from './query'; @@ -258,8 +258,14 @@ export const paneReducer = (state: ExploreItemState = makeExplorePaneState(), ac } if (highlightLogsExpressionAction.match(action)) { - const { expressions } = action.payload; - return { ...state, logsHighlighterExpressions: expressions }; + const { expressions: newExpressions } = action.payload; + const { logsHighlighterExpressions: currentExpressions } = state; + + return { + ...state, + // Prevents re-renders. As logsHighlighterExpressions [] comes from datasource, we cannot control if it returns new array or not. + logsHighlighterExpressions: isEqual(newExpressions, currentExpressions) ? currentExpressions : newExpressions, + }; } if (changeDedupStrategyAction.match(action)) { diff --git a/public/app/features/explore/state/query.ts b/public/app/features/explore/state/query.ts index f7c01368006..7c89893f792 100644 --- a/public/app/features/explore/state/query.ts +++ b/public/app/features/explore/state/query.ts @@ -477,7 +477,6 @@ export const queryReducer = (state: ExploreItemState, action: AnyAction): Explor return { ...state, queries: nextQueries, - queryKeys: getQueryKeys(nextQueries, state.datasourceInstance), }; }