Dashboards: Auto-generate dashboard title and description from settings (#75240)

Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
This commit is contained in:
Ivan Ortega Alba 2023-09-22 04:07:56 +02:00 committed by GitHub
parent b5da762477
commit d531f5ab42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 151 additions and 11 deletions

View File

@ -1,13 +1,25 @@
import React, { useState } from 'react';
import React, { ChangeEvent, useState } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { TimeZone } from '@grafana/data';
import { CollapsableSection, Field, Input, RadioButtonGroup, TagsInput } from '@grafana/ui';
import { config } from '@grafana/runtime';
import {
CollapsableSection,
Field,
Input,
RadioButtonGroup,
TagsInput,
Label,
HorizontalGroup,
TextArea,
} from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
import { updateTimeZoneDashboard, updateWeekStartDashboard } from 'app/features/dashboard/state/actions';
import { DeleteDashboardButton } from '../DeleteDashboard/DeleteDashboardButton';
import { GenAIDashDescriptionButton } from '../GenAI/GenAIDashDescriptionButton';
import { GenAIDashTitleButton } from '../GenAI/GenAIDashTitleButton';
import { TimePickerSettings } from './TimePickerSettings';
import { SettingsPageProps } from './types';
@ -27,6 +39,8 @@ export function GeneralSettingsUnconnected({
sectionNav,
}: Props): JSX.Element {
const [renderCounter, setRenderCounter] = useState(0);
const [dashboardTitle, setDashboardTitle] = useState(dashboard.title);
const [dashboardDescription, setDashboardDescription] = useState(dashboard.description);
const onFolderChange = (newUID: string, newTitle: string) => {
dashboard.meta.folderUid = newUID;
@ -35,11 +49,21 @@ export function GeneralSettingsUnconnected({
setRenderCounter(renderCounter + 1);
};
const onBlur = (event: React.FocusEvent<HTMLInputElement>) => {
if (event.currentTarget.name === 'title' || event.currentTarget.name === 'description') {
dashboard[event.currentTarget.name] = event.currentTarget.value;
}
};
const onTitleChange = React.useCallback(
(title: string) => {
dashboard.title = title;
setDashboardTitle(title);
},
[setDashboardTitle, dashboard]
);
const onDescriptionChange = React.useCallback(
(description: string) => {
dashboard.description = description;
setDashboardDescription(description);
},
[setDashboardDescription, dashboard]
);
const onTooltipChange = (graphTooltip: number) => {
dashboard.graphTooltip = graphTooltip;
@ -95,11 +119,39 @@ export function GeneralSettingsUnconnected({
<Page navModel={sectionNav}>
<div style={{ maxWidth: '600px' }}>
<div className="gf-form-group">
<Field label="Name">
<Input id="title-input" name="title" onBlur={onBlur} defaultValue={dashboard.title} />
<Field
label={
<HorizontalGroup justify="space-between">
<Label htmlFor="title-input">Title</Label>
{config.featureToggles.dashgpt && (
<GenAIDashTitleButton onGenerate={onTitleChange} dashboard={dashboard} />
)}
</HorizontalGroup>
}
>
<Input
id="title-input"
name="title"
value={dashboardTitle}
onChange={(e: ChangeEvent<HTMLInputElement>) => onTitleChange(e.target.value)}
/>
</Field>
<Field label="Description">
<Input id="description-input" name="description" onBlur={onBlur} defaultValue={dashboard.description} />
<Field
label={
<HorizontalGroup justify="space-between">
<Label htmlFor="description-input">Description</Label>
{config.featureToggles.dashgpt && (
<GenAIDashDescriptionButton onGenerate={onDescriptionChange} dashboard={dashboard} />
)}
</HorizontalGroup>
}
>
<TextArea
id="description-input"
name="description"
value={dashboardDescription}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => onDescriptionChange(e.target.value)}
/>
</Field>
<Field label="Tags">
<TagsInput id="tags-input" tags={dashboard.tags} onChange={onTagsChange} width={40} />

View File

@ -0,0 +1,45 @@
import React from 'react';
import { DashboardModel } from '../../state';
import { GenAIButton } from './GenAIButton';
import { Message, Role } from './utils';
interface GenAIDashDescriptionButtonProps {
onGenerate: (description: string, isDone: boolean) => void;
dashboard: DashboardModel;
}
const DESCRIPTION_GENERATION_STANDARD_PROMPT =
'You are an expert in Grafana dashboards.' +
'Your goal is to write short, descriptive, and concise dashboards description using the dashboard panels title and descriptions. ' +
'The description should be shorter than 140 characters.';
export const GenAIDashDescriptionButton = ({ onGenerate, dashboard }: GenAIDashDescriptionButtonProps) => {
const messages = React.useMemo(() => getMessages(dashboard), [dashboard]);
return <GenAIButton messages={messages} onReply={onGenerate} loadingText={'Generating description'} />;
};
function getMessages(dashboard: DashboardModel): Message[] {
return [
{
content: DESCRIPTION_GENERATION_STANDARD_PROMPT,
role: Role.system,
},
{
content: `The title of the dashboard is "${
dashboard.title
}" and the the panels in the dashboard are: ${dashboard.panels
.map(
(panel, idx) => `
- Panel ${idx}
- Title: ${panel.title}
${panel.description ? `- Description: ${panel.description}` : ''}
`
)
.join('\n')}`,
role: Role.system,
},
];
}

View File

@ -0,0 +1,43 @@
import React from 'react';
import { DashboardModel } from '../../state';
import { GenAIButton } from './GenAIButton';
import { Message, Role } from './utils';
interface GenAIDashTitleButtonProps {
dashboard: DashboardModel;
onGenerate: (description: string, isDone: boolean) => void;
}
const DESCRIPTION_GENERATION_STANDARD_PROMPT =
'You are an expert in Grafana dashboards.' +
'Your goal is to write the dashboard title inspired by the title and descriptions for the dashboard panels. ' +
'The title must be shorter than 50 characters.';
export const GenAIDashTitleButton = ({ onGenerate, dashboard }: GenAIDashTitleButtonProps) => {
const messages = React.useMemo(() => getMessages(dashboard), [dashboard]);
return <GenAIButton messages={messages} onReply={onGenerate} loadingText={'Generating title'} />;
};
function getMessages(dashboard: DashboardModel): Message[] {
return [
{
content: DESCRIPTION_GENERATION_STANDARD_PROMPT,
role: Role.system,
},
{
content: `The panels in the dashboard are: ${dashboard.panels
.map(
(panel, idx) => `
- Panel ${idx}
- Title: ${panel.title}
${panel.description ? `- Description: ${panel.description}` : ''}
`
)
.join('\n')}`,
role: Role.system,
},
];
}