From 6c76aa71e8f7464afd48f58b0246d16277b94d02 Mon Sep 17 00:00:00 2001 From: Artur Wierzbicki Date: Wed, 16 Feb 2022 21:49:50 +0400 Subject: [PATCH] Previews: capability check (#44601) * add SQL migrations * dashboard previews from sql: poc * added todos * refactor: use the same enums where possible * use useEffect, always return json * added todo * refactor + delete files after use * refactor + fix manual thumbnail upload * refactor: move all interactions with sqlStore to thumbnail repo * refactor: remove file operations in thumb crawler/service * refactor: fix dashboard_thumbs sql store * refactor: extracted thumbnail fetching/updating to a hook * refactor: store thumbnails in redux store * refactor: store thumbnails in redux store * refactor: private'd repo methods * removed redux storage, saving images as blobs * allow for configurable rendering timeouts * added 1) query for dashboards with stale thumbnails, 2) command for marking thumbnails as stale * use sql-based queue in crawler * ui for marking thumbnails as stale * replaced `stale` boolean prop with `state` enum * introduce rendering session * compilation errors * fix crawler stop button * rename thumbnail state frozen to locked * #44449: fix merge conflicts * #44449: remove thumb methods from `Store` interface * #44449: clean filepath, defer file closing * #44449: fix rendering.Theme cyclic import * #44449: linting * #44449: linting * #44449: mutex'd crawlerStatus access * #44449: added integration tests for `sqlstore.dashboard_thumbs` * #44449: added comments to explain the `ThumbnailState` enum * #44449: use os.ReadFile rather then os.Open * #44449: always enable dashboardPreviews feature during integration tests * #44449: add /previews/system-requirements API * #44449: remove sleep time, adjust number of threads * #44449: review fix: add `orgId` to `DashboardThumbnailMeta` * #44449: review fix: automatic parsing of thumbnailState * #44449: update returned json * #44449: UI changes - dashboard previews sytem req check * #44449: lint fixes * #44449: fix tests * #44449: typo * #44449: fix getSystemRequirements API: return 200 even if we plugin version is invalid * #44449: fix getSystemRequirements API: don't return SemverConstraint on error * #44449: fix getSystemRequirements API * #44449: fix previews sytem requirements text * #44449: add `doThumbnailsExist` to repo * #44449: remove redux api * #44449: add missing model * #44449: implement frontedsettings-driven capability check * #44449: simplify * #44449: revert test changes * #44449: add dummy setup settings * #44449: implicit typing over `FC` * #44449: refactor conditionals * #44449: replace `getText` with a react component * #44449: fix component interface * #44449: add onRemove to `PreviewsSystemRequirements` alert * #44449: add bottom/top margin to previewSystemRequirements modal * #44449: merge conflict fix * #44449: remove console.log Co-authored-by: Ryan McKinley Co-authored-by: Alexander Emelin --- packages/grafana-runtime/src/config.ts | 7 ++ .../grafana-ui/src/components/Alert/Alert.tsx | 25 ++++- pkg/api/frontendsettings.go | 4 + pkg/models/dashboard_thumbs.go | 4 + pkg/services/sqlstore/dashboard_thumbs.go | 14 +++ .../sqlstore/dashboard_thumbs_test.go | 17 ++++ pkg/services/thumbs/dummy.go | 10 ++ pkg/services/thumbs/models.go | 11 +++ pkg/services/thumbs/repo.go | 12 ++- pkg/services/thumbs/service.go | 41 ++++++++ .../features/search/components/ActionRow.tsx | 6 +- .../search/components/DashboardSearch.tsx | 10 +- .../search/components/ManageDashboards.tsx | 4 +- .../components/PreviewsSystemRequirements.tsx | 93 +++++++++++++++++++ .../components/SearchResultsFilter.test.tsx | 2 +- .../search/components/SearchResultsFilter.tsx | 77 ++++++++------- .../search/hooks/useDashboardSearch.ts | 4 +- .../search/hooks/useManageDashboards.ts | 4 +- .../search/hooks/useShowDashboardPreviews.ts | 5 +- 19 files changed, 298 insertions(+), 52 deletions(-) create mode 100644 public/app/features/search/components/PreviewsSystemRequirements.tsx diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index 4cf1813b984..99c63fd66a3 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -66,6 +66,13 @@ export class GrafanaBootConfig implements GrafanaConfig { featureToggles: FeatureToggles = {}; licenseInfo: LicenseInfo = {} as LicenseInfo; rendererAvailable = false; + dashboardPreviews: { + systemRequirements: { + met: boolean; + requiredImageRendererPluginVersion: string; + }; + thumbnailsExist: boolean; + } = { systemRequirements: { met: false, requiredImageRendererPluginVersion: '' }, thumbnailsExist: false }; rendererVersion = ''; http2Enabled = false; dateFormats?: SystemDateFormatSettings; diff --git a/packages/grafana-ui/src/components/Alert/Alert.tsx b/packages/grafana-ui/src/components/Alert/Alert.tsx index b25ecc025f8..c6e73b7fe2b 100644 --- a/packages/grafana-ui/src/components/Alert/Alert.tsx +++ b/packages/grafana-ui/src/components/Alert/Alert.tsx @@ -19,6 +19,7 @@ export interface Props extends HTMLAttributes { elevated?: boolean; buttonContent?: React.ReactNode | string; bottomSpacing?: number; + topSpacing?: number; } function getIconFromSeverity(severity: AlertVariant): string { @@ -37,11 +38,22 @@ function getIconFromSeverity(severity: AlertVariant): string { export const Alert = React.forwardRef( ( - { title, onRemove, children, buttonContent, elevated, bottomSpacing, className, severity = 'error', ...restProps }, + { + title, + onRemove, + children, + buttonContent, + elevated, + bottomSpacing, + topSpacing, + className, + severity = 'error', + ...restProps + }, ref ) => { const theme = useTheme2(); - const styles = getStyles(theme, severity, elevated, bottomSpacing); + const styles = getStyles(theme, severity, elevated, bottomSpacing, topSpacing); return (
( Alert.displayName = 'Alert'; -const getStyles = (theme: GrafanaTheme2, severity: AlertVariant, elevated?: boolean, bottomSpacing?: number) => { +const getStyles = ( + theme: GrafanaTheme2, + severity: AlertVariant, + elevated?: boolean, + bottomSpacing?: number, + topSpacing?: number +) => { const color = theme.colors[severity]; const borderRadius = theme.shape.borderRadius(); @@ -92,6 +110,7 @@ const getStyles = (theme: GrafanaTheme2, severity: AlertVariant, elevated?: bool background: ${theme.colors.background.secondary}; box-shadow: ${elevated ? theme.shadows.z3 : theme.shadows.z1}; margin-bottom: ${theme.spacing(bottomSpacing ?? 2)}; + margin-top: ${theme.spacing(topSpacing ?? 0)}; &:before { content: ''; diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index f59fe8ffbeb..5416cde6b24 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -275,6 +275,10 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i "unifiedAlertingEnabled": hs.Cfg.UnifiedAlerting.Enabled, } + if hs.ThumbService != nil { + jsonObj["dashboardPreviews"] = hs.ThumbService.GetDashboardPreviewsSetupSettings(c) + } + if hs.Cfg.GeomapDefaultBaseLayerConfig != nil { jsonObj["geomapDefaultBaseLayerConfig"] = hs.Cfg.GeomapDefaultBaseLayerConfig } diff --git a/pkg/models/dashboard_thumbs.go b/pkg/models/dashboard_thumbs.go index 954a07b1a42..0935d0c16e0 100644 --- a/pkg/models/dashboard_thumbs.go +++ b/pkg/models/dashboard_thumbs.go @@ -117,6 +117,10 @@ type DashboardWithStaleThumbnail struct { Slug string } +type FindDashboardThumbnailCountCommand struct { + Result int64 +} + type FindDashboardsWithStaleThumbnailsCommand struct { IncludeManuallyUploadedThumbnails bool Theme Theme diff --git a/pkg/services/sqlstore/dashboard_thumbs.go b/pkg/services/sqlstore/dashboard_thumbs.go index 6b819ca46a2..907d7928991 100644 --- a/pkg/services/sqlstore/dashboard_thumbs.go +++ b/pkg/services/sqlstore/dashboard_thumbs.go @@ -81,6 +81,20 @@ func (ss *SQLStore) UpdateThumbnailState(ctx context.Context, cmd *models.Update return err } +func (ss *SQLStore) FindThumbnailCount(ctx context.Context, cmd *models.FindDashboardThumbnailCountCommand) (int64, error) { + err := ss.WithDbSession(ctx, func(sess *DBSession) error { + count, err := sess.Count(&models.DashboardThumbnail{}) + if err != nil { + return err + } + + cmd.Result = count + return nil + }) + + return cmd.Result, err +} + func (ss *SQLStore) FindDashboardsWithStaleThumbnails(ctx context.Context, cmd *models.FindDashboardsWithStaleThumbnailsCommand) ([]*models.DashboardWithStaleThumbnail, error) { err := ss.WithDbSession(ctx, func(sess *DBSession) error { sess.Table("dashboard") diff --git a/pkg/services/sqlstore/dashboard_thumbs_test.go b/pkg/services/sqlstore/dashboard_thumbs_test.go index 5cba1949f64..4fc9395cd57 100644 --- a/pkg/services/sqlstore/dashboard_thumbs_test.go +++ b/pkg/services/sqlstore/dashboard_thumbs_test.go @@ -185,6 +185,23 @@ func TestSqlStorage(t *testing.T) { require.Len(t, res, 1) require.Equal(t, dash.Id, res[0].Id) }) + + t.Run("Should count all dashboard thumbnails", func(t *testing.T) { + setup() + dash := insertTestDashboard(t, sqlStore, "test dash 23", 1, savedFolder.Id, false, "prod", "webapp") + upsertTestDashboardThumbnail(t, sqlStore, dash.Uid, dash.OrgId, 1) + dash2 := insertTestDashboard(t, sqlStore, "test dash 23", 2, savedFolder.Id, false, "prod", "webapp") + upsertTestDashboardThumbnail(t, sqlStore, dash2.Uid, dash2.OrgId, 1) + + updateTestDashboard(t, sqlStore, dash, map[string]interface{}{ + "tags": "different-tag", + }) + + cmd := models.FindDashboardThumbnailCountCommand{} + res, err := sqlStore.FindThumbnailCount(context.Background(), &cmd) + require.NoError(t, err) + require.Equal(t, res, int64(2)) + }) } func getThumbnail(t *testing.T, sqlStore *SQLStore, dashboardUID string, orgId int64) *models.DashboardThumbnail { diff --git a/pkg/services/thumbs/dummy.go b/pkg/services/thumbs/dummy.go index 45fd30f3b2a..6c984d412ad 100644 --- a/pkg/services/thumbs/dummy.go +++ b/pkg/services/thumbs/dummy.go @@ -26,6 +26,16 @@ func (ds *dummyService) Enabled() bool { return false } +func (ds *dummyService) GetDashboardPreviewsSetupSettings(c *models.ReqContext) dashboardPreviewsSetupConfig { + return dashboardPreviewsSetupConfig{ + SystemRequirements: dashboardPreviewsSystemRequirements{ + Met: false, + RequiredImageRendererPluginVersion: "", + }, + ThumbnailsExist: false, + } +} + func (ds *dummyService) StartCrawler(c *models.ReqContext) response.Response { result := make(map[string]string) result["error"] = "Not enabled" diff --git a/pkg/services/thumbs/models.go b/pkg/services/thumbs/models.go index 38b2448b302..a7c128d195c 100644 --- a/pkg/services/thumbs/models.go +++ b/pkg/services/thumbs/models.go @@ -53,6 +53,16 @@ type crawlStatus struct { Last time.Time `json:"last,omitempty"` } +type dashboardPreviewsSystemRequirements struct { + Met bool `json:"met"` + RequiredImageRendererPluginVersion string `json:"requiredImageRendererPluginVersion"` +} + +type dashboardPreviewsSetupConfig struct { + SystemRequirements dashboardPreviewsSystemRequirements `json:"systemRequirements"` + ThumbnailsExist bool `json:"thumbnailsExist"` +} + type dashRenderer interface { // Run Assumes you have already authenticated as admin. @@ -69,6 +79,7 @@ type dashRenderer interface { type thumbnailRepo interface { updateThumbnailState(ctx context.Context, state models.ThumbnailState, meta models.DashboardThumbnailMeta) error + doThumbnailsExist(ctx context.Context) (bool, error) saveFromFile(ctx context.Context, filePath string, meta models.DashboardThumbnailMeta, dashboardVersion int) (int64, error) saveFromBytes(ctx context.Context, bytes []byte, mimeType string, meta models.DashboardThumbnailMeta, dashboardVersion int) (int64, error) getThumbnail(ctx context.Context, meta models.DashboardThumbnailMeta) (*models.DashboardThumbnail, error) diff --git a/pkg/services/thumbs/repo.go b/pkg/services/thumbs/repo.go index 7ce5d0aa006..71f7a7459a9 100644 --- a/pkg/services/thumbs/repo.go +++ b/pkg/services/thumbs/repo.go @@ -63,7 +63,7 @@ func (r *sqlThumbnailRepository) saveFromBytes(ctx context.Context, content []by _, err := r.store.SaveThumbnail(ctx, cmd) if err != nil { - r.log.Error("error saving to the db", "dashboardUID", meta.DashboardUID, "err", err) + r.log.Error("Error saving to the db", "dashboardUID", meta.DashboardUID, "err", err) return 0, err } @@ -91,3 +91,13 @@ func (r *sqlThumbnailRepository) findDashboardsWithStaleThumbnails(ctx context.C Kind: kind, }) } + +func (r *sqlThumbnailRepository) doThumbnailsExist(ctx context.Context) (bool, error) { + cmd := &models.FindDashboardThumbnailCountCommand{} + count, err := r.store.FindThumbnailCount(ctx, cmd) + if err != nil { + r.log.Error("Error finding thumbnails", "err", err) + return false, err + } + return count > 0, err +} diff --git a/pkg/services/thumbs/service.go b/pkg/services/thumbs/service.go index 2de7249389c..f5939a3f7fc 100644 --- a/pkg/services/thumbs/service.go +++ b/pkg/services/thumbs/service.go @@ -27,6 +27,7 @@ type Service interface { Run(ctx context.Context) error Enabled() bool GetImage(c *models.ReqContext) + GetDashboardPreviewsSetupSettings(c *models.ReqContext) dashboardPreviewsSetupConfig // from dashboard page SetImage(c *models.ReqContext) // form post @@ -41,6 +42,7 @@ type Service interface { type thumbService struct { scheduleOptions crawlerScheduleOptions renderer dashRenderer + renderingService rendering.Service thumbnailRepo thumbnailRepo lockService *serverlock.ServerLockService features featuremgmt.FeatureToggles @@ -71,6 +73,7 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, lockS OrgRole: models.ROLE_ADMIN, } return &thumbService{ + renderingService: renderService, renderer: newSimpleCrawler(renderService, gl, thumbnailRepo), thumbnailRepo: thumbnailRepo, features: features, @@ -196,6 +199,44 @@ func (hs *thumbService) GetImage(c *models.ReqContext) { } } +func (hs *thumbService) GetDashboardPreviewsSetupSettings(c *models.ReqContext) dashboardPreviewsSetupConfig { + systemRequirements := hs.getSystemRequirements() + thumbnailsExist, err := hs.thumbnailRepo.doThumbnailsExist(c.Req.Context()) + + if err != nil { + return dashboardPreviewsSetupConfig{ + SystemRequirements: systemRequirements, + ThumbnailsExist: false, + } + } + + return dashboardPreviewsSetupConfig{ + SystemRequirements: systemRequirements, + ThumbnailsExist: thumbnailsExist, + } +} + +func (hs *thumbService) getSystemRequirements() dashboardPreviewsSystemRequirements { + res, err := hs.renderingService.HasCapability(rendering.ScalingDownImages) + if err != nil { + hs.log.Error("Error when verifying dashboard previews system requirements thumbnail", "err", err.Error()) + return dashboardPreviewsSystemRequirements{ + Met: false, + } + } + + if !res.IsSupported { + return dashboardPreviewsSystemRequirements{ + Met: false, + RequiredImageRendererPluginVersion: res.SemverConstraint, + } + } + + return dashboardPreviewsSystemRequirements{ + Met: true, + } +} + // Hack for now -- lets you upload images explicitly func (hs *thumbService) SetImage(c *models.ReqContext) { req := hs.parseImageReq(c, false) diff --git a/public/app/features/search/components/ActionRow.tsx b/public/app/features/search/components/ActionRow.tsx index 54b46fcdaa0..dc6d0af3294 100644 --- a/public/app/features/search/components/ActionRow.tsx +++ b/public/app/features/search/components/ActionRow.tsx @@ -17,7 +17,7 @@ const searchSrv = new SearchSrv(); interface Props { onLayoutChange: (layout: SearchLayout) => void; - onShowPreviewsChange: (event: ChangeEvent) => void; + setShowPreviews: (newValue: boolean) => void; onSortChange: (value: SelectableValue) => void; onStarredFilterChange?: (event: FormEvent) => void; onTagFilterChange: (tags: string[]) => void; @@ -29,7 +29,7 @@ interface Props { export const ActionRow: FC = ({ onLayoutChange, - onShowPreviewsChange, + setShowPreviews, onSortChange, onStarredFilterChange = () => {}, onTagFilterChange, @@ -56,7 +56,7 @@ export const ActionRow: FC = ({ label="Show previews" showLabel value={showPreviews} - onChange={onShowPreviewsChange} + onChange={(ev: ChangeEvent) => setShowPreviews(ev.target.checked)} transparent /> )} diff --git a/public/app/features/search/components/DashboardSearch.tsx b/public/app/features/search/components/DashboardSearch.tsx index 4287b45aa0e..1864459e715 100644 --- a/public/app/features/search/components/DashboardSearch.tsx +++ b/public/app/features/search/components/DashboardSearch.tsx @@ -7,6 +7,7 @@ import { useDashboardSearch } from '../hooks/useDashboardSearch'; import { SearchField } from './SearchField'; import { SearchResults } from './SearchResults'; import { ActionRow } from './ActionRow'; +import { PreviewsSystemRequirements } from './PreviewsSystemRequirements'; export interface Props { onCloseSearch: () => void; @@ -14,7 +15,7 @@ export interface Props { export const DashboardSearch: FC = memo(({ onCloseSearch }) => { const { query, onQueryChange, onTagFilterChange, onTagAdd, onSortChange, onLayoutChange } = useSearchQuery({}); - const { results, loading, onToggleSection, onKeyDown, showPreviews, onShowPreviewsChange } = useDashboardSearch( + const { results, loading, onToggleSection, onKeyDown, showPreviews, setShowPreviews } = useDashboardSearch( query, onCloseSearch ); @@ -34,13 +35,18 @@ export const DashboardSearch: FC = memo(({ onCloseSearch }) => { onShowPreviewsChange(ev.target.checked), + setShowPreviews, onSortChange, onTagFilterChange, query, showPreviews, }} /> + setShowPreviews(false)} + /> = memo(({ folder }) => { onMoveItems, noFolders, showPreviews, - onShowPreviewsChange, + setShowPreviews, } = useManageDashboards(query, {}, folder); const onMoveTo = () => { @@ -108,7 +108,7 @@ export const ManageDashboards: FC = memo(({ folder }) => { canMove={hasEditPermissionInFolders && canMove} deleteItem={onItemDelete} moveTo={onMoveTo} - onShowPreviewsChange={(ev) => onShowPreviewsChange(ev.target.checked)} + setShowPreviews={setShowPreviews} onToggleAllChecked={onToggleAllChecked} onStarredFilterChange={onStarredFilterChange} onSortChange={onSortChange} diff --git a/public/app/features/search/components/PreviewsSystemRequirements.tsx b/public/app/features/search/components/PreviewsSystemRequirements.tsx new file mode 100644 index 00000000000..0e023c4e138 --- /dev/null +++ b/public/app/features/search/components/PreviewsSystemRequirements.tsx @@ -0,0 +1,93 @@ +import { Alert, useStyles2 } from '@grafana/ui'; +import React from 'react'; +import { config } from '@grafana/runtime/src'; +import { css } from '@emotion/css'; + +export interface Props { + showPreviews?: boolean; + /** On click handler for alert button, mostly used for dismissing the alert */ + onRemove?: (event: React.MouseEvent) => void; + topSpacing?: number; + bottomSpacing?: number; +} + +const MessageLink = ({ text }: { text: string }) => ( + + {text} + +); + +const Message = ({ requiredImageRendererPluginVersion }: { requiredImageRendererPluginVersion?: string }) => { + if (requiredImageRendererPluginVersion) { + return ( + <> + You must update the to version{' '} + {requiredImageRendererPluginVersion} to enable dashboard previews. Please contact your Grafana administrator to + update the plugin. + + ); + } + + return ( + <> + You must install the to enable dashboard previews. Please + contact your Grafana administrator to install the plugin. + + ); +}; + +export const PreviewsSystemRequirements = ({ showPreviews, onRemove, topSpacing, bottomSpacing }: Props) => { + const styles = useStyles2(getStyles); + + const previewsEnabled = config.featureToggles.dashboardPreviews; + const rendererAvailable = config.rendererAvailable; + + const { + systemRequirements: { met: systemRequirementsMet, requiredImageRendererPluginVersion }, + thumbnailsExist, + } = config.dashboardPreviews; + + const arePreviewsEnabled = previewsEnabled && showPreviews; + const areRequirementsMet = (rendererAvailable && systemRequirementsMet) || thumbnailsExist; + const shouldDisplayRequirements = arePreviewsEnabled && !areRequirementsMet; + + const title = requiredImageRendererPluginVersion + ? 'Image renderer plugin needs to be updated' + : 'Image renderer plugin not installed'; + + return ( + <> + {shouldDisplayRequirements && ( +
+ + + +
+ )} + + ); +}; + +const getStyles = () => { + return { + wrapper: css` + display: flex; + justify-content: center; + `, + alert: css` + max-width: 800px; + `, + }; +}; diff --git a/public/app/features/search/components/SearchResultsFilter.test.tsx b/public/app/features/search/components/SearchResultsFilter.test.tsx index 798636be17d..ff587ac1ec7 100644 --- a/public/app/features/search/components/SearchResultsFilter.test.tsx +++ b/public/app/features/search/components/SearchResultsFilter.test.tsx @@ -37,7 +37,7 @@ const setup = (propOverrides?: Partial) => { onLayoutChange: noop, query: searchQuery, onSortChange: noop, - onShowPreviewsChange: noop, + setShowPreviews: noop, editable: true, }; diff --git a/public/app/features/search/components/SearchResultsFilter.tsx b/public/app/features/search/components/SearchResultsFilter.tsx index 70b7cd33e74..7a8b43b9ba7 100644 --- a/public/app/features/search/components/SearchResultsFilter.tsx +++ b/public/app/features/search/components/SearchResultsFilter.tsx @@ -1,9 +1,10 @@ -import React, { FC, ChangeEvent, FormEvent } from 'react'; +import React, { FC, FormEvent } from 'react'; import { css } from '@emotion/css'; import { Button, Checkbox, stylesFactory, useTheme, HorizontalGroup } from '@grafana/ui'; import { GrafanaTheme, SelectableValue } from '@grafana/data'; import { DashboardQuery, SearchLayout } from '../types'; import { ActionRow } from './ActionRow'; +import { PreviewsSystemRequirements } from './PreviewsSystemRequirements'; export interface Props { allChecked?: boolean; @@ -13,7 +14,7 @@ export interface Props { hideLayout?: boolean; moveTo: () => void; onLayoutChange: (layout: SearchLayout) => void; - onShowPreviewsChange: (event: ChangeEvent) => void; + setShowPreviews: (newValue: boolean) => void; onSortChange: (value: SelectableValue) => void; onStarredFilterChange: (event: FormEvent) => void; onTagFilterChange: (tags: string[]) => void; @@ -31,7 +32,7 @@ export const SearchResultsFilter: FC = ({ hideLayout, moveTo, onLayoutChange, - onShowPreviewsChange, + setShowPreviews, onSortChange, onStarredFilterChange, onTagFilterChange, @@ -46,35 +47,43 @@ export const SearchResultsFilter: FC = ({ return (
- {editable && ( -
- -
- )} - {showActions ? ( - - - - - ) : ( - - )} +
+ {editable && ( +
+ +
+ )} + {showActions ? ( + + + + + ) : ( + + )} +
+ setShowPreviews(false)} + />
); }; @@ -83,6 +92,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { const { sm, md } = theme.spacing; return { wrapper: css` + display: flex; + flex-direction: column; + `, + rowWrapper: css` height: ${theme.height.md}px; display: flex; justify-content: flex-start; diff --git a/public/app/features/search/hooks/useDashboardSearch.ts b/public/app/features/search/hooks/useDashboardSearch.ts index 6fbcb220ede..c3b7434f002 100644 --- a/public/app/features/search/hooks/useDashboardSearch.ts +++ b/public/app/features/search/hooks/useDashboardSearch.ts @@ -12,7 +12,7 @@ import { useDebounce } from 'react-use'; export const useDashboardSearch = (query: DashboardQuery, onCloseSearch: () => void) => { const reducer = useReducer(searchReducer, dashboardsSearchState); - const { showPreviews, onShowPreviewsChange, previewFeatureEnabled } = useShowDashboardPreviews(); + const { showPreviews, setShowPreviews, previewFeatureEnabled } = useShowDashboardPreviews(); const { state: { results, loading }, onToggleSection, @@ -72,6 +72,6 @@ export const useDashboardSearch = (query: DashboardQuery, onCloseSearch: () => v onToggleSection, onKeyDown, showPreviews, - onShowPreviewsChange, + setShowPreviews, }; }; diff --git a/public/app/features/search/hooks/useManageDashboards.ts b/public/app/features/search/hooks/useManageDashboards.ts index 12a5e61422c..caa971f5b8f 100644 --- a/public/app/features/search/hooks/useManageDashboards.ts +++ b/public/app/features/search/hooks/useManageDashboards.ts @@ -47,7 +47,7 @@ export const useManageDashboards = ( ...state, }); - const { showPreviews, onShowPreviewsChange, previewFeatureEnabled } = useShowDashboardPreviews(); + const { showPreviews, setShowPreviews, previewFeatureEnabled } = useShowDashboardPreviews(); useDebounce( () => { reportDashboardListViewed('manage_dashboards', showPreviews, previewFeatureEnabled, { @@ -123,6 +123,6 @@ export const useManageDashboards = ( onMoveItems, noFolders, showPreviews, - onShowPreviewsChange, + setShowPreviews, }; }; diff --git a/public/app/features/search/hooks/useShowDashboardPreviews.ts b/public/app/features/search/hooks/useShowDashboardPreviews.ts index 2a65e36b0a2..e15780fff6d 100644 --- a/public/app/features/search/hooks/useShowDashboardPreviews.ts +++ b/public/app/features/search/hooks/useShowDashboardPreviews.ts @@ -5,9 +5,6 @@ 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 }; + return { showPreviews: Boolean(showPreviews && previewFeatureEnabled), previewFeatureEnabled, setShowPreviews }; };