From 77c3be054d5a1c38bfdc301e9af440770ecc0b69 Mon Sep 17 00:00:00 2001 From: Kristina Durivage Date: Mon, 1 Jul 2024 18:41:20 -0500 Subject: [PATCH] WIP --- .../QueryLibrary/QueryTemplateForm.tsx | 176 ++++++++++++++++++ .../QueryTemplatesTable/ActionsCell.tsx | 40 +++- .../QueryTemplatesTable/index.tsx | 2 +- .../app/features/query-library/api/factory.ts | 14 +- .../app/features/query-library/api/mappers.ts | 6 +- public/app/features/query-library/index.ts | 8 +- public/app/features/query-library/types.ts | 4 + 7 files changed, 237 insertions(+), 13 deletions(-) create mode 100644 public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx diff --git a/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx b/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx new file mode 100644 index 00000000000..8ebd4f47633 --- /dev/null +++ b/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx @@ -0,0 +1,176 @@ +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; + +import { AppEvents, dateTime } from '@grafana/data'; +import { DataSourcePicker, getAppEvents, getDataSourceSrv } 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 { Trans, t } from 'app/core/internationalization'; +import { getQueryDisplayText } from 'app/core/utils/richHistory'; +import { useAddQueryTemplateMutation, useEditQueryTemplateMutation } from 'app/features/query-library'; +import { AddQueryTemplateCommand, EditQueryTemplateCommand } from 'app/features/query-library/types'; + +import { useDatasource } from '../QueryLibrary/utils/useDatasource'; + +import { QueryTemplateRow } from './QueryTemplatesTable/types'; + +type Props = { + onCancel: () => void; + onSave: (isSuccess: boolean) => void; + queryToAdd?: DataQuery; + templateData?: QueryTemplateRow; +}; + +export type QueryDetails = { + description: string; +}; + +const VisibilityOptions = [ + { value: 'Public', label: t('explore.query-library.public', 'Public') }, + { value: 'Private', label: t('explore.query-library.private', '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 QueryTemplateForm = ({ onCancel, onSave, queryToAdd, templateData }: Props) => { + const { register, handleSubmit } = useForm({ + defaultValues: { + description: templateData?.description, + }, + }); + + const [addQueryTemplate] = useAddQueryTemplateMutation(); + const [editQueryTemplate] = useEditQueryTemplateMutation(); + + const datasource = useDatasource(queryToAdd?.datasource); + + const [queryStrings, setQueryStrings] = useState([]); + + // this is an array to support multi query templates sometime in the future + const queries = + queryToAdd !== undefined ? [queryToAdd] : templateData?.query !== undefined ? [templateData?.query] : []; + + const handleAddQueryTemplate = async (addQueryTemplateCommand: AddQueryTemplateCommand) => { + return addQueryTemplate(addQueryTemplateCommand) + .unwrap() + .then(() => { + getAppEvents().publish({ + type: AppEvents.alertSuccess.name, + payload: [ + t('explore.query-library.query-template-added', 'Query template successfully added to the library'), + ], + }); + return true; + }) + .catch(() => { + getAppEvents().publish({ + type: AppEvents.alertError.name, + payload: [ + t('explore.query-library.query-template-error', 'Error attempting to add this query to the library'), + ], + }); + return false; + }); + }; + + const handleEditQueryTemplate = async (EditQueryTemplateCommand: EditQueryTemplateCommand) => { + return editQueryTemplate(EditQueryTemplateCommand) + .unwrap() + .then(() => { + getAppEvents().publish({ + type: AppEvents.alertSuccess.name, + payload: [ + t('explore.query-library.query-template-added', 'Query template successfully added to the library'), + ], + }); + return true; + }) + .catch(() => { + getAppEvents().publish({ + type: AppEvents.alertError.name, + payload: [ + t('explore.query-library.query-template-error', 'Error attempting to add this query to the library'), + ], + }); + return false; + }); + }; + + const onSubmit = async (data: QueryDetails) => { + const timestamp = dateTime().toISOString(); + const temporaryDefaultTitle = + data.description || t('explore.query-library.default-description', 'Public', { timestamp: timestamp }); + + if (templateData?.uid) { + handleEditQueryTemplate({ + uid: templateData.uid, + title: temporaryDefaultTitle, + targets: [templateData.query!], + }).then((isSuccess) => { + onSave(isSuccess); + }); + } else if (queryToAdd) { + handleAddQueryTemplate({ title: temporaryDefaultTitle, targets: [queryToAdd] }).then((isSuccess) => { + onSave(isSuccess); + }); + } + }; + + const generateQueryText = (queries: DataQuery[]) => { + const promises = queries.map(async (query, i) => { + const datasource = await getDataSourceSrv().get(query.datasource); + return datasource?.getQueryDisplayText?.(query) || getQueryDisplayText(query); + }); + Promise.all(promises).then((qStrings) => setQueryStrings(qStrings)); + }; + + generateQueryText(queries); + + return ( +
+

{info}

+ {queryStrings.map((queryString, i) => ( + + + + ))} + {queryToAdd && ( + <> + + + + + + + + )} + + + + + + + + + + + + + ); +}; diff --git a/public/app/features/explore/QueryLibrary/QueryTemplatesTable/ActionsCell.tsx b/public/app/features/explore/QueryLibrary/QueryTemplatesTable/ActionsCell.tsx index 69ac1e65bdf..211f1db16dc 100644 --- a/public/app/features/explore/QueryLibrary/QueryTemplatesTable/ActionsCell.tsx +++ b/public/app/features/explore/QueryLibrary/QueryTemplatesTable/ActionsCell.tsx @@ -1,6 +1,8 @@ +import { useState } from 'react'; + import { reportInteraction, getAppEvents } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; -import { IconButton } from '@grafana/ui'; +import { IconButton, Modal } from '@grafana/ui'; import { notifyApp } from 'app/core/actions'; import { createSuccessNotification } from 'app/core/copy/appNotification'; import { t } from 'app/core/internationalization'; @@ -9,17 +11,21 @@ import { dispatch } from 'app/store/store'; import { ShowConfirmModalEvent } from 'app/types/events'; import ExploreRunQueryButton from '../../ExploreRunQueryButton'; +import { QueryTemplateForm } from '../QueryTemplateForm'; import { useQueryLibraryListStyles } from './styles'; +import { QueryTemplateRow } from './types'; interface ActionsCellProps { queryUid?: string; - query?: DataQuery; + queryTemplate: QueryTemplateRow; rootDatasourceUid?: string; } -function ActionsCell({ query, rootDatasourceUid, queryUid }: ActionsCellProps) { +function ActionsCell({ queryTemplate, rootDatasourceUid, queryUid }: ActionsCellProps) { const [deleteQueryTemplate] = useDeleteQueryTemplateMutation(); + const [editFormOpen, setEditFormOpen] = useState(false); + //const [editQueryTemplate] = useEditQueryTemplateMutation(); const styles = useQueryLibraryListStyles(); const onDeleteQuery = (queryUid: string) => { @@ -57,7 +63,33 @@ function ActionsCell({ query, rootDatasourceUid, queryUid }: ActionsCellProps) { } }} /> - + { + setEditFormOpen(true); + }} + /> + + setEditFormOpen(false)} + > + setEditFormOpen(false)} + templateData={queryTemplate} + onSave={(data) => { + console.log(data); + }} + /> + ); } diff --git a/public/app/features/explore/QueryLibrary/QueryTemplatesTable/index.tsx b/public/app/features/explore/QueryLibrary/QueryTemplatesTable/index.tsx index 376e7457dd8..c95398b0956 100644 --- a/public/app/features/explore/QueryLibrary/QueryTemplatesTable/index.tsx +++ b/public/app/features/explore/QueryLibrary/QueryTemplatesTable/index.tsx @@ -25,7 +25,7 @@ const columns: Array> = [ id: 'actions', header: '', cell: ({ row: { original } }) => ( - + ), }, ]; diff --git a/public/app/features/query-library/api/factory.ts b/public/app/features/query-library/api/factory.ts index 09cc17f76f6..e316abced4f 100644 --- a/public/app/features/query-library/api/factory.ts +++ b/public/app/features/query-library/api/factory.ts @@ -1,8 +1,8 @@ import { createApi } from '@reduxjs/toolkit/query/react'; -import { AddQueryTemplateCommand, DeleteQueryTemplateCommand, QueryTemplate } from '../types'; +import { AddQueryTemplateCommand, DeleteQueryTemplateCommand, QueryTemplate, EditQueryTemplateCommand } from '../types'; -import { convertAddQueryTemplateCommandToDataQuerySpec, convertDataQueryResponseToQueryTemplates } from './mappers'; +import { convertQueryTemplateCommandToDataQuerySpec, convertDataQueryResponseToQueryTemplates } from './mappers'; import { baseQuery } from './query'; export const queryLibraryApi = createApi({ @@ -17,7 +17,7 @@ export const queryLibraryApi = createApi({ addQueryTemplate: builder.mutation({ query: (addQueryTemplateCommand) => ({ method: 'POST', - data: convertAddQueryTemplateCommandToDataQuerySpec(addQueryTemplateCommand), + data: convertQueryTemplateCommandToDataQuerySpec(addQueryTemplateCommand), }), invalidatesTags: ['QueryTemplatesList'], }), @@ -28,6 +28,14 @@ export const queryLibraryApi = createApi({ }), invalidatesTags: ['QueryTemplatesList'], }), + editQueryTemplate: builder.mutation({ + query: (editQueryTemplateCommand) => ({ + url: `${editQueryTemplateCommand.uid}`, + method: 'POST', + data: convertQueryTemplateCommandToDataQuerySpec(editQueryTemplateCommand), + }), + invalidatesTags: ['QueryTemplatesList'], + }), }), reducerPath: 'queryLibrary', }); diff --git a/public/app/features/query-library/api/mappers.ts b/public/app/features/query-library/api/mappers.ts index d3a90d6e497..2a9f8ebd255 100644 --- a/public/app/features/query-library/api/mappers.ts +++ b/public/app/features/query-library/api/mappers.ts @@ -1,4 +1,4 @@ -import { AddQueryTemplateCommand, QueryTemplate } from '../types'; +import { AddQueryTemplateCommand, EditQueryTemplateCommand, QueryTemplate } from '../types'; import { API_VERSION, QueryTemplateKinds } from './query'; import { CREATED_BY_KEY, DataQuerySpec, DataQuerySpecResponse, DataQueryTarget } from './types'; @@ -40,8 +40,8 @@ export const convertDataQueryResponseToQueryTemplates = (result: DataQuerySpecRe }); }; -export const convertAddQueryTemplateCommandToDataQuerySpec = ( - addQueryTemplateCommand: AddQueryTemplateCommand +export const convertQueryTemplateCommandToDataQuerySpec = ( + addQueryTemplateCommand: AddQueryTemplateCommand | EditQueryTemplateCommand ): DataQuerySpec => { const { title, targets } = addQueryTemplateCommand; return { diff --git a/public/app/features/query-library/index.ts b/public/app/features/query-library/index.ts index 513b7ca5bb8..8c5bd02a098 100644 --- a/public/app/features/query-library/index.ts +++ b/public/app/features/query-library/index.ts @@ -12,8 +12,12 @@ import { config } from '@grafana/runtime'; import { queryLibraryApi } from './api/factory'; import { mockData } from './api/mocks'; -export const { useAllQueryTemplatesQuery, useAddQueryTemplateMutation, useDeleteQueryTemplateMutation } = - queryLibraryApi; +export const { + useAllQueryTemplatesQuery, + useAddQueryTemplateMutation, + useDeleteQueryTemplateMutation, + useEditQueryTemplateMutation, +} = queryLibraryApi; export function isQueryLibraryEnabled() { return config.featureToggles.queryLibrary; diff --git a/public/app/features/query-library/types.ts b/public/app/features/query-library/types.ts index 66a92b085a2..3b9737e5435 100644 --- a/public/app/features/query-library/types.ts +++ b/public/app/features/query-library/types.ts @@ -13,6 +13,10 @@ export type AddQueryTemplateCommand = { targets: DataQuery[]; }; +export interface EditQueryTemplateCommand extends AddQueryTemplateCommand { + uid: string; +} + export type DeleteQueryTemplateCommand = { uid: string; };