From 6e1615b42ad8a2f13c7a90dbe4e94c7a85798b25 Mon Sep 17 00:00:00 2001 From: Artur Wierzbicki Date: Mon, 31 Jan 2022 22:17:05 +0400 Subject: [PATCH] Analytics: send dashboard_list_viewed events to rudderstack (#44589) * #44449: report interactions with showPreviews toggle * #44449: hookify showPreviews * #44449: report dashboard_list_viewed event rather than enabled/disabled_dashboard_previews --- .../components/DashboardSearch.test.tsx | 7 ++++- .../search/components/DashboardSearch.tsx | 19 +++++--------- .../search/components/ManageDashboards.tsx | 18 +++++-------- .../search/hooks/useDashboardSearch.ts | 11 +++++++- .../search/hooks/useManageDashboards.test.ts | 6 +++++ .../search/hooks/useManageDashboards.ts | 26 +++++++++++++++++-- .../search/hooks/useShowDashboardPreviews.ts | 13 ++++++++++ 7 files changed, 72 insertions(+), 28 deletions(-) create mode 100644 public/app/features/search/hooks/useShowDashboardPreviews.ts diff --git a/public/app/features/search/components/DashboardSearch.test.tsx b/public/app/features/search/components/DashboardSearch.test.tsx index ee95a1d6128..029ab069a51 100644 --- a/public/app/features/search/components/DashboardSearch.test.tsx +++ b/public/app/features/search/components/DashboardSearch.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; -import { locationService } from '@grafana/runtime'; +import { locationService, setEchoSrv } from '@grafana/runtime'; import { selectOptionInTest } from '@grafana/ui'; import { selectors } from '@grafana/e2e-selectors'; @@ -9,6 +9,7 @@ import * as MockSearchSrv from 'app/core/services/__mocks__/search_srv'; import { DashboardSearch, Props } from './DashboardSearch'; import { searchResults } from '../testData'; import { SearchLayout } from '../types'; +import { Echo } from 'app/core/services/echo/Echo'; jest.mock('app/core/services/search_srv'); // Typecast the mock search so the mock import is correctly recognised by TS @@ -38,6 +39,10 @@ const setup = (testProps?: Partial) => { * calls inside useDebounce hook */ describe('DashboardSearch', () => { + beforeAll(() => { + setEchoSrv(new Echo()); + }); + it('should call search api with default query when initialised', async () => { locationService.push('/'); setup(); diff --git a/public/app/features/search/components/DashboardSearch.tsx b/public/app/features/search/components/DashboardSearch.tsx index c1213a53f58..4287b45aa0e 100644 --- a/public/app/features/search/components/DashboardSearch.tsx +++ b/public/app/features/search/components/DashboardSearch.tsx @@ -1,15 +1,12 @@ import React, { FC, memo } from 'react'; -import { useLocalStorage } from 'react-use'; import { css } from '@emotion/css'; -import { useTheme2, CustomScrollbar, stylesFactory, IconButton } from '@grafana/ui'; +import { CustomScrollbar, IconButton, stylesFactory, useTheme2 } from '@grafana/ui'; import { GrafanaTheme2 } from '@grafana/data'; -import { config } from '@grafana/runtime'; import { useSearchQuery } from '../hooks/useSearchQuery'; import { useDashboardSearch } from '../hooks/useDashboardSearch'; import { SearchField } from './SearchField'; import { SearchResults } from './SearchResults'; import { ActionRow } from './ActionRow'; -import { PREVIEWS_LOCAL_STORAGE_KEY } from '../constants'; export interface Props { onCloseSearch: () => void; @@ -17,14 +14,12 @@ export interface Props { export const DashboardSearch: FC = memo(({ onCloseSearch }) => { const { query, onQueryChange, onTagFilterChange, onTagAdd, onSortChange, onLayoutChange } = useSearchQuery({}); - const { results, loading, onToggleSection, onKeyDown } = useDashboardSearch(query, onCloseSearch); + const { results, loading, onToggleSection, onKeyDown, showPreviews, onShowPreviewsChange } = useDashboardSearch( + query, + onCloseSearch + ); const theme = useTheme2(); const styles = getStyles(theme); - const previewsEnabled = config.featureToggles.dashboardPreviews; - const [showPreviews, setShowPreviews] = useLocalStorage(PREVIEWS_LOCAL_STORAGE_KEY, previewsEnabled); - const onShowPreviewsChange = (event: React.ChangeEvent) => { - setShowPreviews(event.target.checked); - }; return (
@@ -39,7 +34,7 @@ export const DashboardSearch: FC = memo(({ onCloseSearch }) => { onShowPreviewsChange(ev.target.checked), onSortChange, onTagFilterChange, query, @@ -54,7 +49,7 @@ export const DashboardSearch: FC = memo(({ onCloseSearch }) => { editable={false} onToggleSection={onToggleSection} layout={query.layout} - showPreviews={previewsEnabled && showPreviews} + showPreviews={showPreviews} />
diff --git a/public/app/features/search/components/ManageDashboards.tsx b/public/app/features/search/components/ManageDashboards.tsx index b9382c638e4..b91ab1e75a6 100644 --- a/public/app/features/search/components/ManageDashboards.tsx +++ b/public/app/features/search/components/ManageDashboards.tsx @@ -1,9 +1,7 @@ import React, { FC, memo, useState } from 'react'; -import { useLocalStorage } from 'react-use'; import { css } from '@emotion/css'; -import { stylesFactory, useTheme, Spinner, FilterInput } from '@grafana/ui'; +import { FilterInput, Spinner, stylesFactory, useTheme } from '@grafana/ui'; import { GrafanaTheme } from '@grafana/data'; -import { config } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import { FolderDTO } from 'app/types'; @@ -15,7 +13,6 @@ import { useSearchQuery } from '../hooks/useSearchQuery'; import { SearchResultsFilter } from './SearchResultsFilter'; import { SearchResults } from './SearchResults'; import { DashboardActions } from './DashboardActions'; -import { PREVIEWS_LOCAL_STORAGE_KEY } from '../constants'; export interface Props { folder?: FolderDTO; @@ -24,11 +21,6 @@ export interface Props { const { isEditor } = contextSrv; export const ManageDashboards: FC = memo(({ folder }) => { - const previewsEnabled = Boolean(config.featureToggles.dashboardPreviews); - const [showPreviews, setShowPreviews] = useLocalStorage(PREVIEWS_LOCAL_STORAGE_KEY, true); - const onShowPreviewsChange = (event: React.ChangeEvent) => { - setShowPreviews(event.target.checked); - }; const folderId = folder?.id; const folderUid = folder?.uid; const theme = useTheme(); @@ -69,6 +61,8 @@ export const ManageDashboards: FC = memo(({ folder }) => { onDeleteItems, onMoveItems, noFolders, + showPreviews, + onShowPreviewsChange, } = useManageDashboards(query, {}, folder); const onMoveTo = () => { @@ -114,13 +108,13 @@ export const ManageDashboards: FC = memo(({ folder }) => { canMove={hasEditPermissionInFolders && canMove} deleteItem={onItemDelete} moveTo={onMoveTo} - onShowPreviewsChange={onShowPreviewsChange} + onShowPreviewsChange={(ev) => onShowPreviewsChange(ev.target.checked)} onToggleAllChecked={onToggleAllChecked} onStarredFilterChange={onStarredFilterChange} onSortChange={onSortChange} onTagFilterChange={onTagFilterChange} query={query} - showPreviews={previewsEnabled && showPreviews} + showPreviews={showPreviews} hideLayout={!!folderUid} onLayoutChange={onLayoutChange} editable={hasEditPermissionInFolders} @@ -133,7 +127,7 @@ export const ManageDashboards: FC = memo(({ folder }) => { onToggleSection={onToggleSection} onToggleChecked={onToggleChecked} layout={query.layout} - showPreviews={previewsEnabled && showPreviews} + showPreviews={showPreviews} /> void) => { const reducer = useReducer(searchReducer, dashboardsSearchState); + const { showPreviews, onShowPreviewsChange, previewFeatureEnabled } = useShowDashboardPreviews(); const { state: { results, loading }, onToggleSection, dispatch, } = useSearch(query, reducer, { queryParsing: true }); + useEffect(() => { + reportDashboardListViewed('dashboard_search', showPreviews, previewFeatureEnabled, query.layout); + }, [showPreviews, previewFeatureEnabled, query.layout]); + const onKeyDown = (event: KeyboardEvent) => { switch (event.key) { case 'Escape': @@ -47,5 +54,7 @@ export const useDashboardSearch = (query: DashboardQuery, onCloseSearch: () => v loading, onToggleSection, onKeyDown, + showPreviews, + onShowPreviewsChange, }; }; diff --git a/public/app/features/search/hooks/useManageDashboards.test.ts b/public/app/features/search/hooks/useManageDashboards.test.ts index c3661e99459..f44a8fc0699 100644 --- a/public/app/features/search/hooks/useManageDashboards.test.ts +++ b/public/app/features/search/hooks/useManageDashboards.test.ts @@ -6,11 +6,17 @@ import { DashboardQuery, DashboardSearchItemType, DashboardSection, SearchAction import { ManageDashboardsState } from '../reducers/manageDashboards'; import { useManageDashboards } from './useManageDashboards'; import { GENERAL_FOLDER_ID } from '../constants'; +import { setEchoSrv } from '@grafana/runtime/src'; +import { Echo } from 'app/core/services/echo/Echo'; describe('useManageDashboards', () => { const useSearchMock = jest.spyOn(useSearch, 'useSearch'); const toggle = async (section: DashboardSection) => section; + beforeAll(() => { + setEchoSrv(new Echo()); + }); + function setupTestContext({ results = [] }: { results?: DashboardSection[] } = {}) { jest.clearAllMocks(); diff --git a/public/app/features/search/hooks/useManageDashboards.ts b/public/app/features/search/hooks/useManageDashboards.ts index 82d3bae4e02..6e80aa881b1 100644 --- a/public/app/features/search/hooks/useManageDashboards.ts +++ b/public/app/features/search/hooks/useManageDashboards.ts @@ -1,16 +1,31 @@ -import { useCallback, useMemo, useReducer } from 'react'; +import { useCallback, useEffect, useMemo, useReducer } from 'react'; import { FolderDTO } from 'app/types'; import { contextSrv } from 'app/core/services/context_srv'; -import { DashboardQuery, DashboardSection, OnDeleteItems, OnMoveItems, OnToggleChecked } from '../types'; +import { DashboardQuery, DashboardSection, OnDeleteItems, OnMoveItems, OnToggleChecked, SearchLayout } from '../types'; import { DELETE_ITEMS, MOVE_ITEMS, TOGGLE_ALL_CHECKED, TOGGLE_CHECKED } from '../reducers/actionTypes'; import { manageDashboardsReducer, manageDashboardsState, ManageDashboardsState } from '../reducers/manageDashboards'; import { useSearch } from './useSearch'; import { GENERAL_FOLDER_ID } from '../constants'; +import { useShowDashboardPreviews } from './useShowDashboardPreviews'; +import { reportInteraction } from '@grafana/runtime/src'; const hasChecked = (section: DashboardSection) => { return section.checked || section.items.some((item) => item.checked); }; +export const reportDashboardListViewed = ( + dashboardListType: 'manage_dashboards' | 'dashboard_search', + showPreviews: boolean, + previewsEnabled: boolean, + searchLayout: SearchLayout +) => { + const previews = previewsEnabled ? (showPreviews ? 'on' : 'off') : 'feature_disabled'; + reportInteraction(`${dashboardListType}_viewed`, { + previews, + layout: searchLayout, + }); +}; + export const useManageDashboards = ( query: DashboardQuery, state: Partial = {}, @@ -21,6 +36,11 @@ export const useManageDashboards = ( ...state, }); + const { showPreviews, onShowPreviewsChange, previewFeatureEnabled } = useShowDashboardPreviews(); + useEffect(() => { + reportDashboardListViewed('manage_dashboards', showPreviews, previewFeatureEnabled, query.layout); + }, [showPreviews, previewFeatureEnabled, query.layout]); + const { state: { results, loading, initialLoading, allChecked }, onToggleSection, @@ -73,5 +93,7 @@ export const useManageDashboards = ( onDeleteItems, onMoveItems, noFolders, + showPreviews, + onShowPreviewsChange, }; }; diff --git a/public/app/features/search/hooks/useShowDashboardPreviews.ts b/public/app/features/search/hooks/useShowDashboardPreviews.ts new file mode 100644 index 00000000000..2a65e36b0a2 --- /dev/null +++ b/public/app/features/search/hooks/useShowDashboardPreviews.ts @@ -0,0 +1,13 @@ +import { PREVIEWS_LOCAL_STORAGE_KEY } from '../constants'; +import { config } from '@grafana/runtime/src'; +import { useLocalStorage } from 'react-use'; + +export const useShowDashboardPreviews = () => { + const previewFeatureEnabled = Boolean(config.featureToggles.dashboardPreviews); + const [showPreviews, setShowPreviews] = useLocalStorage(PREVIEWS_LOCAL_STORAGE_KEY, previewFeatureEnabled); + const onShowPreviewsChange = (showPreviews: boolean) => { + setShowPreviews(showPreviews); + }; + + return { showPreviews: Boolean(showPreviews && previewFeatureEnabled), previewFeatureEnabled, onShowPreviewsChange }; +};