diff --git a/public/app/features/dashboard-scene/panel-edit/PanelOptions.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelOptions.test.tsx index 7d3ff9a7ade..e2ff676701f 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelOptions.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelOptions.test.tsx @@ -5,8 +5,10 @@ import { VizPanel } from '@grafana/scenes'; import { OptionFilter } from 'app/features/dashboard/components/PanelEditor/OptionsPaneOptions'; import { DashboardGridItem } from '../scene/DashboardGridItem'; +import { DashboardScene } from '../scene/DashboardScene'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { vizPanelToPanel } from '../serialization/transformSceneToSaveModel'; +import * as utils from '../utils/utils'; import { PanelOptions } from './PanelOptions'; import { VizPanelManager } from './VizPanelManager'; @@ -17,6 +19,9 @@ jest.mock('react-router-dom', () => ({ }), })); +// Needed when the panel is not part of an DashboardScene +jest.spyOn(utils, 'getDashboardSceneFor').mockReturnValue(new DashboardScene({})); + describe('PanelOptions', () => { it('gets library panel options when the editing a library panel', async () => { const panel = new VizPanel({ diff --git a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap index ec93f464003..a4a56bf480c 100644 --- a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap +++ b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap @@ -329,6 +329,7 @@ exports[`transformSceneToSaveModel Given a simple scene with custom settings Sho "type": "testdata", "uid": "PD8C576611E62080A", }, + "description": "This is a simple time series graph", "fieldConfig": { "defaults": { "color": { @@ -684,6 +685,7 @@ exports[`transformSceneToSaveModel Given a simple scene with variables Should tr "type": "testdata", "uid": "PD8C576611E62080A", }, + "description": "This is a simple time series graph", "fieldConfig": { "defaults": { "color": { diff --git a/public/app/features/dashboard-scene/serialization/testfiles/dashboard_to_load1.json b/public/app/features/dashboard-scene/serialization/testfiles/dashboard_to_load1.json index 2c858faae85..7226d9fe0e4 100644 --- a/public/app/features/dashboard-scene/serialization/testfiles/dashboard_to_load1.json +++ b/public/app/features/dashboard-scene/serialization/testfiles/dashboard_to_load1.json @@ -115,6 +115,7 @@ } ], "title": "Simple time series graph ", + "description": "This is a simple time series graph", "type": "timeseries" }, { diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts index d590c168b6f..28b460d41db 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts @@ -214,6 +214,7 @@ export function vizPanelToPanel( id: getPanelIdForVizPanel(vizPanel), type: vizPanel.state.pluginId, title: vizPanel.state.title, + description: vizPanel.state.description ?? undefined, gridPos, options: vizPanel.state.options, fieldConfig: (vizPanel.state.fieldConfig as FieldConfigSource) ?? { defaults: {}, overrides: [] }, diff --git a/public/app/features/dashboard/components/GenAI/GenAIPanelDescriptionButton.tsx b/public/app/features/dashboard/components/GenAI/GenAIPanelDescriptionButton.tsx index b76b213b874..e82194306fe 100644 --- a/public/app/features/dashboard/components/GenAI/GenAIPanelDescriptionButton.tsx +++ b/public/app/features/dashboard/components/GenAI/GenAIPanelDescriptionButton.tsx @@ -1,7 +1,8 @@ import React from 'react'; +import { Panel } from '@grafana/schema'; + import { getDashboardSrv } from '../../services/DashboardSrv'; -import { PanelModel } from '../../state'; import { GenAIButton } from './GenAIButton'; import { EventTrackingSrc } from './tracking'; @@ -9,7 +10,7 @@ import { Message, Role, getFilteredPanelString } from './utils'; interface GenAIPanelDescriptionButtonProps { onGenerate: (description: string) => void; - panel: PanelModel; + panel: Panel; } const PANEL_DESCRIPTION_CHAR_LIMIT = 200; @@ -24,11 +25,9 @@ const DESCRIPTION_GENERATION_STANDARD_PROMPT = `The description should be, at most, ${PANEL_DESCRIPTION_CHAR_LIMIT} characters.`; export const GenAIPanelDescriptionButton = ({ onGenerate, panel }: GenAIPanelDescriptionButtonProps) => { - const messages = React.useMemo(() => getMessages(panel), [panel]); - return ( getMessages(panel)} onGenerate={onGenerate} eventTrackingSrc={EventTrackingSrc.panelDescription} toggleTipTitle={'Improve your panel description'} @@ -36,7 +35,7 @@ export const GenAIPanelDescriptionButton = ({ onGenerate, panel }: GenAIPanelDes ); }; -function getMessages(panel: PanelModel): Message[] { +function getMessages(panel: Panel): Message[] { const dashboard = getDashboardSrv().getCurrent()!; const panelString = getFilteredPanelString(panel); diff --git a/public/app/features/dashboard/components/GenAI/GenAIPanelTitleButton.tsx b/public/app/features/dashboard/components/GenAI/GenAIPanelTitleButton.tsx index fe56af69ba4..298898450f3 100644 --- a/public/app/features/dashboard/components/GenAI/GenAIPanelTitleButton.tsx +++ b/public/app/features/dashboard/components/GenAI/GenAIPanelTitleButton.tsx @@ -1,7 +1,6 @@ import React from 'react'; -import { getDashboardSrv } from '../../services/DashboardSrv'; -import { PanelModel } from '../../state'; +import { Dashboard, Panel } from '@grafana/schema'; import { GenAIButton } from './GenAIButton'; import { EventTrackingSrc } from './tracking'; @@ -9,7 +8,8 @@ import { Message, Role, getFilteredPanelString } from './utils'; interface GenAIPanelTitleButtonProps { onGenerate: (title: string) => void; - panel: PanelModel; + panel: Panel; + dashboard: Dashboard; } const PANEL_TITLE_CHAR_LIMIT = 50; @@ -19,12 +19,10 @@ const TITLE_GENERATION_STANDARD_PROMPT = 'Your goal is to write short, descriptive, and concise panel title.' + `The title should be shorter than ${PANEL_TITLE_CHAR_LIMIT} characters.`; -export const GenAIPanelTitleButton = ({ onGenerate, panel }: GenAIPanelTitleButtonProps) => { - const messages = React.useMemo(() => getMessages(panel), [panel]); - +export const GenAIPanelTitleButton = ({ onGenerate, panel, dashboard }: GenAIPanelTitleButtonProps) => { return ( getMessages(panel, dashboard)} onGenerate={onGenerate} eventTrackingSrc={EventTrackingSrc.panelTitle} toggleTipTitle={'Improve your panel title'} @@ -32,8 +30,7 @@ export const GenAIPanelTitleButton = ({ onGenerate, panel }: GenAIPanelTitleButt ); }; -function getMessages(panel: PanelModel): Message[] { - const dashboard = getDashboardSrv().getCurrent()!; +function getMessages(panel: Panel, dashboard: Dashboard): Message[] { const panelString = getFilteredPanelString(panel); return [ diff --git a/public/app/features/dashboard/components/GenAI/utils.ts b/public/app/features/dashboard/components/GenAI/utils.ts index 46922361777..d195d289a0b 100644 --- a/public/app/features/dashboard/components/GenAI/utils.ts +++ b/public/app/features/dashboard/components/GenAI/utils.ts @@ -1,4 +1,7 @@ +import { pick } from 'lodash'; + import { llms } from '@grafana/experimental'; +import { Panel } from '@grafana/schema'; import { DashboardModel, PanelModel } from '../../state'; @@ -120,27 +123,16 @@ export function getDashboardPanelPrompt(dashboard: DashboardModel): string { return panelPrompt; } -export function getFilteredPanelString(panel: PanelModel): string { - const panelObj = panel.getSaveModel(); +export function getFilteredPanelString(panel: Panel): string { + const keysToKeep: Array = ['datasource', 'title', 'description', 'targets', 'type']; - const keysToKeep = new Set([ - 'id', - 'datasource', - 'title', - 'description', - 'targets', - 'thresholds', - 'type', - 'xaxis', - 'yaxes', - ]); + const filteredPanel: Partial = { + ...pick(panel, keysToKeep), + options: pick(panel.options, [ + // For text panels, the content property helps generate the panel metadata + 'content', + ]), + }; - const panelObjFiltered = Object.keys(panelObj).reduce((obj: { [key: string]: unknown }, key) => { - if (keysToKeep.has(key)) { - obj[key] = panelObj[key]; - } - return obj; - }, {}); - - return JSON.stringify(panelObjFiltered, null, 2); + return JSON.stringify(filteredPanel, null, 2); } diff --git a/public/app/features/dashboard/components/PanelEditor/getPanelFrameOptions.tsx b/public/app/features/dashboard/components/PanelEditor/getPanelFrameOptions.tsx index 3f9793daae3..f03f6884b8e 100644 --- a/public/app/features/dashboard/components/PanelEditor/getPanelFrameOptions.tsx +++ b/public/app/features/dashboard/components/PanelEditor/getPanelFrameOptions.tsx @@ -7,7 +7,12 @@ import { VizPanel } from '@grafana/scenes'; import { DataLinksInlineEditor, Input, RadioButtonGroup, Select, Switch, TextArea } from '@grafana/ui'; import { VizPanelManager, VizPanelManagerState } from 'app/features/dashboard-scene/panel-edit/VizPanelManager'; import { VizPanelLinks } from 'app/features/dashboard-scene/scene/PanelLinks'; +import { + transformSceneToSaveModel, + vizPanelToPanel, +} from 'app/features/dashboard-scene/serialization/transformSceneToSaveModel'; import { dashboardSceneGraph } from 'app/features/dashboard-scene/utils/dashboardSceneGraph'; +import { getDashboardSceneFor } from 'app/features/dashboard-scene/utils/utils'; import { getPanelLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv'; import { GenAIPanelDescriptionButton } from '../GenAI/GenAIPanelDescriptionButton'; @@ -19,7 +24,7 @@ import { OptionsPaneItemDescriptor } from './OptionsPaneItemDescriptor'; import { OptionPaneRenderProps } from './types'; export function getPanelFrameCategory(props: OptionPaneRenderProps): OptionsPaneCategoryDescriptor { - const { panel, onPanelConfigChange } = props; + const { dashboard, panel, onPanelConfigChange } = props; const descriptor = new OptionsPaneCategoryDescriptor({ title: 'Panel options', id: 'Panel options', @@ -58,7 +63,13 @@ export function getPanelFrameCategory(props: OptionPaneRenderProps): OptionsPane /> ); }, - addon: config.featureToggles.dashgpt && , + addon: config.featureToggles.dashgpt && ( + + ), }) ) .addItem( @@ -77,7 +88,7 @@ export function getPanelFrameCategory(props: OptionPaneRenderProps): OptionsPane ); }, addon: config.featureToggles.dashgpt && ( - + ), }) ) @@ -194,6 +205,7 @@ export function getPanelFrameCategory2( const panelLinksObject = dashboardSceneGraph.getPanelLinks(panel); const links = panelLinksObject?.state.rawLinks ?? []; + const dashboard = getDashboardSceneFor(panel); return descriptor .addItem( @@ -202,34 +214,44 @@ export function getPanelFrameCategory2( value: panel.state.title, popularRank: 1, render: function renderTitle() { + const { title } = panel.useState(); return ( panel.setState({ title: e.currentTarget.value })} + value={title} + onChange={(e) => panel.setState({ title: e.currentTarget.value })} /> ); }, - // addon: config.featureToggles.dashgpt && , + addon: config.featureToggles.dashgpt && ( + panel.setState({ title })} + panel={vizPanelToPanel(panel)} + dashboard={transformSceneToSaveModel(dashboard)} + /> + ), }) ) .addItem( new OptionsPaneItemDescriptor({ title: 'Description', - description: panel.state.description, value: panel.state.description, render: function renderDescription() { + const { description } = panel.useState(); return (