From 5f26fd87c754d70c788d406b667c2bc1f639d1d2 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Wed, 9 Oct 2024 06:54:11 -0600 Subject: [PATCH] Query Library: Notifications and query counter (#94444) * Notifications about the feature * i18n * Fix test --- public/app/features/explore/ExplorePage.tsx | 43 +++++++++++++------ .../explore/QueryLibrary/QueryLibrary.tsx | 25 ++++++++++- .../QueryLibrary/QueryLibraryExpmInfo.tsx | 23 ++++++++++ .../QueryLibrary/QueryTemplateForm.tsx | 6 +-- .../QueryLibrary/QueryTemplatesList.tsx | 14 +++++- .../explore/RichHistory/RichHistory.tsx | 5 ++- .../RichHistory/RichHistoryAddToLibrary.tsx | 4 +- .../explore/spec/queryLibrary.test.tsx | 2 +- .../app/features/query-library/api/factory.ts | 4 +- public/locales/en-US/grafana.json | 4 +- public/locales/pseudo-LOCALE/grafana.json | 4 +- 11 files changed, 107 insertions(+), 27 deletions(-) create mode 100644 public/app/features/explore/QueryLibrary/QueryLibraryExpmInfo.tsx diff --git a/public/app/features/explore/ExplorePage.tsx b/public/app/features/explore/ExplorePage.tsx index 1ab96c05414..f5f4c9b6ccc 100644 --- a/public/app/features/explore/ExplorePage.tsx +++ b/public/app/features/explore/ExplorePage.tsx @@ -1,10 +1,11 @@ import { css, cx } from '@emotion/css'; import { useEffect, useState } from 'react'; +import { useLocalStorage } from 'react-use'; import { CoreApp, GrafanaTheme2 } from '@grafana/data'; import { config } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema/dist/esm/index'; -import { ErrorBoundaryAlert, Modal, useStyles2, useTheme2 } from '@grafana/ui'; +import { Badge, ErrorBoundaryAlert, Modal, useStyles2, useTheme2 } from '@grafana/ui'; import { QueryOperationAction } from 'app/core/components/QueryOperationRow/QueryOperationAction'; import { SplitPaneWrapper } from 'app/core/components/SplitPaneWrapper/SplitPaneWrapper'; import { useGrafana } from 'app/core/context/GrafanaContext'; @@ -21,6 +22,7 @@ import { ExploreActions } from './ExploreActions'; import { ExploreDrawer } from './ExploreDrawer'; import { ExplorePaneContainer } from './ExplorePaneContainer'; import { QueriesDrawerContextProvider, useQueriesDrawerContext } from './QueriesDrawer/QueriesDrawerContext'; +import { QUERY_LIBRARY_LOCAL_STORAGE_KEYS } from './QueryLibrary/QueryLibrary'; import { queryLibraryTrackAddFromQueryRow } from './QueryLibrary/QueryLibraryAnalyticsEvents'; import { QueryTemplateForm } from './QueryLibrary/QueryTemplateForm'; import RichHistoryContainer from './RichHistory/RichHistoryContainer'; @@ -63,6 +65,10 @@ function ExplorePageContent(props: GrafanaRouteComponentProps<{}, ExploreQueryPa const { drawerOpened, setDrawerOpened, queryLibraryAvailable } = useQueriesDrawerContext(); const showCorrelationEditorBar = config.featureToggles.correlations && (correlationDetails?.editorMode || false); const [queryToAdd, setQueryToAdd] = useState(); + const [showQueryLibraryBadgeButton, setShowQueryLibraryBadgeButton] = useLocalStorage( + QUERY_LIBRARY_LOCAL_STORAGE_KEYS.explore.newButton, + true + ); useEffect(() => { //This is needed for breadcrumbs and topnav. @@ -77,19 +83,32 @@ function ExplorePageContent(props: GrafanaRouteComponentProps<{}, ExploreQueryPa if (hasQueryLibrary) { RowActionComponents.addKeyedExtraRenderAction(QUERY_LIBRARY_ACTION_KEY, { scope: CoreApp.Explore, - queryActionComponent: (props) => ( - { - setQueryToAdd(props.query); - }} - /> - ), + queryActionComponent: (props) => + showQueryLibraryBadgeButton ? ( + { + setQueryToAdd(props.query); + setShowQueryLibraryBadgeButton(false); + }} + style={{ cursor: 'pointer' }} + /> + ) : ( + { + setQueryToAdd(props.query); + }} + /> + ), }); } - }, []); + }, [showQueryLibraryBadgeButton, setShowQueryLibraryBadgeButton]); useKeyboardShortcuts(); diff --git a/public/app/features/explore/QueryLibrary/QueryLibrary.tsx b/public/app/features/explore/QueryLibrary/QueryLibrary.tsx index 9a296b41fbb..fffc454ee3a 100644 --- a/public/app/features/explore/QueryLibrary/QueryLibrary.tsx +++ b/public/app/features/explore/QueryLibrary/QueryLibrary.tsx @@ -1,3 +1,6 @@ +import { useLocalStorage } from 'react-use'; + +import { QueryLibraryExpmInfo } from './QueryLibraryExpmInfo'; import { QueryTemplatesList } from './QueryTemplatesList'; export interface QueryLibraryProps { @@ -6,6 +9,26 @@ export interface QueryLibraryProps { activeDatasources?: string[]; } +export const QUERY_LIBRARY_LOCAL_STORAGE_KEYS = { + explore: { + notifyUserAboutQueryLibrary: 'grafana.explore.query-library.notifyUserAboutQueryLibrary', + newButton: 'grafana.explore.query-library.newButton', + }, +}; + export function QueryLibrary({ activeDatasources }: QueryLibraryProps) { - return ; + const [notifyUserAboutQueryLibrary, setNotifyUserAboutQueryLibrary] = useLocalStorage( + QUERY_LIBRARY_LOCAL_STORAGE_KEYS.explore.notifyUserAboutQueryLibrary, + true + ); + + return ( + <> + setNotifyUserAboutQueryLibrary(false)} + /> + + + ); } diff --git a/public/app/features/explore/QueryLibrary/QueryLibraryExpmInfo.tsx b/public/app/features/explore/QueryLibrary/QueryLibraryExpmInfo.tsx new file mode 100644 index 00000000000..7c88af4a555 --- /dev/null +++ b/public/app/features/explore/QueryLibrary/QueryLibraryExpmInfo.tsx @@ -0,0 +1,23 @@ +import { Alert, Modal } from '@grafana/ui'; + +interface Props { + isOpen: boolean; + onDismiss: () => void; +} + +export function QueryLibraryExpmInfo({ isOpen, onDismiss }: Props) { + return ( + + + + + + ); +} diff --git a/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx b/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx index ed654fa378d..eefab87c7d1 100644 --- a/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx +++ b/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx @@ -66,9 +66,7 @@ export const QueryTemplateForm = ({ onCancel, onSave, queryToAdd, templateData } .then(() => { getAppEvents().publish({ type: AppEvents.alertSuccess.name, - payload: [ - t('explore.query-library.query-template-added', 'Query template successfully added to the library'), - ], + payload: [t('explore.query-library.query-template-added', 'Query successfully saved to the library')], }); return true; }) @@ -76,7 +74,7 @@ export const QueryTemplateForm = ({ onCancel, onSave, queryToAdd, templateData } getAppEvents().publish({ type: AppEvents.alertError.name, payload: [ - t('explore.query-library.query-template-add-error', 'Error attempting to add this query to the library'), + t('explore.query-library.query-template-add-error', 'Error attempting to save this query to the library'), ], }); return false; diff --git a/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx b/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx index ead35b647c6..48fd253e0f1 100644 --- a/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx +++ b/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx @@ -4,7 +4,7 @@ import { useEffect, useMemo, useState } from 'react'; import { AppEvents, GrafanaTheme2, SelectableValue } from '@grafana/data'; import { getAppEvents, getDataSourceSrv } from '@grafana/runtime'; -import { EmptyState, FilterInput, InlineLabel, MultiSelect, Spinner, useStyles2, Stack } from '@grafana/ui'; +import { EmptyState, FilterInput, InlineLabel, MultiSelect, Spinner, useStyles2, Stack, Badge } from '@grafana/ui'; import { t, Trans } from 'app/core/internationalization'; import { createQueryText } from 'app/core/utils/richHistory'; import { useAllQueryTemplatesQuery } from 'app/features/query-library'; @@ -15,6 +15,7 @@ import { getDatasourceSrv } from '../../plugins/datasource_srv'; import { QueryLibraryProps } from './QueryLibrary'; import { queryLibraryTrackFilterDatasource } from './QueryLibraryAnalyticsEvents'; +import { QueryLibraryExpmInfo } from './QueryLibraryExpmInfo'; import QueryTemplatesTable from './QueryTemplatesTable'; import { QueryTemplateRow } from './QueryTemplatesTable/types'; import { searchQueryLibrary } from './utils/search'; @@ -23,6 +24,7 @@ interface QueryTemplatesListProps extends QueryLibraryProps {} export function QueryTemplatesList(props: QueryTemplatesListProps) { const { data, isLoading, error } = useAllQueryTemplatesQuery(); + const [isModalOpen, setIsModalOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [datasourceFilters, setDatasourceFilters] = useState>>( props.activeDatasources?.map((ds) => ({ value: ds, label: ds })) || [] @@ -163,6 +165,7 @@ export function QueryTemplatesList(props: QueryTemplatesListProps) { return ( <> + setIsModalOpen(false)} /> + setIsModalOpen(true)} + /> diff --git a/public/app/features/explore/RichHistory/RichHistory.tsx b/public/app/features/explore/RichHistory/RichHistory.tsx index cb00212d199..7ddb1d05c90 100644 --- a/public/app/features/explore/RichHistory/RichHistory.tsx +++ b/public/app/features/explore/RichHistory/RichHistory.tsx @@ -11,6 +11,7 @@ import { RichHistorySettings, createDatasourcesList, } from 'app/core/utils/richHistory'; +import { QUERY_LIBRARY_GET_LIMIT, queryLibraryApi } from 'app/features/query-library/api/factory'; import { useSelector } from 'app/types'; import { RichHistoryQuery } from 'app/types/explore'; @@ -96,8 +97,10 @@ export function RichHistory(props: RichHistoryProps) { .map((eDs) => listOfDatasources.find((ds) => ds.uid === eDs.datasource?.uid)?.name) .filter((name): name is string => !!name); + const queryTemplatesCount = useSelector(queryLibraryApi.endpoints.allQueryTemplates.select()).data?.length || 0; + const QueryLibraryTab: TabConfig = { - label: i18n.queryLibrary, + label: `${i18n.queryLibrary} (${queryTemplatesCount}/${QUERY_LIBRARY_GET_LIMIT})`, value: Tabs.QueryLibrary, content: , icon: 'book', diff --git a/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx b/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx index 3c30c6eae7c..cbbcac5f6fe 100644 --- a/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import { DataQuery } from '@grafana/schema'; import { Button, Modal } from '@grafana/ui'; -import { isQueryLibraryEnabled } from 'app/features/query-library'; +import { isQueryLibraryEnabled, useAllQueryTemplatesQuery } from 'app/features/query-library'; import { queryLibraryTrackAddFromQueryHistory, @@ -16,6 +16,7 @@ type Props = { }; export const RichHistoryAddToLibrary = ({ query }: Props) => { + const { refetch } = useAllQueryTemplatesQuery(); const [isOpen, setIsOpen] = useState(false); const [hasBeenSaved, setHasBeenSaved] = useState(false); @@ -45,6 +46,7 @@ export const RichHistoryAddToLibrary = ({ query }: Props) => { if (isSuccess) { setIsOpen(false); setHasBeenSaved(true); + refetch(); queryLibraryTrackAddFromQueryHistory(query.datasource?.type || ''); } }} diff --git a/public/app/features/explore/spec/queryLibrary.test.tsx b/public/app/features/explore/spec/queryLibrary.test.tsx index d69cc001f16..f2422f450d7 100644 --- a/public/app/features/explore/spec/queryLibrary.test.tsx +++ b/public/app/features/explore/spec/queryLibrary.test.tsx @@ -137,7 +137,7 @@ describe('QueryLibrary', () => { expect(testEventBus.publish).toHaveBeenCalledWith( expect.objectContaining({ type: 'alert-success', - payload: ['Query template successfully added to the library'], + payload: ['Query successfully saved to the library'], }) ); await assertAddToQueryLibraryButtonExists(false); diff --git a/public/app/features/query-library/api/factory.ts b/public/app/features/query-library/api/factory.ts index 5ee9b021c59..d456fa4e453 100644 --- a/public/app/features/query-library/api/factory.ts +++ b/public/app/features/query-library/api/factory.ts @@ -7,7 +7,7 @@ import { baseQuery } from './query'; // Currently, we are loading all query templates // Organizations can have maximum of 1000 query templates -const GET_LIMIT = 1000; +export const QUERY_LIBRARY_GET_LIMIT = 1000; export const queryLibraryApi = createApi({ baseQuery, @@ -15,7 +15,7 @@ export const queryLibraryApi = createApi({ endpoints: (builder) => ({ allQueryTemplates: builder.query({ query: () => ({ - url: `?limit=${GET_LIMIT}`, + url: `?limit=${QUERY_LIBRARY_GET_LIMIT}`, }), transformResponse: convertDataQueryResponseToQueryTemplates, providesTags: ['QueryTemplatesList'], diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 0823f93c9b3..2b6cff36111 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -831,8 +831,8 @@ "private": "Private", "public": "Public", "query-deleted": "Query deleted", - "query-template-add-error": "Error attempting to add this query to the library", - "query-template-added": "Query template successfully added to the library", + "query-template-add-error": "Error attempting to save this query to the library", + "query-template-added": "Query successfully saved to the library", "query-template-edit-error": "Error attempting to edit this query", "query-template-edited": "Query template successfully edited", "save": "Save" diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index a2a87a13fd4..c5c78519a42 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -831,8 +831,8 @@ "private": "Přįväŧę", "public": "Pūþľįč", "query-deleted": "Qūęřy đęľęŧęđ", - "query-template-add-error": "Ēřřőř äŧŧęmpŧįʼnģ ŧő äđđ ŧĥįş qūęřy ŧő ŧĥę ľįþřäřy", - "query-template-added": "Qūęřy ŧęmpľäŧę şūččęşşƒūľľy äđđęđ ŧő ŧĥę ľįþřäřy", + "query-template-add-error": "Ēřřőř äŧŧęmpŧįʼnģ ŧő şävę ŧĥįş qūęřy ŧő ŧĥę ľįþřäřy", + "query-template-added": "Qūęřy şūččęşşƒūľľy şävęđ ŧő ŧĥę ľįþřäřy", "query-template-edit-error": "Ēřřőř äŧŧęmpŧįʼnģ ŧő ęđįŧ ŧĥįş qūęřy", "query-template-edited": "Qūęřy ŧęmpľäŧę şūččęşşƒūľľy ęđįŧęđ", "save": "Ŝävę"