diff --git a/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx b/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx index 89d5fde0aab..47de3d424b8 100644 --- a/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx @@ -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 ? ( - + <> + + setIsOpen(false)} + > + setIsOpen(() => false)} + query={query} + onSave={(data) => { + submit(data); + setIsOpen(false); + }} + /> + + ) : undefined; }; diff --git a/public/app/features/explore/RichHistory/RichHistoryAddToLibraryForm.tsx b/public/app/features/explore/RichHistory/RichHistoryAddToLibraryForm.tsx new file mode 100644 index 00000000000..b7bed1dce81 --- /dev/null +++ b/public/app/features/explore/RichHistory/RichHistoryAddToLibraryForm.tsx @@ -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(); + + const datasource = useDatasource(query.datasource); + + const displayText = useMemo(() => { + return datasource?.getQueryDisplayText?.(query) || getQueryDisplayText(query); + }, [datasource, query]); + + const onSubmit = (data: QueryDetails) => { + onSave(data); + }; + + return ( +
+

{info}

+ + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/public/app/features/explore/spec/helper/interactions.ts b/public/app/features/explore/spec/helper/interactions.ts index fa63c6554a9..e3deed335aa 100644 --- a/public/app/features/explore/spec/helper/interactions.ts +++ b/public/app/features/explore/spec/helper/interactions.ts @@ -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' }); diff --git a/public/app/features/explore/spec/queryLibrary.test.tsx b/public/app/features/explore/spec/queryLibrary.test.tsx index 4b0c0ba2b92..8b9b53aa38f 100644 --- a/public/app/features/explore/spec/queryLibrary.test.tsx +++ b/public/app/features/explore/spec/queryLibrary.test.tsx @@ -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', diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index b0e163a7deb..3454d9220f9 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -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", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 33b61703f2e..7e68c61c29f 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -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": "Đäŧä şőūřčę Å-Ż",