From a8e1306d4e679401a3d5d681967de813fe9e5819 Mon Sep 17 00:00:00 2001 From: Milan Raj Date: Tue, 25 Nov 2025 13:52:11 -0600 Subject: [PATCH 1/7] Doc updates for architecture overview (#66) Doc updates for architecture overview references Co-authored-by: rajsite <1588923+rajsite@users.noreply.github.com> Signed-off-by: Ahalya Radhakrishnan --- CONTRIBUTING.md | 6 ++++++ CONTRIBUTING_NI.md | 4 ++++ README.md | 4 ++++ .../dashboard/containers/DashboardPage.tsx | 14 +++++++++++++- .../plugins/panel/timeseries/TimeSeriesPanel.tsx | 12 ++++++++++-- public/app/types/events.ts | 4 ++++ 6 files changed, 41 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4527ac3487d1d..70cc9b507bab8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,9 @@ +**Attention: This is a fork of [Grafana](https://github.com/grafana/grafana) for use in the NI SystemLink™ platform.** + +See the NI-specific contribution docs in [`CONTRIBUTING_NI.md`](./CONTRIBUTING_NI.md). + +--- + # Contributing to Grafana Thank you for your interest in contributing to Grafana! We welcome all people who want to contribute in a healthy and constructive manner within our community. To help us create a safe and positive community experience for all, we require all participants to adhere to the [Code of Conduct](CODE_OF_CONDUCT.md). diff --git a/CONTRIBUTING_NI.md b/CONTRIBUTING_NI.md index a1153e0f63ee1..ec70d52b4f9a0 100644 --- a/CONTRIBUTING_NI.md +++ b/CONTRIBUTING_NI.md @@ -74,6 +74,10 @@ Once the cherry pick is completed, force push main (`git push -f`). If you are n If you find you need to update `main` multiple times during the same release upgrade process (you may find issues that need resolution after initial attempt), or as part of the cherry-pick process you inadvertently introduced new commits, you can/should collapse those commits into a single commit (the most recent commit) using `git rebase -i (commit before first new commit)`. +#### Validate a new release of Grafana? + +See the validation instructions in the [`Skyline/Grafana README.md`](https://dev.azure.com/ni/DevCentral/_git/Skyline?path=/Grafana/README.MD&_a=preview). + ### Security scanning with Snyk This repository uses [Snyk](https://snyk.io/) for security scanning to identify and fix vulnerabilities in code before they reach production. Snyk provides Static Application Security Testing (SAST) that scans your code for security issues as you develop. diff --git a/README.md b/README.md index ac89f6c24724e..1a62c71b54d1e 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,10 @@ - Hiding the "General" folder - Allow unsigned nested plugins if parent is allowed +See the NI-specific contribution docs in [`CONTRIBUTING_NI.md`](./CONTRIBUTING_NI.md). + +See an overview of the SystemLink Grafana integration in the [Dashboard Host Service runbook](https://dev.azure.com/ni/DevCentral/_wiki/wikis/Stratus/91033/Dashboard-Host-Service-runbook). + --- ![Grafana Logo (Light)](docs/logo-horizontal.png#gh-light-mode-only) diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 2f9a6f874fb9c..30dd349ff4a75 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -20,7 +20,7 @@ import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; import { AngularDeprecationNotice } from 'app/features/plugins/angularDeprecation/AngularDeprecationNotice'; import { AngularMigrationNotice } from 'app/features/plugins/angularDeprecation/AngularMigrationNotice'; import { KioskMode, StoreState } from 'app/types'; -import { PanelEditEnteredEvent, PanelEditExitedEvent } from 'app/types/events'; +import { NIRefreshDashboardEvent, PanelEditEnteredEvent, PanelEditExitedEvent } from 'app/types/events'; import { cancelVariables, templateVarsChangedInUrl } from '../../variables/state/actions'; import { findTemplateVarChanges } from '../../variables/utils'; @@ -44,6 +44,8 @@ import { DashboardPageRouteParams, DashboardPageRouteSearchParams } from './type import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; +import { appEvents } from 'app/core/core'; +import { Subscription } from 'rxjs'; export const mapStateToProps = (state: StoreState) => ({ initPhase: state.dashboard.initPhase, @@ -119,6 +121,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ export class UnthemedDashboardPage extends PureComponent { declare context: GrafanaContextType; static contextType = GrafanaContext; + private eventSubs = new Subscription(); private forceRouteReloadCounter = 0; state: State = this.getCleanState(); @@ -137,9 +140,18 @@ export class UnthemedDashboardPage extends PureComponent { componentDidMount() { this.initDashboard(); this.forceRouteReloadCounter = (this.props.location.state as any)?.routeReloadCounter || 0; + + console.log('DashboardPage mounted - subscribing to NIRefreshDashboardEvent'); + this.eventSubs.add( + appEvents.subscribe(NIRefreshDashboardEvent, () => { + console.log('NIRefreshDashboardEvent received in DashboardPage!'); + getTimeSrv().refreshTimeModel(); + }) + ); } componentWillUnmount() { + this.eventSubs.unsubscribe(); this.closeDashboard(); } diff --git a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx index e4124886ce266..73da67400dad6 100644 --- a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx +++ b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx @@ -1,13 +1,14 @@ -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { PanelProps, DataFrameType, DashboardCursorSync } from '@grafana/data'; -import { PanelDataErrorView } from '@grafana/runtime'; +import { PanelDataErrorView, getAppEvents } from '@grafana/runtime'; import { TooltipDisplayMode, VizOrientation } from '@grafana/schema'; import { EventBusPlugin, KeyboardPlugin, TooltipPlugin2, usePanelContext } from '@grafana/ui'; import { TimeRange2, TooltipHoverMode } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2'; import { TimeSeries } from 'app/core/components/TimeSeries/TimeSeries'; import { config } from 'app/core/config'; +import { NIRefreshDashboardEvent } from 'app/types/events'; import { TimeSeriesTooltip } from './TimeSeriesTooltip'; import { Options } from './panelcfg.gen'; import { AnnotationsPlugin2 } from './plugins/AnnotationsPlugin2'; @@ -61,6 +62,13 @@ export const TimeSeriesPanel = ({ const [newAnnotationRange, setNewAnnotationRange] = useState(null); const cursorSync = sync?.() ?? DashboardCursorSync.Off; + // TEST: Publish NIRefreshDashboardEvent to verify it's being caught + // Remove this after testing + useEffect(() => { + console.log('🚀 TimeSeriesPanel publishing NIRefreshDashboardEvent (TEST)'); + getAppEvents().publish(new NIRefreshDashboardEvent()); + }, []); + if (!frames || suggestions) { return ( { static type = 'annotation-query-started'; } From 869b04a56faf794b71ec7f945a73b9d9c9139e5d Mon Sep 17 00:00:00 2001 From: Ahalya Radhakrishnan Date: Thu, 8 Jan 2026 15:33:15 +0530 Subject: [PATCH 2/7] moved logic to proxy page Signed-off-by: Ahalya Radhakrishnan --- .../dashboard/containers/DashboardPage.tsx | 14 +------------- .../dashboard/containers/DashboardPageProxy.tsx | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 30dd349ff4a75..2f9a6f874fb9c 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -20,7 +20,7 @@ import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; import { AngularDeprecationNotice } from 'app/features/plugins/angularDeprecation/AngularDeprecationNotice'; import { AngularMigrationNotice } from 'app/features/plugins/angularDeprecation/AngularMigrationNotice'; import { KioskMode, StoreState } from 'app/types'; -import { NIRefreshDashboardEvent, PanelEditEnteredEvent, PanelEditExitedEvent } from 'app/types/events'; +import { PanelEditEnteredEvent, PanelEditExitedEvent } from 'app/types/events'; import { cancelVariables, templateVarsChangedInUrl } from '../../variables/state/actions'; import { findTemplateVarChanges } from '../../variables/utils'; @@ -44,8 +44,6 @@ import { DashboardPageRouteParams, DashboardPageRouteSearchParams } from './type import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; -import { appEvents } from 'app/core/core'; -import { Subscription } from 'rxjs'; export const mapStateToProps = (state: StoreState) => ({ initPhase: state.dashboard.initPhase, @@ -121,7 +119,6 @@ const getStyles = (theme: GrafanaTheme2) => ({ export class UnthemedDashboardPage extends PureComponent { declare context: GrafanaContextType; static contextType = GrafanaContext; - private eventSubs = new Subscription(); private forceRouteReloadCounter = 0; state: State = this.getCleanState(); @@ -140,18 +137,9 @@ export class UnthemedDashboardPage extends PureComponent { componentDidMount() { this.initDashboard(); this.forceRouteReloadCounter = (this.props.location.state as any)?.routeReloadCounter || 0; - - console.log('DashboardPage mounted - subscribing to NIRefreshDashboardEvent'); - this.eventSubs.add( - appEvents.subscribe(NIRefreshDashboardEvent, () => { - console.log('NIRefreshDashboardEvent received in DashboardPage!'); - getTimeSrv().refreshTimeModel(); - }) - ); } componentWillUnmount() { - this.eventSubs.unsubscribe(); this.closeDashboard(); } diff --git a/public/app/features/dashboard/containers/DashboardPageProxy.tsx b/public/app/features/dashboard/containers/DashboardPageProxy.tsx index 1570fbc6ee7fd..3fd4f7f840e85 100644 --- a/public/app/features/dashboard/containers/DashboardPageProxy.tsx +++ b/public/app/features/dashboard/containers/DashboardPageProxy.tsx @@ -1,11 +1,15 @@ +import { useEffect } from 'react'; import { useLocation, useParams } from 'react-router-dom-v5-compat'; import { useAsync } from 'react-use'; import { config } from '@grafana/runtime'; +import { appEvents } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import DashboardScenePage from 'app/features/dashboard-scene/pages/DashboardScenePage'; import { getDashboardScenePageStateManager } from 'app/features/dashboard-scene/pages/DashboardScenePageStateManager'; +import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { DashboardRoutes } from 'app/types'; +import { NIRefreshDashboardEvent } from 'app/types/events'; import DashboardPage, { DashboardPageParams } from './DashboardPage'; import { DashboardPageError } from './DashboardPageError'; @@ -24,6 +28,17 @@ function DashboardPageProxy(props: DashboardPageProxyProps) { const params = useParams(); const location = useLocation(); + // Subscribe to NIRefreshDashboardEvent regardless of which dashboard implementation is used + useEffect(() => { + const subscription = appEvents.subscribe(NIRefreshDashboardEvent, () => { + getTimeSrv().refreshTimeModel(); + }); + + return () => { + subscription.unsubscribe(); + }; + }, []); + // Force scenes if v2 api and scenes are enabled if (config.featureToggles.useV2DashboardsAPI && config.featureToggles.dashboardScene && !forceOld) { console.log('DashboardPageProxy: forcing scenes because of v2 api'); From ec0ab78f4d4b8a19e83300ec63210e05557dd123 Mon Sep 17 00:00:00 2001 From: Ahalya Radhakrishnan Date: Thu, 8 Jan 2026 18:43:22 +0530 Subject: [PATCH 3/7] add test for dashboard page proxy component Signed-off-by: Ahalya Radhakrishnan --- .../containers/DashboardPageProxy.test.tsx | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/public/app/features/dashboard/containers/DashboardPageProxy.test.tsx b/public/app/features/dashboard/containers/DashboardPageProxy.test.tsx index 8fe6faeac8d56..a421e6ed3fea9 100644 --- a/public/app/features/dashboard/containers/DashboardPageProxy.test.tsx +++ b/public/app/features/dashboard/containers/DashboardPageProxy.test.tsx @@ -5,6 +5,7 @@ import { render } from 'test/test-utils'; import { selectors } from '@grafana/e2e-selectors'; import { config, locationService } from '@grafana/runtime'; +import { appEvents } from 'app/core/core'; import { HOME_DASHBOARD_CACHE_KEY, getDashboardScenePageStateManager, @@ -14,11 +15,20 @@ import { setupLoadDashboardRuntimeErrorMock, } from 'app/features/dashboard-scene/utils/test-utils'; import { DashboardDTO, DashboardRoutes } from 'app/types'; +import { NIRefreshDashboardEvent } from 'app/types/events'; import { DashboardLoaderSrv, setDashboardLoaderSrv } from '../services/DashboardLoaderSrv'; import DashboardPageProxy, { DashboardPageProxyProps } from './DashboardPageProxy'; +const mockRefreshTimeModel = jest.fn(); + +jest.mock('../services/TimeSrv', () => ({ + getTimeSrv: jest.fn(() => ({ + refreshTimeModel: mockRefreshTimeModel, + })), +})); + const dashMock: DashboardDTO = { dashboard: { id: 1, @@ -280,4 +290,102 @@ describe('DashboardPageProxy', () => { expect(await screen.findByTestId('dashboard-page-error')).toHaveTextContent('Runtime error'); }); }); + + describe('NIRefreshDashboardEvent subscription', () => { + beforeEach(() => { + config.featureToggles.dashboardSceneForViewers = false; + mockRefreshTimeModel.mockClear(); + getDashboardScenePageStateManager().setDashboardCache('test-uid', dashMock); + }); + + it('should subscribe to NIRefreshDashboardEvent on mount', async () => { + const subscribeSpy = jest.spyOn(appEvents, 'subscribe'); + + act(() => { + setup({ + route: { routeName: DashboardRoutes.Normal, component: () => null, path: '/' }, + uid: 'test-uid', + }); + }); + + await waitFor(() => { + expect(subscribeSpy).toHaveBeenCalledWith(NIRefreshDashboardEvent, expect.any(Function)); + }); + + subscribeSpy.mockRestore(); + }); + + it('should call refreshTimeModel when NIRefreshDashboardEvent is published', async () => { + act(() => { + setup({ + route: { routeName: DashboardRoutes.Normal, component: () => null, path: '/' }, + uid: 'test-uid', + }); + }); + + await waitFor(() => { + expect(screen.queryByTestId('dashboard-scene-page')).not.toBeInTheDocument(); + }); + + act(() => { + appEvents.publish(new NIRefreshDashboardEvent()); + }); + + await waitFor(() => { + expect(mockRefreshTimeModel).toHaveBeenCalled(); + }); + }); + + it('should unsubscribe from NIRefreshDashboardEvent on unmount', async () => { + const unsubscribeSpy = jest.fn(); + const subscribeSpy = jest.spyOn(appEvents, 'subscribe').mockReturnValue({ + unsubscribe: unsubscribeSpy, + }); + + const { unmount } = setup({ + route: { routeName: DashboardRoutes.Normal, component: () => null, path: '/' }, + uid: 'test-uid', + }); + + await waitFor(() => { + expect(subscribeSpy).toHaveBeenCalled(); + }); + + act(() => { + unmount(); + }); + + expect(unsubscribeSpy).toHaveBeenCalled(); + + subscribeSpy.mockRestore(); + }); + + it('should subscribe regardless of feature toggle state', async () => { + config.featureToggles.dashboardSceneForViewers = true; + getDashboardScenePageStateManager().setDashboardCache('test-uid', dashMock); + + const subscribeSpy = jest.spyOn(appEvents, 'subscribe'); + + act(() => { + setup({ + route: { routeName: DashboardRoutes.Normal, component: () => null, path: '/' }, + uid: 'test-uid', + }); + }); + + await waitFor(() => { + expect(subscribeSpy).toHaveBeenCalledWith(NIRefreshDashboardEvent, expect.any(Function)); + }); + + act(() => { + appEvents.publish(new NIRefreshDashboardEvent()); + }); + + await waitFor(() => { + expect(mockRefreshTimeModel).toHaveBeenCalled(); + }); + + subscribeSpy.mockRestore(); + }); + }); }); From 4ad79c08e63501e3d3e453022f371429c0d9b5e8 Mon Sep 17 00:00:00 2001 From: Ahalya Radhakrishnan Date: Thu, 8 Jan 2026 19:48:40 +0530 Subject: [PATCH 4/7] remove changes made in time series panel Signed-off-by: Ahalya Radhakrishnan --- .../app/plugins/panel/timeseries/TimeSeriesPanel.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx index 73da67400dad6..e4124886ce266 100644 --- a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx +++ b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx @@ -1,14 +1,13 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import { PanelProps, DataFrameType, DashboardCursorSync } from '@grafana/data'; -import { PanelDataErrorView, getAppEvents } from '@grafana/runtime'; +import { PanelDataErrorView } from '@grafana/runtime'; import { TooltipDisplayMode, VizOrientation } from '@grafana/schema'; import { EventBusPlugin, KeyboardPlugin, TooltipPlugin2, usePanelContext } from '@grafana/ui'; import { TimeRange2, TooltipHoverMode } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2'; import { TimeSeries } from 'app/core/components/TimeSeries/TimeSeries'; import { config } from 'app/core/config'; -import { NIRefreshDashboardEvent } from 'app/types/events'; import { TimeSeriesTooltip } from './TimeSeriesTooltip'; import { Options } from './panelcfg.gen'; import { AnnotationsPlugin2 } from './plugins/AnnotationsPlugin2'; @@ -62,13 +61,6 @@ export const TimeSeriesPanel = ({ const [newAnnotationRange, setNewAnnotationRange] = useState(null); const cursorSync = sync?.() ?? DashboardCursorSync.Off; - // TEST: Publish NIRefreshDashboardEvent to verify it's being caught - // Remove this after testing - useEffect(() => { - console.log('🚀 TimeSeriesPanel publishing NIRefreshDashboardEvent (TEST)'); - getAppEvents().publish(new NIRefreshDashboardEvent()); - }, []); - if (!frames || suggestions) { return ( Date: Mon, 12 Jan 2026 23:41:58 +0530 Subject: [PATCH 5/7] updated test Signed-off-by: Ahalya Radhakrishnan --- .../containers/DashboardPageProxy.test.tsx | 56 ++++++------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/public/app/features/dashboard/containers/DashboardPageProxy.test.tsx b/public/app/features/dashboard/containers/DashboardPageProxy.test.tsx index a421e6ed3fea9..960fd8678a991 100644 --- a/public/app/features/dashboard/containers/DashboardPageProxy.test.tsx +++ b/public/app/features/dashboard/containers/DashboardPageProxy.test.tsx @@ -291,16 +291,14 @@ describe('DashboardPageProxy', () => { }); }); - describe('NIRefreshDashboardEvent subscription', () => { + describe('NIRefreshDashboardEvent', () => { beforeEach(() => { config.featureToggles.dashboardSceneForViewers = false; mockRefreshTimeModel.mockClear(); getDashboardScenePageStateManager().setDashboardCache('test-uid', dashMock); }); - it('should subscribe to NIRefreshDashboardEvent on mount', async () => { - const subscribeSpy = jest.spyOn(appEvents, 'subscribe'); - + it('should refresh the dashboard when NIRefreshDashboardEvent is emitted', async () => { act(() => { setup({ route: { routeName: DashboardRoutes.Normal, component: () => null, path: '/' }, @@ -309,13 +307,19 @@ describe('DashboardPageProxy', () => { }); await waitFor(() => { - expect(subscribeSpy).toHaveBeenCalledWith(NIRefreshDashboardEvent, expect.any(Function)); + expect(screen.queryByTestId('dashboard-scene-page')).not.toBeInTheDocument(); }); - subscribeSpy.mockRestore(); + act(() => { + appEvents.publish(new NIRefreshDashboardEvent()); + }); + + await waitFor(() => { + expect(mockRefreshTimeModel).toHaveBeenCalledTimes(1); + }); }); - it('should call refreshTimeModel when NIRefreshDashboardEvent is published', async () => { + it('should refresh the dashboard multiple times when event is emitted multiple times', async () => { act(() => { setup({ route: { routeName: DashboardRoutes.Normal, component: () => null, path: '/' }, @@ -329,43 +333,19 @@ describe('DashboardPageProxy', () => { act(() => { appEvents.publish(new NIRefreshDashboardEvent()); + appEvents.publish(new NIRefreshDashboardEvent()); + appEvents.publish(new NIRefreshDashboardEvent()); }); await waitFor(() => { - expect(mockRefreshTimeModel).toHaveBeenCalled(); - }); - }); - - it('should unsubscribe from NIRefreshDashboardEvent on unmount', async () => { - const unsubscribeSpy = jest.fn(); - const subscribeSpy = jest.spyOn(appEvents, 'subscribe').mockReturnValue({ - unsubscribe: unsubscribeSpy, - }); - - const { unmount } = setup({ - route: { routeName: DashboardRoutes.Normal, component: () => null, path: '/' }, - uid: 'test-uid', - }); - - await waitFor(() => { - expect(subscribeSpy).toHaveBeenCalled(); - }); - - act(() => { - unmount(); + expect(mockRefreshTimeModel).toHaveBeenCalledTimes(3); }); - - expect(unsubscribeSpy).toHaveBeenCalled(); - - subscribeSpy.mockRestore(); }); - it('should subscribe regardless of feature toggle state', async () => { + it('should refresh dashboard when using DashboardScenePage and event is emitted', async () => { config.featureToggles.dashboardSceneForViewers = true; getDashboardScenePageStateManager().setDashboardCache('test-uid', dashMock); - const subscribeSpy = jest.spyOn(appEvents, 'subscribe'); - act(() => { setup({ route: { routeName: DashboardRoutes.Normal, component: () => null, path: '/' }, @@ -374,7 +354,7 @@ describe('DashboardPageProxy', () => { }); await waitFor(() => { - expect(subscribeSpy).toHaveBeenCalledWith(NIRefreshDashboardEvent, expect.any(Function)); + expect(screen.queryByTestId('dashboard-scene-page')).toBeInTheDocument(); }); act(() => { @@ -382,10 +362,8 @@ describe('DashboardPageProxy', () => { }); await waitFor(() => { - expect(mockRefreshTimeModel).toHaveBeenCalled(); + expect(mockRefreshTimeModel).toHaveBeenCalledTimes(1); }); - - subscribeSpy.mockRestore(); }); }); }); From 6cacfa7ffe571ff9cd42b6a8b545f7325263be0c Mon Sep 17 00:00:00 2001 From: Ahalya Radhakrishnan Date: Mon, 12 Jan 2026 23:45:23 +0530 Subject: [PATCH 6/7] resolve nit comments Signed-off-by: Ahalya Radhakrishnan --- public/app/features/dashboard/containers/DashboardPageProxy.tsx | 2 +- public/app/types/events.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/containers/DashboardPageProxy.tsx b/public/app/features/dashboard/containers/DashboardPageProxy.tsx index 3fd4f7f840e85..b160f4c483cfd 100644 --- a/public/app/features/dashboard/containers/DashboardPageProxy.tsx +++ b/public/app/features/dashboard/containers/DashboardPageProxy.tsx @@ -5,9 +5,9 @@ import { useAsync } from 'react-use'; import { config } from '@grafana/runtime'; import { appEvents } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import DashboardScenePage from 'app/features/dashboard-scene/pages/DashboardScenePage'; import { getDashboardScenePageStateManager } from 'app/features/dashboard-scene/pages/DashboardScenePageStateManager'; -import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { DashboardRoutes } from 'app/types'; import { NIRefreshDashboardEvent } from 'app/types/events'; diff --git a/public/app/types/events.ts b/public/app/types/events.ts index 670a871b43842..374bdccda3474 100644 --- a/public/app/types/events.ts +++ b/public/app/types/events.ts @@ -196,7 +196,7 @@ export class DashboardSavedEvent extends BusEventBase { } export class NIRefreshDashboardEvent extends BusEventBase { - static type = "ni-refresh-dashboard"; + static type = 'ni-refresh-dashboard'; } export class AnnotationQueryStarted extends BusEventWithPayload { From 97b5d945bd9c5fb87e9226cb2f4fe8de68bcf2fd Mon Sep 17 00:00:00 2001 From: Ahalya Radhakrishnan Date: Tue, 13 Jan 2026 12:21:31 +0530 Subject: [PATCH 7/7] add code comment and organize tests Signed-off-by: Ahalya Radhakrishnan --- .../containers/DashboardPageProxy.test.tsx | 125 +++++++++++------- public/app/types/events.ts | 3 + 2 files changed, 81 insertions(+), 47 deletions(-) diff --git a/public/app/features/dashboard/containers/DashboardPageProxy.test.tsx b/public/app/features/dashboard/containers/DashboardPageProxy.test.tsx index 960fd8678a991..4fdf20a5d4549 100644 --- a/public/app/features/dashboard/containers/DashboardPageProxy.test.tsx +++ b/public/app/features/dashboard/containers/DashboardPageProxy.test.tsx @@ -293,76 +293,107 @@ describe('DashboardPageProxy', () => { describe('NIRefreshDashboardEvent', () => { beforeEach(() => { - config.featureToggles.dashboardSceneForViewers = false; mockRefreshTimeModel.mockClear(); getDashboardScenePageStateManager().setDashboardCache('test-uid', dashMock); }); - it('should refresh the dashboard when NIRefreshDashboardEvent is emitted', async () => { - act(() => { - setup({ - route: { routeName: DashboardRoutes.Normal, component: () => null, path: '/' }, - uid: 'test-uid', - }); + describe('when dashboard scenes is disabled', () => { + beforeEach(() => { + config.featureToggles.dashboardSceneForViewers = false; }); - await waitFor(() => { - expect(screen.queryByTestId('dashboard-scene-page')).not.toBeInTheDocument(); - }); + it('should refresh the dashboard when NIRefreshDashboardEvent is emitted', async () => { + act(() => { + setup({ + route: { routeName: DashboardRoutes.Normal, component: () => null, path: '/' }, + uid: 'test-uid', + }); + }); - act(() => { - appEvents.publish(new NIRefreshDashboardEvent()); - }); + await waitFor(() => { + expect(screen.queryByTestId('dashboard-scene-page')).not.toBeInTheDocument(); + }); - await waitFor(() => { - expect(mockRefreshTimeModel).toHaveBeenCalledTimes(1); - }); - }); + act(() => { + appEvents.publish(new NIRefreshDashboardEvent()); + }); - it('should refresh the dashboard multiple times when event is emitted multiple times', async () => { - act(() => { - setup({ - route: { routeName: DashboardRoutes.Normal, component: () => null, path: '/' }, - uid: 'test-uid', + await waitFor(() => { + expect(mockRefreshTimeModel).toHaveBeenCalledTimes(1); }); }); - await waitFor(() => { - expect(screen.queryByTestId('dashboard-scene-page')).not.toBeInTheDocument(); - }); + it('should refresh the dashboard multiple times when event is emitted multiple times', async () => { + act(() => { + setup({ + route: { routeName: DashboardRoutes.Normal, component: () => null, path: '/' }, + uid: 'test-uid', + }); + }); - act(() => { - appEvents.publish(new NIRefreshDashboardEvent()); - appEvents.publish(new NIRefreshDashboardEvent()); - appEvents.publish(new NIRefreshDashboardEvent()); - }); + await waitFor(() => { + expect(screen.queryByTestId('dashboard-scene-page')).not.toBeInTheDocument(); + }); - await waitFor(() => { - expect(mockRefreshTimeModel).toHaveBeenCalledTimes(3); + act(() => { + appEvents.publish(new NIRefreshDashboardEvent()); + appEvents.publish(new NIRefreshDashboardEvent()); + appEvents.publish(new NIRefreshDashboardEvent()); + }); + + await waitFor(() => { + expect(mockRefreshTimeModel).toHaveBeenCalledTimes(3); + }); }); }); - it('should refresh dashboard when using DashboardScenePage and event is emitted', async () => { - config.featureToggles.dashboardSceneForViewers = true; - getDashboardScenePageStateManager().setDashboardCache('test-uid', dashMock); + describe('when dashboard scenes is enabled', () => { + beforeEach(() => { + config.featureToggles.dashboardSceneForViewers = true; + }); - act(() => { - setup({ - route: { routeName: DashboardRoutes.Normal, component: () => null, path: '/' }, - uid: 'test-uid', + it('should refresh dashboard when NIRefreshDashboardEvent is emitted', async () => { + act(() => { + setup({ + route: { routeName: DashboardRoutes.Normal, component: () => null, path: '/' }, + uid: 'test-uid', + }); }); - }); - await waitFor(() => { - expect(screen.queryByTestId('dashboard-scene-page')).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.queryByTestId('dashboard-scene-page')).toBeInTheDocument(); + }); - act(() => { - appEvents.publish(new NIRefreshDashboardEvent()); + act(() => { + appEvents.publish(new NIRefreshDashboardEvent()); + }); + + await waitFor(() => { + expect(mockRefreshTimeModel).toHaveBeenCalledTimes(1); + }); }); - await waitFor(() => { - expect(mockRefreshTimeModel).toHaveBeenCalledTimes(1); + it('should refresh the dashboard multiple times when event is emitted multiple times', async () => { + act(() => { + setup({ + route: { routeName: DashboardRoutes.Normal, component: () => null, path: '/' }, + uid: 'test-uid', + }); + }); + + await waitFor(() => { + expect(screen.queryByTestId('dashboard-scene-page')).toBeInTheDocument(); + }); + + act(() => { + appEvents.publish(new NIRefreshDashboardEvent()); + appEvents.publish(new NIRefreshDashboardEvent()); + appEvents.publish(new NIRefreshDashboardEvent()); + }); + + await waitFor(() => { + expect(mockRefreshTimeModel).toHaveBeenCalledTimes(3); + }); }); }); }); diff --git a/public/app/types/events.ts b/public/app/types/events.ts index 374bdccda3474..8f269143a2a49 100644 --- a/public/app/types/events.ts +++ b/public/app/types/events.ts @@ -195,6 +195,9 @@ export class DashboardSavedEvent extends BusEventBase { static type = 'dashboard-saved'; } +/** + * Custom event that triggers a dashboard refresh. + */ export class NIRefreshDashboardEvent extends BusEventBase { static type = 'ni-refresh-dashboard'; }