mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboards: Auto-generate dashboard title and description from settings (#75240)
Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
This commit is contained in:
parent
b5da762477
commit
d531f5ab42
@ -1,13 +1,25 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { ChangeEvent, useState } from 'react';
|
||||||
import { connect, ConnectedProps } from 'react-redux';
|
import { connect, ConnectedProps } from 'react-redux';
|
||||||
|
|
||||||
import { TimeZone } from '@grafana/data';
|
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 { Page } from 'app/core/components/Page/Page';
|
||||||
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
||||||
import { updateTimeZoneDashboard, updateWeekStartDashboard } from 'app/features/dashboard/state/actions';
|
import { updateTimeZoneDashboard, updateWeekStartDashboard } from 'app/features/dashboard/state/actions';
|
||||||
|
|
||||||
import { DeleteDashboardButton } from '../DeleteDashboard/DeleteDashboardButton';
|
import { DeleteDashboardButton } from '../DeleteDashboard/DeleteDashboardButton';
|
||||||
|
import { GenAIDashDescriptionButton } from '../GenAI/GenAIDashDescriptionButton';
|
||||||
|
import { GenAIDashTitleButton } from '../GenAI/GenAIDashTitleButton';
|
||||||
|
|
||||||
import { TimePickerSettings } from './TimePickerSettings';
|
import { TimePickerSettings } from './TimePickerSettings';
|
||||||
import { SettingsPageProps } from './types';
|
import { SettingsPageProps } from './types';
|
||||||
@ -27,6 +39,8 @@ export function GeneralSettingsUnconnected({
|
|||||||
sectionNav,
|
sectionNav,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const [renderCounter, setRenderCounter] = useState(0);
|
const [renderCounter, setRenderCounter] = useState(0);
|
||||||
|
const [dashboardTitle, setDashboardTitle] = useState(dashboard.title);
|
||||||
|
const [dashboardDescription, setDashboardDescription] = useState(dashboard.description);
|
||||||
|
|
||||||
const onFolderChange = (newUID: string, newTitle: string) => {
|
const onFolderChange = (newUID: string, newTitle: string) => {
|
||||||
dashboard.meta.folderUid = newUID;
|
dashboard.meta.folderUid = newUID;
|
||||||
@ -35,11 +49,21 @@ export function GeneralSettingsUnconnected({
|
|||||||
setRenderCounter(renderCounter + 1);
|
setRenderCounter(renderCounter + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onBlur = (event: React.FocusEvent<HTMLInputElement>) => {
|
const onTitleChange = React.useCallback(
|
||||||
if (event.currentTarget.name === 'title' || event.currentTarget.name === 'description') {
|
(title: string) => {
|
||||||
dashboard[event.currentTarget.name] = event.currentTarget.value;
|
dashboard.title = title;
|
||||||
}
|
setDashboardTitle(title);
|
||||||
};
|
},
|
||||||
|
[setDashboardTitle, dashboard]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onDescriptionChange = React.useCallback(
|
||||||
|
(description: string) => {
|
||||||
|
dashboard.description = description;
|
||||||
|
setDashboardDescription(description);
|
||||||
|
},
|
||||||
|
[setDashboardDescription, dashboard]
|
||||||
|
);
|
||||||
|
|
||||||
const onTooltipChange = (graphTooltip: number) => {
|
const onTooltipChange = (graphTooltip: number) => {
|
||||||
dashboard.graphTooltip = graphTooltip;
|
dashboard.graphTooltip = graphTooltip;
|
||||||
@ -95,11 +119,39 @@ export function GeneralSettingsUnconnected({
|
|||||||
<Page navModel={sectionNav}>
|
<Page navModel={sectionNav}>
|
||||||
<div style={{ maxWidth: '600px' }}>
|
<div style={{ maxWidth: '600px' }}>
|
||||||
<div className="gf-form-group">
|
<div className="gf-form-group">
|
||||||
<Field label="Name">
|
<Field
|
||||||
<Input id="title-input" name="title" onBlur={onBlur} defaultValue={dashboard.title} />
|
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>
|
||||||
<Field label="Description">
|
<Field
|
||||||
<Input id="description-input" name="description" onBlur={onBlur} defaultValue={dashboard.description} />
|
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>
|
||||||
<Field label="Tags">
|
<Field label="Tags">
|
||||||
<TagsInput id="tags-input" tags={dashboard.tags} onChange={onTagsChange} width={40} />
|
<TagsInput id="tags-input" tags={dashboard.tags} onChange={onTagsChange} width={40} />
|
||||||
|
@ -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,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
@ -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,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user