diff --git a/packages/grafana-data/src/utils/OptionsUIBuilders.ts b/packages/grafana-data/src/utils/OptionsUIBuilders.ts index a37cc30a871..f9082e05033 100644 --- a/packages/grafana-data/src/utils/OptionsUIBuilders.ts +++ b/packages/grafana-data/src/utils/OptionsUIBuilders.ts @@ -246,4 +246,14 @@ export class PanelOptionsEditorBuilder extends OptionsUIRegistryBuilde editor: standardEditorsRegistry.get('field-name').editor as any, }); } + + addDashboardPicker( + config: PanelOptionsEditorConfig + ): this { + return this.addCustomEditor({ + ...config, + id: config.path, + editor: standardEditorsRegistry.get('dashboard-uid').editor as any, // added at runtime + }); + } } diff --git a/packages/grafana-ui/src/utils/standardEditors.tsx b/packages/grafana-ui/src/utils/standardEditors.tsx index 4275cd55113..5f094f7fecc 100644 --- a/packages/grafana-ui/src/utils/standardEditors.tsx +++ b/packages/grafana-ui/src/utils/standardEditors.tsx @@ -229,6 +229,8 @@ export const getStandardFieldConfigs = () => { /** * Returns collection of standard option editors definitions + * + * @internal */ export const getStandardOptionEditors = () => { const number: StandardEditorsRegistryItem = { diff --git a/public/app/app.ts b/public/app/app.ts index 6ea299b0da4..58fa46d24b8 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -31,7 +31,7 @@ import { reportPerformance } from './core/services/echo/EchoSrv'; import { PerformanceBackend } from './core/services/echo/backends/PerformanceBackend'; import 'app/routes/GrafanaCtrl'; import 'app/features/all'; -import { getScrollbarWidth, getStandardFieldConfigs, getStandardOptionEditors } from '@grafana/ui'; +import { getScrollbarWidth, getStandardFieldConfigs } from '@grafana/ui'; import { getDefaultVariableAdapters, variableAdapters } from './features/variables/adapters'; import { initDevFeatures } from './dev'; import { getStandardTransformers } from 'app/core/utils/standardTransformers'; @@ -49,6 +49,7 @@ import getDefaultMonacoLanguages from '../lib/monaco-languages'; import { contextSrv } from './core/services/context_srv'; import { GAEchoBackend } from './core/services/echo/backends/analytics/GABackend'; import { RudderstackBackend } from './core/services/echo/backends/analytics/RudderstackBackend'; +import { getAllOptionEditors } from './core/components/editors/registry'; // add move to lodash for backward compatabilty with plugins // @ts-ignore @@ -81,7 +82,7 @@ export class GrafanaApp { initExtensions(); configureStore(); - standardEditorsRegistry.setInit(getStandardOptionEditors); + standardEditorsRegistry.setInit(getAllOptionEditors); standardFieldConfigEditorRegistry.setInit(getStandardFieldConfigs); standardTransformersRegistry.setInit(getStandardTransformers); variableAdapters.setInit(getDefaultVariableAdapters); diff --git a/public/app/core/components/editors/DashboardPicker.tsx b/public/app/core/components/editors/DashboardPicker.tsx new file mode 100644 index 00000000000..f806d44b300 --- /dev/null +++ b/public/app/core/components/editors/DashboardPicker.tsx @@ -0,0 +1,63 @@ +import React, { FC, useCallback, useState } from 'react'; +import debounce from 'debounce-promise'; +import { SelectableValue, StandardEditorProps } from '@grafana/data'; +import { DashboardSearchHit } from 'app/features/search/types'; +import { backendSrv } from 'app/core/services/backend_srv'; +import { AsyncSelect } from '@grafana/ui'; +import { useAsync } from 'react-use'; + +export interface DashboardPickerOptions { + placeholder?: string; + isClearable?: boolean; +} + +const getDashboards = (query = '') => { + return backendSrv.search({ type: 'dash-db', query, limit: 100 }).then((result: DashboardSearchHit[]) => { + return result.map((item: DashboardSearchHit) => ({ + value: item.uid, + label: `${item?.folderTitle ?? 'General'}/${item.title}`, + })); + }); +}; + +/** This will return the item UID */ +export const DashboardPicker: FC> = ({ value, onChange, item }) => { + const [current, setCurrent] = useState>(); + + // This is required because the async select does not match the raw uid value + // We can not use a simple Select because the dashboard search should not return *everything* + useAsync(async () => { + if (!value) { + setCurrent(undefined); + return; + } + const res = await backendSrv.getDashboardByUid(value); + setCurrent({ + value: res.dashboard.uid, + label: `${res.meta?.folderTitle ?? 'General'}/${res.dashboard.title}`, + }); + return undefined; + }, [value]); + + const onPicked = useCallback( + (sel: SelectableValue) => { + onChange(sel?.value); + }, + [onChange] + ); + const debouncedSearch = debounce(getDashboards, 300); + const { placeholder, isClearable } = item?.settings ?? {}; + + return ( + + ); +}; diff --git a/public/app/core/components/Select/DashboardPicker.tsx b/public/app/core/components/editors/DashboardPickerByID.tsx similarity index 69% rename from public/app/core/components/Select/DashboardPicker.tsx rename to public/app/core/components/editors/DashboardPickerByID.tsx index 0803e825c70..33084541da9 100644 --- a/public/app/core/components/Select/DashboardPicker.tsx +++ b/public/app/core/components/editors/DashboardPickerByID.tsx @@ -5,14 +5,19 @@ import { AsyncSelect } from '@grafana/ui'; import { backendSrv } from 'app/core/services/backend_srv'; import { DashboardSearchHit } from 'app/features/search/types'; -export interface DashboardPickerItem extends Pick { +/** + * @deprecated prefer using dashboard uid rather than id + */ +export interface DashboardPickerItem extends SelectableValue { + id: number; + uid: string; value: number; label: string; } -export interface Props { +interface Props { onChange: (dashboard: DashboardPickerItem) => void; - value?: SelectableValue; + value?: DashboardPickerItem; width?: number; isClearable?: boolean; invalid?: boolean; @@ -20,7 +25,7 @@ export interface Props { } const getDashboards = (query = '') => { - return backendSrv.search({ type: 'dash-db', query }).then((result: DashboardSearchHit[]) => { + return backendSrv.search({ type: 'dash-db', query, limit: 100 }).then((result: DashboardSearchHit[]) => { return result.map((item: DashboardSearchHit) => ({ id: item.id, uid: item.uid, @@ -30,7 +35,10 @@ const getDashboards = (query = '') => { }); }; -export const DashboardPicker: FC = ({ onChange, value, width, isClearable = false, invalid, disabled }) => { +/** + * @deprecated prefer using dashboard uid rather than id + */ +export const DashboardPickerByID: FC = ({ onChange, value, width, isClearable = false, invalid, disabled }) => { const debouncedSearch = debounce(getDashboards, 300); return ( diff --git a/public/app/core/components/editors/registry.tsx b/public/app/core/components/editors/registry.tsx new file mode 100644 index 00000000000..8c51fb4438c --- /dev/null +++ b/public/app/core/components/editors/registry.tsx @@ -0,0 +1,16 @@ +import { DashboardPicker, DashboardPickerOptions } from './DashboardPicker'; +import { getStandardOptionEditors } from '@grafana/ui'; +import { StandardEditorsRegistryItem } from '@grafana/data'; + +/** + * Returns collection of standard option editors definitions + */ +export const getAllOptionEditors = () => { + const dashboardPicker: StandardEditorsRegistryItem = { + id: 'dashboard-uid', + name: 'Dashboard', + description: 'Select dashboard', + editor: DashboardPicker as any, + }; + return [...getStandardOptionEditors(), dashboardPicker]; +}; diff --git a/public/app/features/playlist/PlaylistForm.tsx b/public/app/features/playlist/PlaylistForm.tsx index d1591ba43d5..644cad2db40 100644 --- a/public/app/features/playlist/PlaylistForm.tsx +++ b/public/app/features/playlist/PlaylistForm.tsx @@ -4,11 +4,11 @@ import { Button, Field, Form, HorizontalGroup, Input, LinkButton } from '@grafan import { selectors } from '@grafana/e2e-selectors'; import { Playlist } from './types'; -import { DashboardPicker } from '../../core/components/Select/DashboardPicker'; import { TagFilter } from '../../core/components/TagFilter/TagFilter'; import { SearchSrv } from '../../core/services/search_srv'; import { usePlaylistItems } from './usePlaylistItems'; import { PlaylistTable } from './PlaylistTable'; +import { DashboardPickerByID } from 'app/core/components/editors/DashboardPickerByID'; interface PlaylistFormProps { onSubmit: (playlist: Playlist) => void; @@ -52,7 +52,7 @@ export const PlaylistForm: FC = ({ onSubmit, playlist }) => {

Add dashboards

- + diff --git a/public/app/features/playlist/usePlaylistItems.tsx b/public/app/features/playlist/usePlaylistItems.tsx index 7407f88389e..3ab1954d69f 100644 --- a/public/app/features/playlist/usePlaylistItems.tsx +++ b/public/app/features/playlist/usePlaylistItems.tsx @@ -1,14 +1,14 @@ +import { DashboardPickerItem } from 'app/core/components/editors/DashboardPickerByID'; import { useCallback, useState } from 'react'; import { PlaylistItem } from './types'; -import { DashboardPickerItem } from '../../core/components/Select/DashboardPicker'; export function usePlaylistItems(playlistItems?: PlaylistItem[]) { const [items, setItems] = useState(playlistItems ?? []); const addById = useCallback( (dashboard: DashboardPickerItem) => { - if (items.find((item) => item.id === dashboard.id)) { + if (!dashboard || items.find((item) => item.id === dashboard.id)) { return; } diff --git a/public/app/plugins/panel/debug/module.tsx b/public/app/plugins/panel/debug/module.tsx index 0158eb7fe3d..0dcc3a9832e 100644 --- a/public/app/plugins/panel/debug/module.tsx +++ b/public/app/plugins/panel/debug/module.tsx @@ -33,5 +33,13 @@ export const plugin = new PanelPlugin(DebugPanel).useFieldCon name: 'Schema Changed Count', defaultValue: true, showIf: ({ mode }) => mode === DebugMode.Render, + }) + .addDashboardPicker({ + path: 'dashboardUID', + name: 'Dashboard', + settings: { + placeholder: 'Select dashboard', + isClearable: true, + }, }); });