diff --git a/public/app/core/hooks/useBusEvent.ts b/public/app/core/hooks/useBusEvent.ts new file mode 100644 index 00000000000..0c4abcaecdb --- /dev/null +++ b/public/app/core/hooks/useBusEvent.ts @@ -0,0 +1,17 @@ +import { useEffect, useState } from 'react'; + +import { BusEvent, BusEventType, EventBus } from '@grafana/data'; + +/** + A bit more efficient than using useObservable(eventBus.getStream(MyEventType)) as that will create a new Observable and subscription every render + */ +export function useBusEvent(eventBus: EventBus, eventType: BusEventType): T | undefined { + const [event, setEvent] = useState(); + + useEffect(() => { + const sub = eventBus.subscribe(eventType, setEvent); + return () => sub.unsubscribe(); + }, [eventBus, eventType]); + + return event; +} diff --git a/public/app/features/dashboard/components/DashNav/DashNav.test.tsx b/public/app/features/dashboard/components/DashNav/DashNav.test.tsx new file mode 100644 index 00000000000..81cfd6dfb04 --- /dev/null +++ b/public/app/features/dashboard/components/DashNav/DashNav.test.tsx @@ -0,0 +1,54 @@ +import { act, render, screen, waitFor } from '@testing-library/react'; +import React from 'react'; +import { Provider } from 'react-redux'; +import { Router } from 'react-router-dom'; + +import { locationService } from '@grafana/runtime/src'; +import { GrafanaContext } from 'app/core/context/GrafanaContext'; + +import { getGrafanaContextMock } from '../../../../../test/mocks/getGrafanaContextMock'; +import { setStarred } from '../../../../core/reducers/navBarTree'; +import { configureStore } from '../../../../store/configureStore'; +import { updateTimeZoneForSession } from '../../../profile/state/reducers'; +import { DashboardModel } from '../../state'; + +import { DashNav } from './DashNav'; + +describe('Public dashboard title tag', () => { + it('will be rendered when publicDashboardEnabled set to true in dashboard meta', async () => { + let dashboard = new DashboardModel({}, { publicDashboardEnabled: false }); + + const store = configureStore(); + const context = getGrafanaContextMock(); + const props = { + setStarred: jest.fn() as unknown as typeof setStarred, + updateTimeZoneForSession: jest.fn() as unknown as typeof updateTimeZoneForSession, + }; + + render( + + + + {}} + title="test" + /> + + + + ); + + const publicTag = screen.queryByText('Public'); + expect(publicTag).not.toBeInTheDocument(); + + act(() => { + dashboard.updateMeta({ publicDashboardEnabled: true }); + }); + + await waitFor(() => screen.getByText('Public')); + }); +}); diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index d6a1e551ec7..0f9b9e8d0d6 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -19,12 +19,14 @@ import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbarSeparator'; import config from 'app/core/config'; import { useGrafana } from 'app/core/context/GrafanaContext'; +import { useBusEvent } from 'app/core/hooks/useBusEvent'; import { DashboardCommentsModal } from 'app/features/dashboard/components/DashboardComments/DashboardCommentsModal'; import { SaveDashboardDrawer } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer'; import { ShareModal } from 'app/features/dashboard/components/ShareModal'; import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; import { updateTimeZoneForSession } from 'app/features/profile/state/reducers'; import { KioskMode } from 'app/types'; +import { DashboardMetaChangedEvent } from 'app/types/events'; import { setStarred } from '../../../../core/reducers/navBarTree'; import { getDashboardSrv } from '../../services/DashboardSrv'; @@ -75,6 +77,9 @@ export const DashNav = React.memo((props) => { const forceUpdate = useForceUpdate(); const { chrome } = useGrafana(); + // We don't really care about the event payload here only that it triggeres a re-render of this component + useBusEvent(props.dashboard.events, DashboardMetaChangedEvent); + const onStarDashboard = () => { const dashboardSrv = getDashboardSrv(); const { dashboard, setStarred } = props; diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard.test.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard.test.tsx index 017d54dd808..e6da5d32eb8 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard.test.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard.test.tsx @@ -119,6 +119,7 @@ describe('SharePublic', () => { await screen.findByText('Welcome to Grafana public dashboards alpha!'); expect(screen.getByText('Last 6 hours')).toBeInTheDocument(); }); + it('renders default absolute time in input 2', async () => { config.featureToggles.publicDashboards = true; const mockDashboard = new DashboardModel({ diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard.tsx index 30faf560c5b..c4fb590c65a 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard.tsx @@ -65,10 +65,7 @@ export const SharePublicDashboard = (props: Props) => { usage: false, }); - const timeRange = getTimeRange( - { from: props.dashboard.getDefaultTime().from, to: props.dashboard.getDefaultTime().to }, - props.dashboard.timezone - ); + const timeRange = getTimeRange(props.dashboard.getDefaultTime(), props.dashboard); useEffect(() => { reportInteraction('grafana_dashboards_public_share_viewed'); @@ -96,7 +93,7 @@ export const SharePublicDashboard = (props: Props) => { return; } - savePublicDashboardConfig(props.dashboard.uid, publicDashboard, setPublicDashboardConfig).catch(); + savePublicDashboardConfig(props.dashboard, publicDashboard, setPublicDashboardConfig).catch(); }; const onAcknowledge = useCallback( diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboardUtils.ts b/public/app/features/dashboard/components/ShareModal/SharePublicDashboardUtils.ts index b62baee71f2..8902f1e80d0 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboardUtils.ts +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboardUtils.ts @@ -6,6 +6,8 @@ import { VariableModel } from 'app/features/variables/types'; import { dispatch } from 'app/store/store'; import { DashboardDataDTO, DashboardMeta } from 'app/types/dashboard'; +import { DashboardModel } from '../../state'; + export interface PublicDashboard { accessToken?: string; isEnabled: boolean; @@ -28,12 +30,12 @@ export const getPublicDashboardConfig = async ( }; export const savePublicDashboardConfig = async ( - dashboardUid: string, + dashboard: DashboardModel, publicDashboardConfig: PublicDashboard, setPublicDashboard: React.Dispatch> ) => { const pdResp: PublicDashboard = await getBackendSrv().post( - savePublicDashboardConfigUrl(dashboardUid), + savePublicDashboardConfigUrl(dashboard.uid), publicDashboardConfig ); @@ -43,6 +45,12 @@ export const savePublicDashboardConfig = async ( dispatch(notifyApp(createSuccessNotification('Dashboard sharing configuration saved'))); setPublicDashboard(pdResp); + + // Update runtime emta flag + dashboard.updateMeta({ + publicDashboardUid: pdResp.uid, + publicDashboardEnabled: publicDashboardConfig.isEnabled, + }); }; export const getPublicDashboardConfigUrl = (dashboardUid: string) => { diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index 0e397e28d3b..86a16b949f1 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -25,7 +25,7 @@ import { variableAdapters } from 'app/features/variables/adapters'; import { onTimeRangeUpdated } from 'app/features/variables/state/actions'; import { GetVariables, getVariablesByKey } from 'app/features/variables/state/selectors'; import { CoreEvents, DashboardMeta, KioskMode } from 'app/types'; -import { DashboardPanelsChangedEvent, RenderEvent } from 'app/types/events'; +import { DashboardMetaChangedEvent, DashboardPanelsChangedEvent, RenderEvent } from 'app/types/events'; import { appEvents } from '../../../core/core'; import { dispatch } from '../../../store/store'; @@ -480,6 +480,11 @@ export class DashboardModel implements TimeModel { this.events.publish(new DashboardPanelsChangedEvent()); } + updateMeta(updates: Partial) { + this.meta = { ...this.meta, ...updates }; + this.events.publish(new DashboardMetaChangedEvent()); + } + sortPanelsByGridPos() { this.panels.sort((panelA, panelB) => { if (panelA.gridPos.y === panelB.gridPos.y) { diff --git a/public/app/types/events.ts b/public/app/types/events.ts index d4d984cfde5..57546810132 100644 --- a/public/app/types/events.ts +++ b/public/app/types/events.ts @@ -124,6 +124,10 @@ export class DashboardPanelsChangedEvent extends BusEventBase { static type = 'dashboard-panels-changed'; } +export class DashboardMetaChangedEvent extends BusEventBase { + static type = 'dashboard-meta-changed'; +} + export class PanelDirectiveReadyEvent extends BusEventBase { static type = 'panel-directive-ready'; }