Explore: Show a modal to edit query template before saving it (#88211)

* Create a mock modal

* Add basic form handling

* Update tests

* Extract translations

* Disable auto-star switch

* Keep disabled input uncontrolled
This commit is contained in:
Piotr Jamróz 2024-05-31 11:55:01 +02:00 committed by GitHub
parent 287e3868ed
commit ce23a455c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 144 additions and 13 deletions

View File

@ -1,18 +1,21 @@
import { t } from 'i18next';
import React from 'react';
import React, { useState } from 'react';
import { AppEvents, dateTime } from '@grafana/data';
import { getAppEvents } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema';
import { Button } from '@grafana/ui';
import { Button, Modal } from '@grafana/ui';
import { isQueryLibraryEnabled, useAddQueryTemplateMutation } from 'app/features/query-library';
import { AddQueryTemplateCommand } from 'app/features/query-library/types';
import { QueryDetails, RichHistoryAddToLibraryForm } from './RichHistoryAddToLibraryForm';
type Props = {
query: DataQuery;
};
export const RichHistoryAddToLibrary = ({ query }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const [addQueryTemplate, { isSuccess }] = useAddQueryTemplateMutation();
const handleAddQueryTemplate = async (addQueryTemplateCommand: AddQueryTemplateCommand) => {
@ -29,17 +32,31 @@ export const RichHistoryAddToLibrary = ({ query }: Props) => {
const buttonLabel = t('explore.rich-history-card.add-to-library', 'Add to library');
const submit = (data: QueryDetails) => {
const timestamp = dateTime().toISOString();
const temporaryDefaultTitle = data.description || `Imported from Explore - ${timestamp}`;
handleAddQueryTemplate({ title: temporaryDefaultTitle, targets: [query] });
};
return isQueryLibraryEnabled() && !isSuccess ? (
<Button
variant="secondary"
aria-label={buttonLabel}
onClick={() => {
const timestamp = dateTime().toISOString();
const temporaryDefaultTitle = `Imported from Explore - ${timestamp}`;
handleAddQueryTemplate({ title: temporaryDefaultTitle, targets: [query] });
}}
>
{buttonLabel}
</Button>
<>
<Button variant="secondary" aria-label={buttonLabel} onClick={() => setIsOpen(true)}>
{buttonLabel}
</Button>
<Modal
title={t('explore.add-to-library-modal.title', 'Add query to Query Library')}
isOpen={isOpen}
onDismiss={() => setIsOpen(false)}
>
<RichHistoryAddToLibraryForm
onCancel={() => setIsOpen(() => false)}
query={query}
onSave={(data) => {
submit(data);
setIsOpen(false);
}}
/>
</Modal>
</>
) : undefined;
};

View File

@ -0,0 +1,83 @@
import React, { useMemo } from 'react';
import { useForm } from 'react-hook-form';
import { DataSourcePicker } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema';
import { Button, InlineSwitch, Modal, RadioButtonGroup, TextArea } from '@grafana/ui';
import { Field } from '@grafana/ui/';
import { Input } from '@grafana/ui/src/components/Input/Input';
import { t } from 'app/core/internationalization';
import { getQueryDisplayText } from '../../../core/utils/richHistory';
import { useDatasource } from '../QueryLibrary/utils/useDatasource';
type Props = {
onCancel: () => void;
onSave: (details: QueryDetails) => void;
query: DataQuery;
};
export type QueryDetails = {
description: string;
};
const VisibilityOptions = [
{ value: 'Public', label: 'Public' },
{ value: 'Private', label: 'Private' },
];
const info = t(
'explore.add-to-library-modal.info',
`You're about to save this query. Once saved, you can easily access it in the Query Library tab for future use and reference.`
);
export const RichHistoryAddToLibraryForm = ({ onCancel, onSave, query }: Props) => {
const { register, handleSubmit } = useForm<QueryDetails>();
const datasource = useDatasource(query.datasource);
const displayText = useMemo(() => {
return datasource?.getQueryDisplayText?.(query) || getQueryDisplayText(query);
}, [datasource, query]);
const onSubmit = (data: QueryDetails) => {
onSave(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<p>{info}</p>
<Field label={t('explore.add-to-library-modal.query', 'Query')}>
<TextArea readOnly={true} value={displayText}></TextArea>
</Field>
<Field label={t('explore.add-to-library-modal.data-source-name', 'Data source name')}>
<DataSourcePicker current={datasource?.uid} disabled={true} />
</Field>
<Field label={t('explore.add-to-library-modal.data-source-type', 'Data source type')}>
<Input disabled={true} defaultValue={datasource?.meta.name}></Input>
</Field>
<Field label={t('explore.add-to-library-modal.description', 'Description')}>
<Input id="query-template-description" autoFocus={true} {...register('description')}></Input>
</Field>
<Field label={t('explore.add-to-library-modal.visibility', 'Visibility')}>
<RadioButtonGroup options={VisibilityOptions} value={'Public'} disabled={true} />
</Field>
<InlineSwitch
showLabel={true}
disabled={true}
label={t(
'explore.add-to-library-modal.auto-star',
'Auto-star this query to add it to your starred list in the Query Library.'
)}
/>
<Modal.ButtonRow>
<Button variant="secondary" onClick={() => onCancel()} fill="outline">
Cancel
</Button>
<Button variant="primary" type="submit">
Save
</Button>
</Modal.ButtonRow>
</form>
);
};

View File

@ -55,6 +55,15 @@ export const addQueryHistoryToQueryLibrary = async () => {
await userEvent.click(button);
};
export const submitAddToQueryLibrary = async ({ description }: { description: string }) => {
const input = within(screen.getByRole('dialog')).getByLabelText('Description');
await userEvent.type(input, description);
const saveButton = screen.getByRole('button', {
name: /save/i,
});
await userEvent.click(saveButton);
};
export const closeQueryHistory = async () => {
const selector = withinQueryHistory();
const closeButton = selector.getByRole('button', { name: 'Close query history' });

View File

@ -16,6 +16,7 @@ import {
addQueryHistoryToQueryLibrary,
openQueryHistory,
openQueryLibrary,
submitAddToQueryLibrary,
switchToQueryHistory,
} from './helper/interactions';
import { setupExplore, waitForExplore } from './helper/setup';
@ -134,6 +135,7 @@ describe('QueryLibrary', () => {
await switchToQueryHistory();
await assertQueryHistory(['{"expr":"TEST"}']);
await addQueryHistoryToQueryLibrary();
await submitAddToQueryLibrary({ description: 'Test' });
expect(testEventBus.publish).toHaveBeenCalledWith(
expect.objectContaining({
type: 'alert-success',

View File

@ -465,6 +465,16 @@
},
"explore": {
"add-to-dashboard": "Add to dashboard",
"add-to-library-modal": {
"auto-star": "Auto-star this query to add it to your starred list in the Query Library.",
"data-source-name": "Data source name",
"data-source-type": "Data source type",
"description": "Description",
"info": "You're about to save this query. Once saved, you can easily access it in the Query Library tab for future use and reference.",
"query": "Query",
"title": "Add query to Query Library",
"visibility": "Visibility"
},
"rich-history": {
"close-tooltip": "Close query history",
"datasource-a-z": "Data source A-Z",

View File

@ -465,6 +465,16 @@
},
"explore": {
"add-to-dashboard": "Åđđ ŧő đäşĥþőäřđ",
"add-to-library-modal": {
"auto-star": "Åūŧő-şŧäř ŧĥįş qūęřy ŧő äđđ įŧ ŧő yőūř şŧäřřęđ ľįşŧ įʼn ŧĥę Qūęřy Ŀįþřäřy.",
"data-source-name": "Đäŧä şőūřčę ʼnämę",
"data-source-type": "Đäŧä şőūřčę ŧypę",
"description": "Đęşčřįpŧįőʼn",
"info": "Ÿőū'řę äþőūŧ ŧő şävę ŧĥįş qūęřy. Øʼnčę şävęđ, yőū čäʼn ęäşįľy äččęşş įŧ įʼn ŧĥę Qūęřy Ŀįþřäřy ŧäþ ƒőř ƒūŧūřę ūşę äʼnđ řęƒęřęʼnčę.",
"query": "Qūęřy",
"title": "Åđđ qūęřy ŧő Qūęřy Ŀįþřäřy",
"visibility": "Vįşįþįľįŧy"
},
"rich-history": {
"close-tooltip": "Cľőşę qūęřy ĥįşŧőřy",
"datasource-a-z": "Đäŧä şőūřčę Å-Ż",