Dashboard: Allow disabling dashboard grid lazy loading (#89280)

* Schema update

* Dashboard: Allow opting out from dashboard panels lazy loading

* Locale

* Lint fix

* Snaps fix
This commit is contained in:
Dominik Prokop 2024-07-03 16:00:45 +02:00 committed by GitHub
parent d1952bb681
commit f659bc1f40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 114 additions and 46 deletions

View File

@ -99,6 +99,9 @@ lineage: schemas: [{
// Snapshot options. They are present only if the dashboard is a snapshot.
snapshot?: #Snapshot @grafanamaturity(NeedsExpertReview)
// When set to true, the dashboard will load all panels in the dashboard when it's loaded.
preload?: bool
} @cuetsy(kind="interface") @grafana(TSVeneer="type")
///////////////////////////////////////

View File

@ -1062,6 +1062,10 @@ export interface Dashboard {
* List of dashboard panels
*/
panels?: Array<(Panel | RowPanel)>;
/**
* When set to true, the dashboard will load all panels in the dashboard when it's loaded.
*/
preload?: boolean;
/**
* Refresh rate of dashboard. Represented via interval string, e.g. "5s", "1m", "1h", "1d".
*/

View File

@ -753,6 +753,9 @@ type Spec struct {
// List of dashboard panels
Panels []any `json:"panels,omitempty"`
// When set to true, the dashboard will load all panels in the dashboard when it's loaded.
Preload *bool `json:"preload,omitempty"`
// Refresh rate of dashboard. Represented via interval string, e.g. "5s", "1m", "1h", "1d".
Refresh *string `json:"refresh,omitempty"`

View File

@ -322,40 +322,57 @@ export function getDashboardScenePageStateManager(): DashboardScenePageStateMana
}
function getErrorScene(msg: string) {
return createDashboardSceneFromDashboardModel(
new DashboardModel(
{
...defaultDashboard,
title: msg,
panels: [
const dto: DashboardDTO = {
dashboard: {
...defaultDashboard,
uid: 'error-dash',
title: msg,
annotations: {
list: [
{
fieldConfig: {
defaults: {},
overrides: [],
builtIn: 1,
datasource: {
type: 'grafana',
uid: '-- Grafana --',
},
gridPos: {
h: 6,
w: 12,
x: 7,
y: 0,
},
id: 1,
options: {
code: {
language: 'plaintext',
showLineNumbers: false,
showMiniMap: false,
},
content: `<br/><br/><center><h1>${msg}</h1></center>`,
mode: 'html',
},
title: '',
transparent: true,
type: 'text',
enable: false,
hide: true,
iconColor: 'rgba(0, 211, 255, 1)',
name: 'Annotations & Alerts',
type: 'dashboard',
},
],
},
{ canSave: false, canEdit: false }
)
);
panels: [
{
fieldConfig: {
defaults: {},
overrides: [],
},
gridPos: {
h: 6,
w: 12,
x: 7,
y: 0,
},
id: 1,
options: {
code: {
language: 'plaintext',
showLineNumbers: false,
showMiniMap: false,
},
content: `<br/><br/><center><h1>${msg}</h1></center>`,
mode: 'html',
},
title: '',
transparent: true,
type: 'text',
},
],
},
meta: { canSave: false, canEdit: false },
};
return createDashboardSceneFromDashboardModel(new DashboardModel(dto.dashboard, dto.meta), dto.dashboard);
}

View File

@ -28,7 +28,7 @@ import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { configureStore } from 'app/store/configureStore';
import { AccessControlAction } from 'app/types';
import { AccessControlAction, DashboardDataDTO } from 'app/types';
import { AlertQuery, PromRulesResponse } from 'app/types/unified-alerting-dto';
import { createDashboardSceneFromDashboardModel } from '../../serialization/transformSaveModelToScene';
@ -359,7 +359,7 @@ async function clickNewButton() {
}
function createModel(dashboard: DashboardModel) {
const scene = createDashboardSceneFromDashboardModel(dashboard);
const scene = createDashboardSceneFromDashboardModel(dashboard, {} as DashboardDataDTO);
const vizPanel = findVizPanelByKey(scene, getVizPanelKeyForPanelId(34))!;
const model = new PanelDataAlertingTab(VizPanelManager.createFor(vizPanel));
jest.spyOn(utils, 'getDashboardSceneFor').mockReturnValue(scene);

View File

@ -78,7 +78,7 @@ import { ScopesScene } from './Scopes/ScopesScene';
import { ViewPanelScene } from './ViewPanelScene';
import { setupKeyboardShortcuts } from './keyboardShortcuts';
export const PERSISTED_PROPS = ['title', 'description', 'tags', 'editable', 'graphTooltip', 'links', 'meta'];
export const PERSISTED_PROPS = ['title', 'description', 'tags', 'editable', 'graphTooltip', 'links', 'meta', 'preload'];
export interface DashboardSceneState extends SceneObjectState {
/** The title */
@ -91,6 +91,8 @@ export interface DashboardSceneState extends SceneObjectState {
links: DashboardLink[];
/** Is editable */
editable?: boolean;
/** Allows disabling grid lazy loading */
preload?: boolean;
/** A uid when saved */
uid?: string;
/** @deprecated */

View File

@ -278,6 +278,7 @@ exports[`transformSceneToSaveModel Given a scene with rows Should transform back
"type": "row",
},
],
"preload": false,
"refresh": "",
"schemaVersion": 39,
"tags": [
@ -545,6 +546,7 @@ exports[`transformSceneToSaveModel Given a simple scene with custom settings Sho
"type": "text",
},
],
"preload": false,
"refresh": "5m",
"schemaVersion": 39,
"tags": [
@ -902,6 +904,7 @@ exports[`transformSceneToSaveModel Given a simple scene with variables Should tr
"type": "text",
},
],
"preload": false,
"refresh": "",
"schemaVersion": 39,
"tags": [

View File

@ -111,7 +111,7 @@ describe('transformSaveModelToScene', () => {
};
const oldModel = new DashboardModel(dash);
const scene = createDashboardSceneFromDashboardModel(oldModel);
const scene = createDashboardSceneFromDashboardModel(oldModel, dash);
const dashboardControls = scene.state.controls!;
expect(scene.state.title).toBe('test');
@ -138,11 +138,13 @@ describe('transformSaveModelToScene', () => {
it('should apply cursor sync behavior', () => {
const dash = {
...defaultDashboard,
title: 'Test dashboard',
uid: 'test-uid',
graphTooltip: DashboardCursorSync.Crosshair,
};
const oldModel = new DashboardModel(dash);
const scene = createDashboardSceneFromDashboardModel(oldModel);
const scene = createDashboardSceneFromDashboardModel(oldModel, dash);
const cursorSync = scene.state.$behaviors?.find((b) => b instanceof behaviors.CursorSync);
expect(cursorSync).toBeInstanceOf(behaviors.CursorSync);
@ -150,8 +152,13 @@ describe('transformSaveModelToScene', () => {
});
it('should apply live now timer behavior', () => {
const oldModel = new DashboardModel(defaultDashboard);
const scene = createDashboardSceneFromDashboardModel(oldModel);
const dash = {
...defaultDashboard,
title: 'Test dashboard',
uid: 'test-uid',
};
const oldModel = new DashboardModel(dash);
const scene = createDashboardSceneFromDashboardModel(oldModel, dash);
const liveNowTimer = scene.state.$behaviors?.find((b) => b instanceof behaviors.LiveNowTimer);
expect(liveNowTimer).toBeInstanceOf(behaviors.LiveNowTimer);
@ -172,7 +179,7 @@ describe('transformSaveModelToScene', () => {
};
const oldModel = new DashboardModel(dash);
const scene = createDashboardSceneFromDashboardModel(oldModel);
const scene = createDashboardSceneFromDashboardModel(oldModel, dash);
expect(scene.state.$variables?.state.variables).toBeDefined();
});
});
@ -212,12 +219,14 @@ describe('transformSaveModelToScene', () => {
const dashboard = {
...defaultDashboard,
title: 'Test dashboard',
uid: 'test-uid',
panels: [row],
};
const oldModel = new DashboardModel(dashboard);
const scene = createDashboardSceneFromDashboardModel(oldModel);
const scene = createDashboardSceneFromDashboardModel(oldModel, dashboard);
const body = scene.state.body as SceneGridLayout;
expect(body.state.children).toHaveLength(1);
@ -304,12 +313,14 @@ describe('transformSaveModelToScene', () => {
const dashboard = {
...defaultDashboard,
title: 'Test dashboard',
uid: 'test-uid',
panels: [panelOutOfRow, libPanelOutOfRow, rowWithPanel, panelInRow, libPanelInRow, emptyRow],
};
const oldModel = new DashboardModel(dashboard);
const scene = createDashboardSceneFromDashboardModel(oldModel);
const scene = createDashboardSceneFromDashboardModel(oldModel, dashboard);
const body = scene.state.body as SceneGridLayout;
expect(body.state.children).toHaveLength(4);

View File

@ -30,7 +30,7 @@ import {
AdHocFiltersVariable,
} from '@grafana/scenes';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { DashboardDTO } from 'app/types';
import { DashboardDTO, DashboardDataDTO } from 'app/types';
import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
@ -74,7 +74,7 @@ export function transformSaveModelToScene(rsp: DashboardDTO): DashboardScene {
// Just to have migrations run
const oldModel = new DashboardModel(rsp.dashboard, rsp.meta);
const scene = createDashboardSceneFromDashboardModel(oldModel);
const scene = createDashboardSceneFromDashboardModel(oldModel, rsp.dashboard);
// TODO: refactor createDashboardSceneFromDashboardModel to work on Dashboard schema model
scene.setInitialSaveModel(rsp.dashboard);
@ -190,7 +190,7 @@ function createRowFromPanelModel(row: PanelModel, content: SceneGridItemLike[]):
});
}
export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) {
export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel, dto: DashboardDataDTO) {
let variables: SceneVariableSet | undefined;
let annotationLayers: SceneDataLayerProvider[] = [];
let alertStatesLayer: AlertStatesDataLayer | undefined;
@ -249,6 +249,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
const dashboardScene = new DashboardScene({
description: oldModel.description,
editable: oldModel.editable,
preload: dto.preload ?? false,
id: oldModel.id,
isDirty: false,
links: oldModel.links || [],
@ -258,7 +259,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
uid: oldModel.uid,
version: oldModel.version,
body: new SceneGridLayout({
isLazy: true,
isLazy: dto.preload ? false : true,
children: createSceneObjectsForPanels(oldModel.panels),
$behaviors: [trackIfEmpty],
}),

View File

@ -112,6 +112,7 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa
uid: state.uid,
id: state.id,
editable: state.editable,
preload: state.preload,
time: {
from: timeRange.from,
to: timeRange.to,

View File

@ -12,6 +12,7 @@ import {
Label,
RadioButtonGroup,
Stack,
Switch,
TagsInput,
TextArea,
} from '@grafana/ui';
@ -161,6 +162,10 @@ export class GeneralSettingsEditView
this.getCursorSync()?.setState({ sync: value });
};
public onPreloadChange = (preload: boolean) => {
this._dashboard.setState({ preload });
};
public onDeleteDashboard = () => {};
static Component = ({ model }: SceneComponentProps<GeneralSettingsEditView>) => {
@ -271,6 +276,20 @@ export class GeneralSettingsEditView
>
<RadioButtonGroup onChange={model.onTooltipChange} options={GRAPH_TOOLTIP_OPTIONS} value={graphTooltip} />
</Field>
<Field
label={t('dashboard-settings.general.panels-preload-label', 'Preload panels')}
description={t(
'dashboard-settings.general.panels-preload-description',
'When enabled all panels will start loading as soon as the dashboard has been loaded.'
)}
>
<Switch
id="preload-panels-dashboards-toggle"
value={dashboard.state.preload}
onChange={(e) => model.onPreloadChange(e.currentTarget.checked)}
/>
</Field>
</CollapsableSection>
<Box marginTop={3}>{meta.canDelete && <DeleteDashboardButton dashboard={dashboard} />}</Box>

View File

@ -83,7 +83,7 @@ export class SupportSnapshotService extends StateManagerBase<SupportSnapshotStat
if (!panel.isAngularPlugin()) {
try {
const oldModel = new DashboardModel(snapshot, { isEmbedded: true });
const dash = createDashboardSceneFromDashboardModel(oldModel);
const dash = createDashboardSceneFromDashboardModel(oldModel, snapshot);
scene = dash.state.body; // skip the wrappers
} catch (ex) {
console.log('Error creating scene:', ex);

View File

@ -446,6 +446,8 @@
"panel-options-graph-tooltip-description": "Controls tooltip and hover highlight behavior across different panels. Reload the dashboard for changes to take effect",
"panel-options-graph-tooltip-label": "Graph tooltip",
"panel-options-label": "Panel options",
"panels-preload-description": "When enabled all panels will start loading as soon as the dashboard has been loaded.",
"panels-preload-label": "Preload panels",
"tags-label": "Tags",
"title": "General",
"title-label": "Title"

View File

@ -446,6 +446,8 @@
"panel-options-graph-tooltip-description": "Cőʼnŧřőľş ŧőőľŧįp äʼnđ ĥővęř ĥįģĥľįģĥŧ þęĥävįőř äčřőşş đįƒƒęřęʼnŧ päʼnęľş. Ŗęľőäđ ŧĥę đäşĥþőäřđ ƒőř čĥäʼnģęş ŧő ŧäĸę ęƒƒęčŧ",
"panel-options-graph-tooltip-label": "Ğřäpĥ ŧőőľŧįp",
"panel-options-label": "Päʼnęľ őpŧįőʼnş",
"panels-preload-description": "Ŵĥęʼn ęʼnäþľęđ äľľ päʼnęľş ŵįľľ şŧäřŧ ľőäđįʼnģ äş şőőʼn äş ŧĥę đäşĥþőäřđ ĥäş þęęʼn ľőäđęđ.",
"panels-preload-label": "Přęľőäđ päʼnęľş",
"tags-label": "Ŧäģş",
"title": "Ğęʼnęřäľ",
"title-label": "Ŧįŧľę"