diff --git a/.betterer.results b/.betterer.results index 73b50136af9..df9a3163107 100644 --- a/.betterer.results +++ b/.betterer.results @@ -5025,11 +5025,11 @@ exports[`better eslint`] = { [264, 15, 56, "Do not use any type assertions.", "2674630592"], [266, 13, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/dashboard/containers/DashboardPage.tsx:707193595": [ - [107, 36, 40, "Do not use any type assertions.", "2547843745"], - [107, 73, 3, "Unexpected any. Specify a different type.", "193409811"], - [142, 32, 40, "Do not use any type assertions.", "2547843745"], - [142, 69, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/features/dashboard/containers/DashboardPage.tsx:3173260528": [ + [108, 36, 40, "Do not use any type assertions.", "2547843745"], + [108, 73, 3, "Unexpected any. Specify a different type.", "193409811"], + [144, 32, 40, "Do not use any type assertions.", "2547843745"], + [144, 69, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/dashboard/containers/SoloPanelPage.test.tsx:1655234814": [ [26, 29, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -5366,8 +5366,8 @@ exports[`better eslint`] = { [143, 17, 41, "Do not use any type assertions.", "1363816804"], [145, 11, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/dashboard/state/initDashboard.ts:4291397377": [ - [215, 71, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/features/dashboard/state/initDashboard.ts:1143357497": [ + [216, 84, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/dashboard/state/reducers.ts:2272523560": [ [64, 9, 3, "Unexpected any. Specify a different type.", "193409811"] @@ -6420,8 +6420,8 @@ exports[`better eslint`] = { [32, 25, 3, "Unexpected any. Specify a different type.", "193409811"], [61, 22, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/plugins/admin/components/PluginDetailsBody.tsx:3199506957": [ - [45, 35, 25, "Do not use any type assertions.", "3298329852"] + "public/app/features/plugins/admin/components/PluginDetailsBody.tsx:2575001007": [ + [46, 35, 25, "Do not use any type assertions.", "3298329852"] ], "public/app/features/plugins/admin/components/PluginDetailsHeader.tsx:4233259": [ [64, 48, 3, "Unexpected any. Specify a different type.", "193409811"] @@ -6462,7 +6462,7 @@ exports[`better eslint`] = { "public/app/features/plugins/admin/pages/PluginDetails.test.tsx:186575126": [ [85, 15, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/plugins/admin/pages/PluginDetails.tsx:4160094062": [ + "public/app/features/plugins/admin/pages/PluginDetails.tsx:2558474094": [ [43, 18, 32, "Do not use any type assertions.", "4000149916"], [87, 24, 20, "Do not use any type assertions.", "2850977225"] ], @@ -6478,8 +6478,8 @@ exports[`better eslint`] = { "public/app/features/plugins/admin/state/selectors.ts:345773501": [ [62, 23, 27, "Do not use any type assertions.", "1285719276"] ], - "public/app/features/plugins/admin/types.ts:593250396": [ - [236, 10, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/features/plugins/admin/types.ts:1638901340": [ + [238, 10, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/plugins/built_in_plugins.ts:2973583336": [ [93, 22, 3, "Unexpected any. Specify a different type.", "193409811"] @@ -6839,13 +6839,13 @@ exports[`better eslint`] = { [62, 12, 21, "Do not use any type assertions.", "976337522"], [63, 14, 26, "Do not use any type assertions.", "2909521803"] ], - "public/app/features/search/page/components/columns.tsx:282017932": [ - [33, 20, 70, "Do not use any type assertions.", "512413936"], - [33, 21, 13, "Do not use any type assertions.", "3933930693"], - [33, 31, 3, "Unexpected any. Specify a different type.", "193409811"], - [48, 32, 21, "Do not use any type assertions.", "3454251755"], - [48, 50, 3, "Unexpected any. Specify a different type.", "193409811"], - [127, 15, 56, "Do not use any type assertions.", "1375039711"] + "public/app/features/search/page/components/columns.tsx:2440349722": [ + [34, 20, 70, "Do not use any type assertions.", "512413936"], + [34, 21, 13, "Do not use any type assertions.", "3933930693"], + [34, 31, 3, "Unexpected any. Specify a different type.", "193409811"], + [49, 32, 21, "Do not use any type assertions.", "3454251755"], + [49, 50, 3, "Unexpected any. Specify a different type.", "193409811"], + [145, 15, 56, "Do not use any type assertions.", "1375039711"] ], "public/app/features/search/reducers/dashboardSearch.test.ts:1813679195": [ [5, 66, 17, "Do not use any type assertions.", "3518965757"], diff --git a/pkg/services/searchV2/bluge.go b/pkg/services/searchV2/bluge.go index 8a74b2dbf13..79f7d52ea4a 100644 --- a/pkg/services/searchV2/bluge.go +++ b/pkg/services/searchV2/bluge.go @@ -394,6 +394,12 @@ func doSearchQuery( hasConstraints = true } + // Panel type + if q.PanelType != "" { + fullQuery.AddMust(bluge.NewTermQuery(q.PanelType).SetField(documentFieldPanelType)) + hasConstraints = true + } + // Datasource if q.Datasource != "" { fullQuery.AddMust(bluge.NewTermQuery(q.Datasource).SetField(documentFieldDSUID)) diff --git a/pkg/services/searchV2/types.go b/pkg/services/searchV2/types.go index 9be6e5737a5..17dcbb54840 100644 --- a/pkg/services/searchV2/types.go +++ b/pkg/services/searchV2/types.go @@ -20,6 +20,7 @@ type DashboardQuery struct { Datasource string `json:"ds_uid,omitempty"` // "datasource" collides with the JSON value at the same leel :() Tags []string `json:"tags,omitempty"` Kind []string `json:"kind,omitempty"` + PanelType string `json:"panel_type,omitempty"` UIDs []string `json:"uid,omitempty"` Explain bool `json:"explain,omitempty"` // adds details on why document matched Facet []FacetField `json:"facet,omitempty"` diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 270f624efe9..d32eb5c1817 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -46,6 +46,7 @@ export type DashboardPageRouteSearchParams = { editPanel?: string; viewPanel?: string; editview?: string; + panelType?: string; inspect?: string; from?: string; to?: string; @@ -129,6 +130,7 @@ export class UnthemedDashboardPage extends PureComponent { urlUid: match.params.uid, urlType: match.params.type, urlFolderId: queryParams.folderId, + panelType: queryParams.panelType, routeName: this.props.route.routeName, fixUrl: !isPublic, accessToken: match.params.accessToken, diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts index 027f427956a..6c723db030b 100644 --- a/public/app/features/dashboard/state/initDashboard.ts +++ b/public/app/features/dashboard/state/initDashboard.ts @@ -24,7 +24,8 @@ export interface InitDashboardArgs { urlUid?: string; urlSlug?: string; urlType?: string; - urlFolderId?: string | null; + urlFolderId?: string; + panelType?: string; accessToken?: string; routeName?: string; fixUrl: boolean; @@ -84,7 +85,7 @@ async function fetchDashboard( return dashDTO; } case DashboardRoutes.New: { - return getNewDashboardModelData(args.urlFolderId); + return getNewDashboardModelData(args.urlFolderId, args.panelType); } default: throw { message: 'Unknown route ' + args.routeName }; @@ -213,7 +214,7 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult { }; } -export function getNewDashboardModelData(urlFolderId?: string | null): any { +export function getNewDashboardModelData(urlFolderId?: string, panelType?: string): any { const data = { meta: { canStar: false, @@ -226,7 +227,7 @@ export function getNewDashboardModelData(urlFolderId?: string | null): any { title: 'New dashboard', panels: [ { - type: 'add-panel', + type: panelType ?? 'add-panel', gridPos: { x: 0, y: 0, w: 12, h: 9 }, title: 'Panel Title', }, diff --git a/public/app/features/plugins/admin/components/PluginDetailsBody.tsx b/public/app/features/plugins/admin/components/PluginDetailsBody.tsx index f6a2cfde15d..7a9ffffbe98 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsBody.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsBody.tsx @@ -10,6 +10,7 @@ import { CatalogPlugin, PluginTabIds } from '../types'; import { AppConfigCtrlWrapper } from './AppConfigWrapper'; import { PluginDashboards } from './PluginDashboards'; +import { PluginUsage } from './PluginUsage'; type Props = { plugin: CatalogPlugin; @@ -60,6 +61,14 @@ export function PluginDetailsBody({ plugin, queryParams, pageId }: Props): JSX.E } } + if (pageId === PluginTabIds.USAGE && pluginConfig) { + return ( +
+ +
+ ); + } + if (pageId === PluginTabIds.DASHBOARDS && pluginConfig) { return (
@@ -78,6 +87,7 @@ export function PluginDetailsBody({ plugin, queryParams, pageId }: Props): JSX.E export const getStyles = (theme: GrafanaTheme2) => ({ container: css` padding: ${theme.spacing(3, 4)}; + height: 100%; `, readme: css` & img { diff --git a/public/app/features/plugins/admin/components/PluginUsage.tsx b/public/app/features/plugins/admin/components/PluginUsage.tsx new file mode 100644 index 00000000000..1b820083782 --- /dev/null +++ b/public/app/features/plugins/admin/components/PluginUsage.tsx @@ -0,0 +1,90 @@ +import { css } from '@emotion/css'; +import React, { useMemo } from 'react'; +import { useAsync } from 'react-use'; +import AutoSizer from 'react-virtualized-auto-sizer'; +import { of } from 'rxjs'; + +import { GrafanaTheme2, PluginMeta } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { Alert, Spinner, useStyles2 } from '@grafana/ui'; +import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; +import { SearchResultsTable } from 'app/features/search/page/components/SearchResultsTable'; +import { getGrafanaSearcher, SearchQuery } from 'app/features/search/service'; + +type Props = { + plugin: PluginMeta; +}; + +export function PluginUsage({ plugin }: Props) { + const styles = useStyles2(getStyles); + + const searchQuery = useMemo(() => { + return { + query: '*', + panel_type: plugin.id, + kind: ['panel'], + }; + }, [plugin]); + + const results = useAsync(() => { + return getGrafanaSearcher().search(searchQuery); + }, [searchQuery]); + + const found = results.value; + if (found?.totalRows) { + return ( +
+
+ {plugin.name} is used {found.totalRows} times. +
+ + {({ width, height }) => { + return ( + {}} + keyboardEvents={of()} + onTagSelected={() => {}} + /> + ); + }} + +
+ ); + } + + if (results.loading) { + return ; + } + + if (!config.featureToggles.panelTitleSearch) { + return ( + + Plugin usage requires the new search index to find usage across dashboards + + ); + } + + return ( + + ); +} + +export const getStyles = (theme: GrafanaTheme2) => { + return { + wrap: css` + width: 100%; + height: 100%; + `, + info: css` + padding-bottom: 30px; + `, + }; +}; diff --git a/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx b/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx index 40c17a061f7..45a7a4c627b 100644 --- a/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx +++ b/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx @@ -2,6 +2,7 @@ import { useMemo } from 'react'; import { useLocation } from 'react-router-dom'; import { PluginIncludeType, PluginType } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { usePluginConfig } from '../hooks/usePluginConfig'; import { isOrgAdmin } from '../permissions'; @@ -23,7 +24,6 @@ export const usePluginDetailsTabs = (plugin?: CatalogPlugin, defaultTabs: Plugin const canConfigurePlugins = isOrgAdmin(); const tabs: PluginDetailsTab[] = [...defaultTabs]; let defaultTab; - if (isPublished) { tabs.push({ label: PluginTabLabels.VERSIONS, @@ -39,6 +39,15 @@ export const usePluginDetailsTabs = (plugin?: CatalogPlugin, defaultTabs: Plugin return [tabs, defaultTab]; } + if (config.featureToggles.panelTitleSearch && pluginConfig.meta.type === PluginType.panel) { + tabs.push({ + label: PluginTabLabels.USAGE, + icon: 'list-ul', + id: PluginTabIds.USAGE, + href: `${pathname}?page=${PluginTabIds.USAGE}`, + }); + } + if (canConfigurePlugins) { if (pluginConfig.meta.type === PluginType.app) { if (pluginConfig.angularConfigCtrl) { diff --git a/public/app/features/plugins/admin/pages/PluginDetails.tsx b/public/app/features/plugins/admin/pages/PluginDetails.tsx index 246ff2677d5..a46b65702ad 100644 --- a/public/app/features/plugins/admin/pages/PluginDetails.tsx +++ b/public/app/features/plugins/admin/pages/PluginDetails.tsx @@ -114,6 +114,7 @@ export const getStyles = (theme: GrafanaTheme2) => { // Needed due to block formatting context tabContent: css` overflow: auto; + height: 100%; `, }; }; diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts index 53907d3bdb6..e1551490ef1 100644 --- a/public/app/features/plugins/admin/types.ts +++ b/public/app/features/plugins/admin/types.ts @@ -212,6 +212,7 @@ export enum PluginTabLabels { VERSIONS = 'Version history', CONFIG = 'Config', DASHBOARDS = 'Dashboards', + USAGE = 'Usage', } export enum PluginTabIds { @@ -219,6 +220,7 @@ export enum PluginTabIds { VERSIONS = 'version-history', CONFIG = 'config', DASHBOARDS = 'dashboards', + USAGE = 'usage', } export enum RequestStatus { diff --git a/public/app/features/search/page/components/SearchResultsTable.tsx b/public/app/features/search/page/components/SearchResultsTable.tsx index 125fab5c9e8..0732e3f3120 100644 --- a/public/app/features/search/page/components/SearchResultsTable.tsx +++ b/public/app/features/search/page/components/SearchResultsTable.tsx @@ -84,7 +84,8 @@ export const SearchResultsTable = React.memo( clearSelection, styles, onTagSelected, - onDatasourceChange + onDatasourceChange, + response.view?.length >= response.totalRows ); }, [response, width, styles, selection, selectionToggle, clearSelection, onTagSelected, onDatasourceChange]); diff --git a/public/app/features/search/page/components/columns.tsx b/public/app/features/search/page/components/columns.tsx index a1abbeb2e55..5ff28cbb73a 100644 --- a/public/app/features/search/page/components/columns.tsx +++ b/public/app/features/search/page/components/columns.tsx @@ -25,7 +25,8 @@ export const generateColumns = ( clearSelection: () => void, styles: { [key: string]: string }, onTagSelected: (tag: string) => void, - onDatasourceChange?: (datasource?: string) => void + onDatasourceChange?: (datasource?: string) => void, + showingEverything?: boolean ): TableColumn[] => { const columns: TableColumn[] = []; const access = response.view.fields; @@ -125,9 +126,26 @@ export const generateColumns = ( columns.push(makeTypeColumn(access.kind, access.panel_type, width, styles)); availableWidth -= width; + // Show datasources if we have any + if (access.ds_uid && onDatasourceChange) { + width = Math.min(availableWidth / 2.5, DATASOURCE_COLUMN_WIDTH); + columns.push( + makeDataSourceColumn( + access.ds_uid, + width, + styles.typeIcon, + styles.datasourceItem, + styles.invalidDatasourceItem, + onDatasourceChange + ) + ); + availableWidth -= width; + } + + const showTags = !showingEverything || hasValue(response.view.fields.tags); const meta = response.view.dataFrame.meta?.custom as SearchResultMeta; if (meta?.locationInfo && availableWidth > 0) { - width = Math.max(availableWidth / 1.75, 300); + width = showTags ? Math.max(availableWidth / 1.75, 300) : availableWidth; availableWidth -= width; columns.push({ Cell: (p) => { @@ -154,23 +172,7 @@ export const generateColumns = ( }); } - // Show datasources if we have any - if (access.ds_uid && onDatasourceChange) { - width = DATASOURCE_COLUMN_WIDTH; - columns.push( - makeDataSourceColumn( - access.ds_uid, - width, - styles.typeIcon, - styles.datasourceItem, - styles.invalidDatasourceItem, - onDatasourceChange - ) - ); - availableWidth -= width; - } - - if (availableWidth > 0) { + if (availableWidth > 0 && showTags) { columns.push(makeTagsColumn(access.tags, availableWidth, styles.tagList, onTagSelected)); } @@ -209,6 +211,15 @@ function getIconForKind(v: string): IconName { return 'question-circle'; } +function hasValue(f: Field): boolean { + for (let i = 0; i < f.values.length; i++) { + if (f.values.get(i) != null) { + return true; + } + } + return false; +} + function makeDataSourceColumn( field: Field, width: number, diff --git a/public/app/features/search/service/types.ts b/public/app/features/search/service/types.ts index 1b0d79b9d2a..e0a9eaf6aa9 100644 --- a/public/app/features/search/service/types.ts +++ b/public/app/features/search/service/types.ts @@ -13,6 +13,7 @@ export interface SearchQuery { ds_uid?: string; tags?: string[]; kind?: string[]; + panel_type?: string; uid?: string[]; id?: number[]; facet?: FacetField[];