Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions public/app/core/reducers/fn-slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ export interface FnPanelOptionsUpdate {
readonly revision: number;
}

export interface FnPanelQueryPreviewUpdate {
readonly panelId: number;
readonly rawSql: string;
readonly revision: number;
}

export interface FnState {
uid: string;
slug: string;
Expand Down Expand Up @@ -38,6 +44,7 @@ export interface FnState {
eventListener: (<T>(event: { type: string; data: T }) => void) | null;
};
panelOptionsUpdate?: FnPanelOptionsUpdate;
panelQueryPreviewUpdate?: FnPanelQueryPreviewUpdate;
/**
* Monotonic host-owned revision that requests a one-shot refresh of this
* dashboard without remounting the microfrontend.
Expand Down Expand Up @@ -65,6 +72,7 @@ export type FnPropMappedFromState = Extract<
| 'enablePanelDelete'
| 'enablePanelLayoutEdit'
| 'panelOptionsUpdate'
| 'panelQueryPreviewUpdate'
| 'refreshRevision'
| 'portalContainerID'
>;
Expand All @@ -85,6 +93,7 @@ export const fnStateProps: FnStateProp[] = [
'hiddenVariables',
'pageTitle',
'panelOptionsUpdate',
'panelQueryPreviewUpdate',
'refreshRevision',
'portalContainerID',
'queryParams',
Expand Down Expand Up @@ -115,6 +124,7 @@ export const INITIAL_FN_STATE: FnState = {
eventListener: null,
},
panelOptionsUpdate: undefined,
panelQueryPreviewUpdate: undefined,
refreshRevision: 0,
portalContainerID: 'grafana-portal',
} as const;
Expand Down
85 changes: 85 additions & 0 deletions public/app/features/dashboard/containers/DashboardPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,88 @@ describe('applyFnPanelOptionsPreview', () => {
expect(panel.updateOptions).not.toHaveBeenCalled();
});
});

describe('applyFnPanelQueryPreview', () => {
it('replaces only the first query and refreshes the live panel', async () => {
const { applyFnPanelQueryPreview } = await import('./DashboardPageFnPanelOptions');
const panel = getPanel('table');
panel.targets = [
{ refId: 'A', rawSql: 'SELECT old' },
{ refId: 'B', rawSql: 'SELECT unchanged' },
];
panel.refresh = jest.fn();

applyFnPanelQueryPreview(panel, {
panelId: panel.id,
rawSql: 'SELECT preview',
revision: 2,
});

expect(panel.targets).toEqual([
{ refId: 'A', rawSql: 'SELECT preview' },
{ refId: 'B', rawSql: 'SELECT unchanged' },
]);
expect(panel.refresh).toHaveBeenCalledTimes(1);
});

it('does not replace a non-SQL first query target', async () => {
const { applyFnPanelQueryPreview } = await import('./DashboardPageFnPanelOptions');
const panel = getPanel('table');
panel.targets = [{ refId: 'A' }, { refId: 'B', rawSql: 'SELECT unchanged' }];
panel.refresh = jest.fn();

applyFnPanelQueryPreview(panel, {
panelId: panel.id,
rawSql: 'SELECT preview',
revision: 1,
});

expect(panel.targets).toEqual([{ refId: 'A' }, { refId: 'B', rawSql: 'SELECT unchanged' }]);
expect(panel.refresh).not.toHaveBeenCalled();
});

it('does nothing when a panel cannot execute a query preview', async () => {
const { applyFnPanelQueryPreview } = await import('./DashboardPageFnPanelOptions');
const panel = getPanel('table');

expect(() =>
applyFnPanelQueryPreview(panel, {
panelId: panel.id,
rawSql: 'SELECT preview',
revision: 1,
})
).not.toThrow();
expect(panel.render).not.toHaveBeenCalled();
});
});

describe('applyFnPanelQueryPreview editor mode', () => {
it('does not replace a builder-mode SQL target that retains rawSql', async () => {
const { applyFnPanelQueryPreview } = await import('./DashboardPageFnPanelOptions');
const panel = getPanel('table');
panel.targets = [{ refId: 'A', editorMode: 'builder', rawSql: 'SELECT built' }];
panel.refresh = jest.fn();

applyFnPanelQueryPreview(panel, { panelId: panel.id, rawSql: 'SELECT preview', revision: 1 });

expect(panel.targets).toEqual([{ refId: 'A', editorMode: 'builder', rawSql: 'SELECT built' }]);
expect(panel.refresh).not.toHaveBeenCalled();
});
});

describe('resolveFnPanelQueryPreview', () => {
it('replays a retained update only on the dashboard it was issued against', async () => {
const { resolveFnPanelQueryPreview } = await import('./DashboardPageFnPanelOptions');
const update = { panelId: 1, rawSql: 'SELECT preview', revision: 1 };

const first = resolveFnPanelQueryPreview(undefined, update, 'a', true);
expect(first).toEqual({ apply: true, owner: { panelId: 1, revision: 1, uid: 'a' } });

expect(resolveFnPanelQueryPreview(first.owner, update, 'a', true).apply).toBe(true);
expect(resolveFnPanelQueryPreview(first.owner, update, 'a', false).apply).toBe(false);
expect(resolveFnPanelQueryPreview(first.owner, update, 'b', true).apply).toBe(false);

const next = resolveFnPanelQueryPreview(first.owner, { ...update, revision: 2 }, 'b', false);
expect(next).toEqual({ apply: true, owner: { panelId: 1, revision: 2, uid: 'b' } });
});
});
45 changes: 42 additions & 3 deletions public/app/features/dashboard/containers/DashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { GrafanaContext, GrafanaContextType } from 'app/core/context/GrafanaCont
import { createErrorNotification } from 'app/core/copy/appNotification';
import { getKioskMode } from 'app/core/navigation/kiosk';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { FnGlobalState, FnPanelOptionsUpdate } from 'app/core/reducers/fn-slice';
import { FnGlobalState, FnPanelOptionsUpdate, FnPanelQueryPreviewUpdate } from 'app/core/reducers/fn-slice';
import { getNavModel } from 'app/core/selectors/navModel';
import { PanelModel } from 'app/features/dashboard/state';
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
Expand Down Expand Up @@ -45,7 +45,12 @@ import { cleanUpDashboardAndVariables } from '../state/actions';
import { initDashboard } from '../state/initDashboard';
import { calculateNewPanelGridPos } from '../utils/panel';

import { applyFnPanelOptionsPreview } from './DashboardPageFnPanelOptions';
import {
applyFnPanelOptionsPreview,
applyFnPanelQueryPreview,
type FnPanelQueryPreviewOwner,
resolveFnPanelQueryPreview,
} from './DashboardPageFnPanelOptions';

export { applyFnPanelOptionsPreview } from './DashboardPageFnPanelOptions';

Expand Down Expand Up @@ -79,7 +84,12 @@ export type MapStateToDashboardPageProps = MapStateToProps<
navIndex: StoreState['navIndex'];
} & Pick<
FnGlobalState,
'FNDashboard' | 'controlsContainer' | 'dashboardAccessMode' | 'enablePanelLayoutEdit' | 'panelOptionsUpdate'
| 'FNDashboard'
| 'controlsContainer'
| 'dashboardAccessMode'
| 'enablePanelLayoutEdit'
| 'panelOptionsUpdate'
| 'panelQueryPreviewUpdate'
> & {
dashboardEventListener: FnGlobalState['metadata']['eventListener'];
},
Expand Down Expand Up @@ -107,6 +117,7 @@ export const mapStateToProps: MapStateToDashboardPageProps = (state) => ({
dashboardAccessMode: state.fnGlobalState.dashboardAccessMode,
enablePanelLayoutEdit: state.fnGlobalState.enablePanelLayoutEdit,
panelOptionsUpdate: state.fnGlobalState.panelOptionsUpdate,
panelQueryPreviewUpdate: state.fnGlobalState.panelQueryPreviewUpdate,
dashboardEventListener: state.fnGlobalState.metadata?.eventListener ?? null,
});

Expand Down Expand Up @@ -225,6 +236,20 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
this.applyFnPanelOptionsUpdate(this.props.panelOptionsUpdate);
}

if (FNDashboard && this.props.panelQueryPreviewUpdate) {
const update = this.props.panelQueryPreviewUpdate;
const { apply, owner } = resolveFnPanelQueryPreview(
this.fnPanelQueryPreviewOwner,
update,
dashboard.uid,
prevProps.dashboard !== dashboard
);
this.fnPanelQueryPreviewOwner = owner;
if (apply) {
this.applyFnPanelQueryPreviewUpdate(update);
}
}

if (!FNDashboard) {
const routeReloadCounter = (this.props.history.location?.state as any)?.routeReloadCounter;

Expand Down Expand Up @@ -286,6 +311,20 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
}
}

private fnPanelQueryPreviewOwner?: FnPanelQueryPreviewOwner;

applyFnPanelQueryPreviewUpdate(update: FnPanelQueryPreviewUpdate) {
const panel = this.props.dashboard?.getPanelById(update.panelId);
if (!panel) {
FnLoggerService.warn('Unable to apply FN panel query preview because the panel was not found', {
panelId: update.panelId,
});
return;
}

applyFnPanelQueryPreview(panel, update);
}

applyFnPanelOptionsUpdate(update: FnPanelOptionsUpdate) {
const panel = this.props.dashboard?.getPanelById(update.panelId);
if (!panel) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import type { FieldConfigSource } from '@grafana/data';
import type { FnPanelOptionsUpdate } from 'app/core/reducers/fn-slice';
import type { DataQuery, FieldConfigSource } from '@grafana/data';
import type { FnPanelOptionsUpdate, FnPanelQueryPreviewUpdate } from 'app/core/reducers/fn-slice';

export interface FnPanelQueryPreviewTarget extends DataQuery {
readonly editorMode?: string;
readonly rawSql?: string;
}

export interface FnPanelOptionsPreviewTarget {
description?: string;
fieldConfig?: FieldConfigSource;
id: number;
options?: Record<string, unknown>;
refresh?: () => void;
targets?: FnPanelQueryPreviewTarget[];
render: () => void;
title: string;
type: string;
Expand Down Expand Up @@ -339,3 +346,40 @@ export function applyFnPanelOptionsPreview(panel: FnPanelOptionsPreviewTarget, u
panel.render();
}
}

export function applyFnPanelQueryPreview(panel: FnPanelOptionsPreviewTarget, update: FnPanelQueryPreviewUpdate): void {
const target = panel.targets?.[0];
// A builder-mode SQL target keeps a stale `rawSql` string; only a Raw SQL
// target executes it, so only that mode may take the preview.
if (!panel.refresh || !panel.targets || typeof target?.rawSql !== 'string' || target.editorMode === 'builder') {
return;
}

panel.targets = panel.targets.map((target, index) => (index === 0 ? { ...target, rawSql: update.rawSql } : target));
panel.refresh();
}

export interface FnPanelQueryPreviewOwner {
readonly panelId: number;
readonly revision: number;
readonly uid: string;
}

/**
* Decide whether a panel query preview should be applied to `dashboardUid`.
*
* The first dashboard to receive an update owns it. A retained update is
* replayed after a reload only on its owner, so another dashboard with the
* same panel id is left untouched.
*/
export function resolveFnPanelQueryPreview(
owner: FnPanelQueryPreviewOwner | undefined,
update: FnPanelQueryPreviewUpdate,
dashboardUid: string,
dashboardChanged: boolean
): { apply: boolean; owner: FnPanelQueryPreviewOwner } {
if (owner?.panelId !== update.panelId || owner.revision !== update.revision) {
return { apply: true, owner: { panelId: update.panelId, revision: update.revision, uid: dashboardUid } };
}
return { apply: dashboardChanged && owner.uid === dashboardUid, owner };
}
10 changes: 5 additions & 5 deletions public/microfrontends/fn_dashboard/index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!doctype html><html lang="en"><head><title>CodeRabbit Micro-frontend</title><base href="/"/></head><body class="theme-light app-grafana"><div id="grafanaRoot"></div><script nonce="">window.fnData = {
themePaths: {
light: '../../../public/build/grafana.light.7f28e65680a5b6545827.css',
dark: '../../../public/build/grafana.dark.62a51288393eb54cd0f0.css',
}
};</script><script nonce="" src="../../../public/build/runtime.ee93c6753bd54c8fab9d.js" integrity="../../../sha384-nug4PW/w5f0jMrhkS0B1fQCddCk28yG96OfqDsUAr7r2iXbXt5kukplGZFd/hJQ2" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/6029.5c0893d1623856e7b325.js" integrity="../../../sha384-LCMBgpyynxT2jXlhS9LdLRKvKFwONDIYBW6yP4ZgNSZo7VxpgMGsUdspsX7oCvt5" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/5234.28fd59ea0e9666a0826e.js" integrity="../../../sha384-ad5xEmu01AuQ0ldC/JRHefbRBgrPrK0uSsKUd3wduHEcxfqIS7itIqlYZHeMmXKC" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/3510.a92110136b13cf93709f.js" integrity="../../../sha384-oCv1G3xe+4DsppDmUP06Hfk2u9E8jD/Nnz4ocbEsY7iBkgbV0hEofaXuLgBVKwhl" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/2410.606a87b065106e81ad31.js" integrity="../../../sha384-BFVRm68sCi1AfvPOncSziFN0wyR9pFWDILKADQNXW3osulVTaKUFvv6PVohbLlgE" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/9569.e030b8dea7b64b0c932a.js" integrity="../../../sha384-YcJ32x3C33s7kqMun94azN5FEkZXiyR4HF2rHgrCqWfUlF5fvk1D1ZgyCM0OmaMw" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/6263.f793d63a6599004c60fc.js" integrity="../../../sha384-TJDhxx+XskW0OkCdpyqQJRIWoy8tpfIAc5jXUb5x3sizBMkX+oYaafGHhl24wrrl" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/6947.1545d29aa28536898413.js" integrity="../../../sha384-Oz2nA0Zl/KJ/cZDxIPrWCEjyyfNz9YuCZh1XkF2MCfxhTwiuHZFPrJehm03BOQ6Q" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/2494.c195c3956b16e91f8287.js" integrity="../../../sha384-mgedj9ojo9iIioeZmJ38gM5aAoxghIpTgDlRttvE0B7kr17YpW8zgOcoskgpjakb" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/530.1c5ae442b33495c705a5.js" integrity="../../../sha384-Cv4HLe8qompnosQrBeW5214V6jyT5fWyYce0wl/H6F0e0VdI+icvpGcLUrnCqnHs" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/7223.d95df3befd30f60820cd.js" integrity="../../../sha384-9RmfNojs5KdJLCZyMmZLnUpOekJqfChcL6vfX2Ah6FMwTd84xk6TAq97giwsYkNY" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/280.45658a454830320f1b8c.js" integrity="../../../sha384-sKOuRlFNRQ3DlQeuR+QAH4IrOuXntWqlTwLRBGU8Eyoi9QXLLEXKlY6GWdKGxKgy" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/fn_dashboard.38fb2ecd3a848fa9e0c4.js" integrity="../../../sha384-YrQ8RjqEvUMl5McKb5X+P1eCrouScKoCCCpPV2qmoo+muTEpktF2A2cads0n/Gkl" crossorigin="anonymous"></script></body></html>
themePaths: {
light: '../../../public/build/grafana.light.626001a02b5b33e83edf.css',
dark: '../../../public/build/grafana.dark.dafda7e3786ebd2d8f31.css',
},
};</script><script nonce="" src="../../../public/build/runtime.c0bfcc98adb69418760b.js" integrity="sha384-2xNVFAzR915+P8oZ+LtRuTM0lYTxnanGL/BE7glSj/JXJ6LliY4s7d5qD2FOezDd" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/6029.5539a1a4669937facf91.js" integrity="sha384-yIX6VWoOdLvPWKUaFT8CgeSMr2zYIQiuPjrR42xbSAfWith1oosiLqRSf6O2Scgw" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/5234.cf8bec9afc8504ff211d.js" integrity="sha384-cthJ1YlKWf8dLVHNI5cwauSannx43LigdvlmPwdAnKravfPmjtB0ocM+67s9TCWO" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/3510.e3eb2115d68ede46c696.js" integrity="sha384-dWGVW9RSfzuAUecE2c683TetpeR+WCyhNUQBznWQYKpfb31Yzc1gP0GoOWySzXT6" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/2410.df62624199d14d2c9da4.js" integrity="sha384-wfNH4FNQzRlFOJhG9xHcQAwF71WV/Ds3MH/wECm+hJJCvnV/N4GQ1ScYxXGdB8hH" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/9569.9ee6c809e7b08a079b27.js" integrity="sha384-Avq0OXHZbZ8nSvnE9JmvR6Ssz7GXpKs8jEX4Sh6EHQMuXGG7eTUeNvobBlCGj6m7" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/6263.cbcb400e68249a588a26.js" integrity="sha384-bM74FynH+ttGo5V3vtxmtD1v0ZPqSQLJjSP/N5b1aOjkWLXRYffIiaCxQjsr5iaZ" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/6947.7d5397e8d8dad8a0cb8d.js" integrity="sha384-WSKbfrMO0Z5+uhb6kQ2QeVJv39Od9Q3VllZpim7mmzDYhSnxBCRVybVTTHbWRfht" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/2494.44df157e8243ad6c4f47.js" integrity="sha384-t8TKZgSnYDmT+SEcrOQyqjLsBfuCOQBjIKC57M8JF/rZ/7oDLQPQuc4Tz+gSElHt" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/530.4695602d8339e22a881d.js" integrity="sha384-GVGYuLU85dFhih7voyGGcxr5rRE671goDeJuEi7lNEMwK2ODX7lmGRZ2I9hN9Ygs" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/7223.3a3649beecb5dc180543.js" integrity="sha384-791+9QUaCZXM2XQkcYZqHvX+Wp5UoXlo9PrrzP3U91CmwDHrgjmTOM6950J06ul/" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/280.2d2d293d3a2aa3819a70.js" integrity="sha384-6Z4ru2KoqgjiN7FWvo+TTD8/IkyiPHn5aWQ7zrPeZMEA487WIWuchG4BsDNaqx01" crossorigin="anonymous"></script><script nonce="" src="../../../public/build/fn_dashboard.3fb4161b8f5179d4064e.js" integrity="sha384-HK5GfQ6GdNuVPk8j2f8iGqdJ63R0ndGQ7mncbR4Zi79GfBvABYLuBZFRCfj80h3B" crossorigin="anonymous"></script></body></html>
18 changes: 9 additions & 9 deletions public/views/index-microfrontend-template.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@

<body class="theme-light app-grafana">
<div id="grafanaRoot"></div>
<script nonce="">
window.fnData = {
themePaths: {
light: '../../../public/build/<%= htmlWebpackPlugin.files.cssChunks.light %>',
dark: '../../../public/build/<%= htmlWebpackPlugin.files.cssChunks.dark %>',
}
};
</script>
<script nonce="">
window.fnData = {
themePaths: {
light: '../../../public/build/<%= htmlWebpackPlugin.files.cssChunks.light %>',
dark: '../../../public/build/<%= htmlWebpackPlugin.files.cssChunks.dark %>',
},
};
</script>

<% for (index in htmlWebpackPlugin.files.js) { %> <% if (htmlWebpackPlugin.files.jsIntegrity) { %>
<script
nonce=""
src="../../../<%= htmlWebpackPlugin.files.js[index] %>"
type="text/javascript"
integrity="../../../<%= htmlWebpackPlugin.files.jsIntegrity[index] %>"
integrity="<%= htmlWebpackPlugin.files.jsIntegrity[index] %>"
crossorigin="<%= webpackConfig.output.crossOriginLoading %>"
></script>
<% } else { %>
Expand Down
Loading