mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DashGPT: Disable GenAI title and description buttons for empty dashboards (#90341)
* Disable genai title and description buttons when dashboard doesn't have at least one panel with a title or description * Fix test * Additional tooltip tests * address pr feedback * Fix test: Use const for panel title --------- Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
This commit is contained in:
parent
9bc68562d4
commit
e0416cc0f8
@ -133,6 +133,23 @@ describe('GenAIButton', () => {
|
||||
|
||||
await waitFor(() => expect(onClick).toHaveBeenCalledTimes(1));
|
||||
});
|
||||
|
||||
it('should display the tooltip if provided', async () => {
|
||||
const { getByRole, getByTestId } = setup({
|
||||
tooltip: 'This is a tooltip',
|
||||
onGenerate,
|
||||
messages: [],
|
||||
eventTrackingSrc,
|
||||
});
|
||||
|
||||
// Wait for the check to be completed
|
||||
const button = getByRole('button');
|
||||
await userEvent.hover(button);
|
||||
|
||||
const tooltip = await waitFor(() => getByTestId(selectors.components.Tooltip.container));
|
||||
expect(tooltip).toBeVisible();
|
||||
expect(tooltip).toHaveTextContent('This is a tooltip');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when it is generating data', () => {
|
||||
@ -288,7 +305,30 @@ describe('GenAIButton', () => {
|
||||
await userEvent.hover(tooltip);
|
||||
expect(tooltip).toBeVisible();
|
||||
expect(tooltip).toHaveTextContent(
|
||||
'Failed to generate content using OpenAI. Please try again or if the problem persist, contact your organization admin.'
|
||||
'Failed to generate content using OpenAI. Please try again or if the problem persists, contact your organization admin.'
|
||||
);
|
||||
});
|
||||
|
||||
it('error message should overwrite the tooltip content passed in tooltip prop', async () => {
|
||||
const { getByRole, getByTestId } = setup({
|
||||
tooltip: 'This is a tooltip',
|
||||
onGenerate,
|
||||
messages: [],
|
||||
eventTrackingSrc,
|
||||
});
|
||||
|
||||
// Wait for the check to be completed
|
||||
const button = getByRole('button');
|
||||
await userEvent.hover(button);
|
||||
|
||||
const tooltip = await waitFor(() => getByTestId(selectors.components.Tooltip.container));
|
||||
expect(tooltip).toBeVisible();
|
||||
|
||||
// The tooltip keeps interactive to be able to click the link
|
||||
await userEvent.hover(tooltip);
|
||||
expect(tooltip).toBeVisible();
|
||||
expect(tooltip).toHaveTextContent(
|
||||
'Failed to generate content using OpenAI. Please try again or if the problem persists, contact your organization admin.'
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -28,6 +28,13 @@ export interface GenAIButtonProps {
|
||||
eventTrackingSrc: EventTrackingSrc;
|
||||
// Whether the button should be disabled
|
||||
disabled?: boolean;
|
||||
/*
|
||||
Tooltip to show when hovering over the button
|
||||
Tooltip will be shown only before the improvement stage.
|
||||
i.e once the button title changes to "Improve", the tooltip will not be shown because
|
||||
toggletip will be enabled.
|
||||
*/
|
||||
tooltip?: string;
|
||||
}
|
||||
export const STOP_GENERATION_TEXT = 'Stop generating';
|
||||
|
||||
@ -41,6 +48,7 @@ export const GenAIButton = ({
|
||||
temperature = 1,
|
||||
eventTrackingSrc,
|
||||
disabled,
|
||||
tooltip,
|
||||
}: GenAIButtonProps) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
@ -55,6 +63,11 @@ export const GenAIButton = ({
|
||||
const isButtonDisabled = disabled || (value && !value.enabled && !error);
|
||||
const reportInteraction = (item: AutoGenerateItem) => reportAutoGenerateInteraction(eventTrackingSrc, item);
|
||||
|
||||
const showTooltip = error || tooltip ? undefined : false;
|
||||
const tooltipContent = error
|
||||
? 'Failed to generate content using OpenAI. Please try again or if the problem persists, contact your organization admin.'
|
||||
: tooltip || '';
|
||||
|
||||
const onClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (streamStatus === StreamStatus.GENERATING) {
|
||||
setStopGeneration(true);
|
||||
@ -192,13 +205,7 @@ export const GenAIButton = ({
|
||||
<div className={styles.wrapper}>
|
||||
{isGenerating && <Spinner size="sm" className={styles.spinner} />}
|
||||
{isFirstHistoryEntry ? (
|
||||
<Tooltip
|
||||
show={error ? undefined : false}
|
||||
interactive
|
||||
content={
|
||||
'Failed to generate content using OpenAI. Please try again or if the problem persist, contact your organization admin.'
|
||||
}
|
||||
>
|
||||
<Tooltip show={showTooltip} interactive content={tooltipContent}>
|
||||
{button}
|
||||
</Tooltip>
|
||||
) : (
|
||||
|
@ -1,8 +1,15 @@
|
||||
import { getDashboardSrv } from '../../services/DashboardSrv';
|
||||
import { DashboardModel } from '../../state';
|
||||
|
||||
import { GenAIButton } from './GenAIButton';
|
||||
import { EventTrackingSrc } from './tracking';
|
||||
import { getDashboardPanelPrompt, Message, Role } from './utils';
|
||||
import {
|
||||
DASHBOARD_NEED_PANEL_TITLES_AND_DESCRIPTIONS_MESSAGE,
|
||||
getDashboardPanelPrompt,
|
||||
getPanelStrings,
|
||||
Message,
|
||||
Role,
|
||||
} from './utils';
|
||||
|
||||
interface GenAIDashDescriptionButtonProps {
|
||||
onGenerate: (description: string) => void;
|
||||
@ -22,27 +29,29 @@ const DESCRIPTION_GENERATION_STANDARD_PROMPT =
|
||||
'Respond with only the description of the dashboard.';
|
||||
|
||||
export const GenAIDashDescriptionButton = ({ onGenerate }: GenAIDashDescriptionButtonProps) => {
|
||||
const dashboard = getDashboardSrv().getCurrent()!;
|
||||
const panelStrings = getPanelStrings(dashboard);
|
||||
|
||||
return (
|
||||
<GenAIButton
|
||||
messages={getMessages}
|
||||
messages={getMessages(dashboard)}
|
||||
onGenerate={onGenerate}
|
||||
eventTrackingSrc={EventTrackingSrc.dashboardDescription}
|
||||
toggleTipTitle={'Improve your dashboard description'}
|
||||
disabled={panelStrings.length === 0}
|
||||
tooltip={panelStrings.length === 0 ? DASHBOARD_NEED_PANEL_TITLES_AND_DESCRIPTIONS_MESSAGE : undefined}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
function getMessages(): Message[] {
|
||||
const dashboard = getDashboardSrv().getCurrent()!;
|
||||
const panelPrompt = getDashboardPanelPrompt(dashboard);
|
||||
|
||||
function getMessages(dashboard: DashboardModel): Message[] {
|
||||
return [
|
||||
{
|
||||
content: DESCRIPTION_GENERATION_STANDARD_PROMPT,
|
||||
role: Role.system,
|
||||
},
|
||||
{
|
||||
content: `The title of the dashboard is "${dashboard.title}"\n` + `${panelPrompt}`,
|
||||
content: `The title of the dashboard is "${dashboard.title}"\n` + `${getDashboardPanelPrompt(dashboard)}`,
|
||||
role: Role.user,
|
||||
},
|
||||
];
|
||||
|
@ -1,8 +1,15 @@
|
||||
import { getDashboardSrv } from '../../services/DashboardSrv';
|
||||
import { DashboardModel } from '../../state';
|
||||
|
||||
import { GenAIButton } from './GenAIButton';
|
||||
import { EventTrackingSrc } from './tracking';
|
||||
import { getDashboardPanelPrompt, Message, Role } from './utils';
|
||||
import {
|
||||
DASHBOARD_NEED_PANEL_TITLES_AND_DESCRIPTIONS_MESSAGE,
|
||||
getDashboardPanelPrompt,
|
||||
getPanelStrings,
|
||||
Message,
|
||||
Role,
|
||||
} from './utils';
|
||||
|
||||
interface GenAIDashTitleButtonProps {
|
||||
onGenerate: (description: string) => void;
|
||||
@ -22,19 +29,22 @@ const TITLE_GENERATION_STANDARD_PROMPT =
|
||||
'Respond with only the title of the dashboard.';
|
||||
|
||||
export const GenAIDashTitleButton = ({ onGenerate }: GenAIDashTitleButtonProps) => {
|
||||
const dashboard = getDashboardSrv().getCurrent()!;
|
||||
const panelStrings = getPanelStrings(dashboard);
|
||||
|
||||
return (
|
||||
<GenAIButton
|
||||
messages={getMessages}
|
||||
messages={getMessages(dashboard)}
|
||||
onGenerate={onGenerate}
|
||||
eventTrackingSrc={EventTrackingSrc.dashboardTitle}
|
||||
toggleTipTitle={'Improve your dashboard title'}
|
||||
disabled={panelStrings.length === 0}
|
||||
tooltip={panelStrings.length === 0 ? DASHBOARD_NEED_PANEL_TITLES_AND_DESCRIPTIONS_MESSAGE : undefined}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
function getMessages(): Message[] {
|
||||
const dashboard = getDashboardSrv().getCurrent()!;
|
||||
|
||||
function getMessages(dashboard: DashboardModel): Message[] {
|
||||
return [
|
||||
{
|
||||
content: TITLE_GENERATION_STANDARD_PROMPT,
|
||||
|
@ -2,8 +2,9 @@ import { llms } from '@grafana/experimental';
|
||||
|
||||
import { DASHBOARD_SCHEMA_VERSION } from '../../state/DashboardMigrator';
|
||||
import { createDashboardModelFixture, createPanelSaveModel } from '../../state/__fixtures__/dashboardFixtures';
|
||||
import { NEW_PANEL_TITLE } from '../../utils/dashboard';
|
||||
|
||||
import { getDashboardChanges, isLLMPluginEnabled, sanitizeReply } from './utils';
|
||||
import { getDashboardChanges, getPanelStrings, isLLMPluginEnabled, sanitizeReply } from './utils';
|
||||
|
||||
// Mock the llms.openai module
|
||||
jest.mock('@grafana/experimental', () => ({
|
||||
@ -134,3 +135,47 @@ describe('sanitizeReply', () => {
|
||||
expect(sanitizeReply('')).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPanelStrings', () => {
|
||||
function dashboardSetup(items: Array<{ title: string; description: string }>) {
|
||||
return createDashboardModelFixture({
|
||||
panels: items.map((item) => createPanelSaveModel(item)),
|
||||
});
|
||||
}
|
||||
|
||||
it('should return an empty array if all panels dont have title or descriptions', () => {
|
||||
const dashboard = dashboardSetup([{ title: '', description: '' }]);
|
||||
|
||||
expect(getPanelStrings(dashboard)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return an empty array if all panels have no description and panels that have title are titled "Panel title', () => {
|
||||
const dashboard = dashboardSetup([{ title: NEW_PANEL_TITLE, description: '' }]);
|
||||
|
||||
expect(getPanelStrings(dashboard)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return an array of panels if a panel has a title or description', () => {
|
||||
const dashboard = dashboardSetup([
|
||||
{ title: 'Graph panel', description: '' },
|
||||
{ title: '', description: 'Logs' },
|
||||
]);
|
||||
|
||||
expect(getPanelStrings(dashboard)).toEqual([
|
||||
'- Panel 0\n- Title: Graph panel',
|
||||
'- Panel 1\n- Title: \n- Description: Logs',
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns an array with title and description if both are present', () => {
|
||||
const dashboard = dashboardSetup([
|
||||
{ title: 'Graph panel', description: 'Logs' },
|
||||
{ title: 'Table panel', description: 'Metrics' },
|
||||
]);
|
||||
|
||||
expect(getPanelStrings(dashboard)).toEqual([
|
||||
'- Panel 0\n- Title: Graph panel\n- Description: Logs',
|
||||
'- Panel 1\n- Title: Table panel\n- Description: Metrics',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -5,6 +5,7 @@ import { config } from '@grafana/runtime';
|
||||
import { Panel } from '@grafana/schema';
|
||||
|
||||
import { DashboardModel, PanelModel } from '../../state';
|
||||
import { NEW_PANEL_TITLE } from '../../utils/dashboard';
|
||||
|
||||
import { getDashboardStringDiff } from './jsonDiffText';
|
||||
|
||||
@ -111,11 +112,7 @@ export const getFeedbackMessage = (previousResponse: string, feedback: string |
|
||||
* @returns String for inclusion in prompts stating what the dashboard's panels are
|
||||
*/
|
||||
export function getDashboardPanelPrompt(dashboard: DashboardModel): string {
|
||||
const getPanelString = (panel: PanelModel, idx: number) =>
|
||||
`- Panel ${idx}
|
||||
- Title: ${panel.title}${panel.description ? `\n- Description: ${panel.description}` : ''}`;
|
||||
|
||||
const panelStrings: string[] = dashboard.panels.map(getPanelString);
|
||||
const panelStrings: string[] = getPanelStrings(dashboard);
|
||||
let panelPrompt: string;
|
||||
|
||||
if (panelStrings.length <= 10) {
|
||||
@ -158,3 +155,22 @@ export function getFilteredPanelString(panel: Panel): string {
|
||||
|
||||
return JSON.stringify(filteredPanel, null, 2);
|
||||
}
|
||||
|
||||
export const DASHBOARD_NEED_PANEL_TITLES_AND_DESCRIPTIONS_MESSAGE =
|
||||
'To generate this content your dashboard must contain at least one panel with a valid title or description.';
|
||||
|
||||
export function getPanelStrings(dashboard: DashboardModel): string[] {
|
||||
const panelStrings = dashboard.panels
|
||||
.filter(
|
||||
(panel) =>
|
||||
(panel.title.length > 0 && panel.title !== NEW_PANEL_TITLE) ||
|
||||
(panel.description && panel.description.length > 0)
|
||||
)
|
||||
.map(getPanelString);
|
||||
|
||||
return panelStrings;
|
||||
}
|
||||
|
||||
const getPanelString = (panel: PanelModel, idx: number) =>
|
||||
`- Panel ${idx}
|
||||
- Title: ${panel.title}${panel.description ? `\n- Description: ${panel.description}` : ''}`;
|
||||
|
@ -8,10 +8,12 @@ import store from 'app/core/store';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
import { calculateNewPanelGridPos } from 'app/features/dashboard/utils/panel';
|
||||
|
||||
export const NEW_PANEL_TITLE = 'Panel Title';
|
||||
|
||||
export function onCreateNewPanel(dashboard: DashboardModel, datasource?: string): number | undefined {
|
||||
const newPanel: Partial<PanelModel> = {
|
||||
type: 'timeseries',
|
||||
title: 'Panel Title',
|
||||
title: NEW_PANEL_TITLE,
|
||||
gridPos: calculateNewPanelGridPos(dashboard),
|
||||
datasource: datasource ? { uid: datasource } : null,
|
||||
isNew: true,
|
||||
@ -68,7 +70,7 @@ export function onPasteCopiedPanel(dashboard: DashboardModel, panelPluginInfo?:
|
||||
|
||||
const newPanel = {
|
||||
type: panelPluginInfo.id,
|
||||
title: 'Panel Title',
|
||||
title: NEW_PANEL_TITLE,
|
||||
gridPos: {
|
||||
x: gridPos.x,
|
||||
y: gridPos.y,
|
||||
|
Loading…
Reference in New Issue
Block a user