diff --git a/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx b/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx index a1e8bb5c568..6989044a9c4 100644 --- a/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx +++ b/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx @@ -41,6 +41,7 @@ export function QueryTemplatesList() { const datasourceType = getDatasourceSrv().getInstanceSettings(datasourceRef)?.meta.name || ''; return { index: index.toString(), + uid: queryTemplate.uid, datasourceRef, datasourceType, createdAtTimestamp: queryTemplate?.createdAtTimestamp || 0, diff --git a/public/app/features/explore/QueryLibrary/QueryTemplatesTable/ActionsCell.tsx b/public/app/features/explore/QueryLibrary/QueryTemplatesTable/ActionsCell.tsx index 6dd1c181d69..4ffd14edad6 100644 --- a/public/app/features/explore/QueryLibrary/QueryTemplatesTable/ActionsCell.tsx +++ b/public/app/features/explore/QueryLibrary/QueryTemplatesTable/ActionsCell.tsx @@ -1,16 +1,67 @@ import React from 'react'; +import { reportInteraction, getAppEvents } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; +import { IconButton } from '@grafana/ui'; +import { notifyApp } from 'app/core/actions'; +import { createSuccessNotification } from 'app/core/copy/appNotification'; +import { t } from 'app/core/internationalization'; +import { useDeleteQueryTemplateMutation } from 'app/features/query-library'; +import { dispatch } from 'app/store/store'; +import { ShowConfirmModalEvent } from 'app/types/events'; import ExploreRunQueryButton from '../../ExploreRunQueryButton'; +import { useQueryLibraryListStyles } from './styles'; + interface ActionsCellProps { + queryUid?: string; query?: DataQuery; rootDatasourceUid?: string; } -function ActionsCell({ query, rootDatasourceUid }: ActionsCellProps) { - return ; +function ActionsCell({ query, rootDatasourceUid, queryUid }: ActionsCellProps) { + const [deleteQueryTemplate] = useDeleteQueryTemplateMutation(); + const styles = useQueryLibraryListStyles(); + + const onDeleteQuery = (queryUid: string) => { + const performDelete = (queryUid: string) => { + deleteQueryTemplate({ uid: queryUid }); + dispatch(notifyApp(createSuccessNotification(t('explore.query-library.query-deleted', 'Query deleted')))); + reportInteraction('grafana_explore_query_library_deleted'); + }; + + getAppEvents().publish( + new ShowConfirmModalEvent({ + title: t('explore.query-library.delete-query-title', 'Delete query'), + text: t( + 'explore.query-library.delete-query-text', + "You're about to remove this query from the query library. This action cannot be undone. Do you want to continue?" + ), + yesText: t('query-library.delete-query-button', 'Delete query'), + icon: 'trash-alt', + onConfirm: () => performDelete(queryUid), + }) + ); + }; + + return ( +
+ { + if (queryUid) { + onDeleteQuery(queryUid); + } + }} + /> + +
+ ); } export default ActionsCell; diff --git a/public/app/features/explore/QueryLibrary/QueryTemplatesTable/index.tsx b/public/app/features/explore/QueryLibrary/QueryTemplatesTable/index.tsx index 56625e3cf15..0b90f8d29dd 100644 --- a/public/app/features/explore/QueryLibrary/QueryTemplatesTable/index.tsx +++ b/public/app/features/explore/QueryLibrary/QueryTemplatesTable/index.tsx @@ -26,7 +26,7 @@ const columns: Array> = [ id: 'actions', header: '', cell: ({ row: { original } }) => ( - + ), }, ]; diff --git a/public/app/features/explore/QueryLibrary/QueryTemplatesTable/styles.tsx b/public/app/features/explore/QueryLibrary/QueryTemplatesTable/styles.tsx index 1c7897e6e5d..715cd5d235b 100644 --- a/public/app/features/explore/QueryLibrary/QueryTemplatesTable/styles.tsx +++ b/public/app/features/explore/QueryLibrary/QueryTemplatesTable/styles.tsx @@ -34,4 +34,14 @@ const getStyles = (theme: GrafanaTheme2) => ({ WebkitLineClamp: 1, overflow: 'hidden', }), + cell: css({ + display: 'flex', + alignItems: 'center', + '&:last-child': { + justifyContent: 'end', + }, + }), + actionButton: css({ + padding: theme.spacing(1), + }), }); diff --git a/public/app/features/explore/QueryLibrary/QueryTemplatesTable/types.ts b/public/app/features/explore/QueryLibrary/QueryTemplatesTable/types.ts index 62ddcf12420..59cc7b24b6a 100644 --- a/public/app/features/explore/QueryLibrary/QueryTemplatesTable/types.ts +++ b/public/app/features/explore/QueryLibrary/QueryTemplatesTable/types.ts @@ -7,4 +7,5 @@ export type QueryTemplateRow = { datasourceRef?: DataSourceRef | null; datasourceType?: string; createdAtTimestamp?: number; + uid?: string; }; diff --git a/public/app/features/query-library/api/factory.ts b/public/app/features/query-library/api/factory.ts index bff1973010f..09cc17f76f6 100644 --- a/public/app/features/query-library/api/factory.ts +++ b/public/app/features/query-library/api/factory.ts @@ -1,6 +1,6 @@ import { createApi } from '@reduxjs/toolkit/query/react'; -import { AddQueryTemplateCommand, QueryTemplate } from '../types'; +import { AddQueryTemplateCommand, DeleteQueryTemplateCommand, QueryTemplate } from '../types'; import { convertAddQueryTemplateCommandToDataQuerySpec, convertDataQueryResponseToQueryTemplates } from './mappers'; import { baseQuery } from './query'; @@ -21,6 +21,13 @@ export const queryLibraryApi = createApi({ }), invalidatesTags: ['QueryTemplatesList'], }), + deleteQueryTemplate: builder.mutation({ + query: ({ uid }) => ({ + url: `${uid}`, + method: 'DELETE', + }), + invalidatesTags: ['QueryTemplatesList'], + }), }), reducerPath: 'queryLibrary', }); diff --git a/public/app/features/query-library/api/query.ts b/public/app/features/query-library/api/query.ts index 80464e1625b..b3baced6237 100644 --- a/public/app/features/query-library/api/query.ts +++ b/public/app/features/query-library/api/query.ts @@ -24,15 +24,20 @@ export enum QueryTemplateKinds { */ export const BASE_URL = `/apis/${API_VERSION}/namespaces/default/querytemplates/`; +// URL is optional for these requests +interface QueryLibraryBackendRequest extends Pick { + url?: string; +} + /** * TODO: similar code is duplicated in many places. To be unified in #86960 */ -export const baseQuery: BaseQueryFn, DataQuerySpecResponse, Error> = async ( +export const baseQuery: BaseQueryFn = async ( requestOptions ) => { try { const responseObservable = getBackendSrv().fetch({ - url: BASE_URL, + url: `${BASE_URL}${requestOptions.url ?? ''}`, showErrorAlert: true, method: requestOptions.method || 'GET', data: requestOptions.data, diff --git a/public/app/features/query-library/index.ts b/public/app/features/query-library/index.ts index 3c2e74e1f81..513b7ca5bb8 100644 --- a/public/app/features/query-library/index.ts +++ b/public/app/features/query-library/index.ts @@ -12,7 +12,8 @@ import { config } from '@grafana/runtime'; import { queryLibraryApi } from './api/factory'; import { mockData } from './api/mocks'; -export const { useAllQueryTemplatesQuery, useAddQueryTemplateMutation } = queryLibraryApi; +export const { useAllQueryTemplatesQuery, useAddQueryTemplateMutation, useDeleteQueryTemplateMutation } = + 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 030d292c901..9be092869d4 100644 --- a/public/app/features/query-library/types.ts +++ b/public/app/features/query-library/types.ts @@ -11,3 +11,7 @@ export type AddQueryTemplateCommand = { title: string; targets: DataQuery[]; }; + +export type DeleteQueryTemplateCommand = { + uid: string; +}; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 2f81df6b661..66cd0278d59 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -532,6 +532,12 @@ "scan-for-older-logs": "Scan for older logs", "stop-scan": "Stop scan" }, + "query-library": { + "delete-query": "Delete query", + "delete-query-text": "You're about to remove this query from the query library. This action cannot be undone. Do you want to continue?", + "delete-query-title": "Delete query", + "query-deleted": "Query deleted" + }, "rich-history": { "close-tooltip": "Close query history", "datasource-a-z": "Data source A-Z", @@ -1559,6 +1565,9 @@ "role-label": "Role" } }, + "query-library": { + "delete-query-button": "Delete query" + }, "query-operation": { "header": { "collapse-row": "Collapse query row", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 288b9f25abc..ce0e02527a6 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -532,6 +532,12 @@ "scan-for-older-logs": "Ŝčäʼn ƒőř őľđęř ľőģş", "stop-scan": "Ŝŧőp şčäʼn" }, + "query-library": { + "delete-query": "Đęľęŧę qūęřy", + "delete-query-text": "Ÿőū'řę äþőūŧ ŧő řęmővę ŧĥįş qūęřy ƒřőm ŧĥę qūęřy ľįþřäřy. Ŧĥįş äčŧįőʼn čäʼnʼnőŧ þę ūʼnđőʼnę. Đő yőū ŵäʼnŧ ŧő čőʼnŧįʼnūę?", + "delete-query-title": "Đęľęŧę qūęřy", + "query-deleted": "Qūęřy đęľęŧęđ" + }, "rich-history": { "close-tooltip": "Cľőşę qūęřy ĥįşŧőřy", "datasource-a-z": "Đäŧä şőūřčę Å-Ż", @@ -1559,6 +1565,9 @@ "role-label": "Ŗőľę" } }, + "query-library": { + "delete-query-button": "Đęľęŧę qūęřy" + }, "query-operation": { "header": { "collapse-row": "Cőľľäpşę qūęřy řőŵ",