diff --git a/public/app/core/components/PanelTypeFilter/PanelTypeFilter.tsx b/public/app/core/components/PanelTypeFilter/PanelTypeFilter.tsx index ca93e4fb182..9e852741f0e 100644 --- a/public/app/core/components/PanelTypeFilter/PanelTypeFilter.tsx +++ b/public/app/core/components/PanelTypeFilter/PanelTypeFilter.tsx @@ -2,16 +2,26 @@ import { css } from '@emotion/css'; import React, { useCallback, useMemo, useState } from 'react'; import { GrafanaTheme2, PanelPluginMeta, SelectableValue } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { Icon, Button, MultiSelect, useStyles2 } from '@grafana/ui'; -import { getAllPanelPluginMeta } from 'app/features/panel/state/util'; +import { getAllPanelPluginMeta, getVizPluginMeta, getWidgetPluginMeta } from 'app/features/panel/state/util'; export interface Props { onChange: (plugins: PanelPluginMeta[]) => void; maxMenuHeight?: number; + isWidget?: boolean; } -export const PanelTypeFilter = ({ onChange: propsOnChange, maxMenuHeight }: Props): JSX.Element => { - const plugins = useMemo(() => getAllPanelPluginMeta(), []); +export const PanelTypeFilter = ({ onChange: propsOnChange, maxMenuHeight, isWidget = false }: Props): JSX.Element => { + const getPluginMetaData = (): PanelPluginMeta[] => { + if (config.featureToggles.vizAndWidgetSplit) { + return isWidget ? getWidgetPluginMeta() : getVizPluginMeta(); + } else { + return getAllPanelPluginMeta(); + } + }; + + const plugins = useMemo(getPluginMetaData, [isWidget]); const options = useMemo( () => plugins diff --git a/public/app/core/constants.ts b/public/app/core/constants.ts index 66417d663af..31b513373e2 100644 --- a/public/app/core/constants.ts +++ b/public/app/core/constants.ts @@ -17,3 +17,5 @@ export const EDIT_PANEL_ID = 23763571993; export const DEFAULT_PER_PAGE_PAGINATION = 40; export const LS_VISUALIZATION_SELECT_TAB_KEY = 'VisualizationSelectPane.ListMode'; + +export const LS_WIDGET_SELECT_TAB_KEY = 'WidgetSelectPane.ListMode'; diff --git a/public/app/features/dashboard/components/AddPanelButton/AddPanelMenu.test.tsx b/public/app/features/dashboard/components/AddPanelButton/AddPanelMenu.test.tsx index 8473453e3d3..b97e76ed0f4 100644 --- a/public/app/features/dashboard/components/AddPanelButton/AddPanelMenu.test.tsx +++ b/public/app/features/dashboard/components/AddPanelButton/AddPanelMenu.test.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { PluginType } from '@grafana/data'; import { locationService, reportInteraction } from '@grafana/runtime'; import { defaultDashboard } from '@grafana/schema'; +import config from 'app/core/config'; import { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures'; import { onCreateNewPanel, @@ -46,6 +47,7 @@ function setup() { } beforeEach(() => { + config.featureToggles = { vizAndWidgetSplit: false }; jest.clearAllMocks(); }); @@ -136,3 +138,15 @@ it('adds a library panel when clicked on menu item Import from library', () => { expect(locationService.partial).not.toHaveBeenCalled(); expect(onAddLibraryPanel).toHaveBeenCalled(); }); + +it('renders menu list without Widget button when feature flag is disabled', () => { + setup(); + expect(screen.queryByText('Widget')).not.toBeInTheDocument(); +}); + +it('renders menu list with Widget button when feature flag is enabled', () => { + config.featureToggles.vizAndWidgetSplit = true; + setup(); + + expect(screen.getByText('Widget')).toBeInTheDocument(); +}); diff --git a/public/app/features/dashboard/components/AddPanelButton/AddPanelMenu.tsx b/public/app/features/dashboard/components/AddPanelButton/AddPanelMenu.tsx index 01ff2616cb2..3ddd62f5aa6 100644 --- a/public/app/features/dashboard/components/AddPanelButton/AddPanelMenu.tsx +++ b/public/app/features/dashboard/components/AddPanelButton/AddPanelMenu.tsx @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import { selectors } from '@grafana/e2e-selectors'; -import { locationService, reportInteraction } from '@grafana/runtime'; +import { config, locationService, reportInteraction } from '@grafana/runtime'; import { Menu } from '@grafana/ui'; import { t } from 'app/core/internationalization'; import { DashboardModel } from 'app/features/dashboard/state'; @@ -38,6 +38,17 @@ const AddPanelMenu = ({ dashboard }: Props) => { dispatch(setInitialDatasource(undefined)); }} /> + {config.featureToggles.vizAndWidgetSplit && ( + { + reportInteraction('dashboards_toolbar_add_clicked', { item: 'add_widget' }); + locationService.partial({ addWidget: true }); + }} + /> + )} { + const styles = useStyles2(getStyles); + const [searchQuery, setSearchQuery] = useState(''); + const dashboard = useSelector((state) => state.dashboard.getModel()); + + const widgetsList: PanelPluginMeta[] = useMemo(() => { + return getWidgetPluginMeta(); + }, []); + + const filteredWidgetsTypes = useMemo((): PanelPluginMeta[] => { + return filterPluginList(widgetsList, searchQuery); + }, [widgetsList, searchQuery]); + + const onDismiss = () => { + locationService.partial({ addWidget: null }); + }; + + return ( + + } + placeholder="Search widget" + onChange={(e) => { + setSearchQuery(e.currentTarget.value); + }} + /> + +
+ {filteredWidgetsTypes.map((plugin, index) => ( + { + const id = onCreateNewWidgetPanel(dashboard!, plugin.id); + locationService.partial({ editPanel: id, addWidget: null }); + }} + /> + ))} +
+
+
+ ); +}; + +const getStyles = (theme: GrafanaTheme2) => ({ + modal: css` + width: 65%; + max-width: 960px; + + ${theme.breakpoints.down('md')} { + width: 100%; + } + `, + searchInput: css` + margin-bottom: ${theme.spacing(2)}; + `, + grid: css` + display: grid; + grid-gap: ${theme.spacing(1)}; + `, +}); diff --git a/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx b/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx index 5eac3c5caea..ade128c9b95 100644 --- a/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx +++ b/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx @@ -4,9 +4,10 @@ import { useLocalStorage } from 'react-use'; import { GrafanaTheme2, PanelData, SelectableValue } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { config } from '@grafana/runtime'; import { Button, CustomScrollbar, FilterInput, RadioButtonGroup, useStyles2 } from '@grafana/ui'; import { Field } from '@grafana/ui/src/components/Forms/Field'; -import { LS_VISUALIZATION_SELECT_TAB_KEY } from 'app/core/constants'; +import { LS_VISUALIZATION_SELECT_TAB_KEY, LS_WIDGET_SELECT_TAB_KEY } from 'app/core/constants'; import { PanelLibraryOptionsGroup } from 'app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup'; import { VisualizationSuggestions } from 'app/features/panel/components/VizTypePicker/VisualizationSuggestions'; import { VizTypeChangeDetails } from 'app/features/panel/components/VizTypePicker/types'; @@ -28,10 +29,15 @@ interface Props { export const VisualizationSelectPane = ({ panel, data }: Props) => { const plugin = useSelector(getPanelPluginWithFallback(panel.type)); const [searchQuery, setSearchQuery] = useState(''); - const [listMode, setListMode] = useLocalStorage( - LS_VISUALIZATION_SELECT_TAB_KEY, - VisualizationSelectPaneTab.Visualizations - ); + + // Add support to show widgets in the visualization picker + const isWidget = !!plugin.meta.skipDataQuery; + const isWidgetEnabled = Boolean(isWidget && config.featureToggles.vizAndWidgetSplit); + + const tabKey = isWidgetEnabled ? LS_WIDGET_SELECT_TAB_KEY : LS_VISUALIZATION_SELECT_TAB_KEY; + const defaultTab = isWidgetEnabled ? VisualizationSelectPaneTab.Widgets : VisualizationSelectPaneTab.Visualizations; + + const [listMode, setListMode] = useLocalStorage(tabKey, defaultTab); const dispatch = useDispatch(); const styles = useStyles2(getStyles); @@ -67,6 +73,15 @@ export const VisualizationSelectPane = ({ panel, data }: Props) => { }, ]; + const radioOptionsWidgetFlow: Array> = [ + { label: 'Widgets', value: VisualizationSelectPaneTab.Widgets }, + { + label: 'Library panels', + value: VisualizationSelectPaneTab.LibraryPanels, + description: 'Reusable panels you can share between multiple dashboards.', + }, + ]; + return (
@@ -88,7 +103,12 @@ export const VisualizationSelectPane = ({ panel, data }: Props) => { />
- +
@@ -103,6 +123,17 @@ export const VisualizationSelectPane = ({ panel, data }: Props) => { onClose={() => {}} /> )} + {listMode === VisualizationSelectPaneTab.Widgets && ( + {}} + isWidget + /> + )} + {listMode === VisualizationSelectPaneTab.Suggestions && ( { /> )} {listMode === VisualizationSelectPaneTab.LibraryPanels && ( - + )}
diff --git a/public/app/features/dashboard/components/PanelEditor/types.ts b/public/app/features/dashboard/components/PanelEditor/types.ts index 9de5e5fba73..6c6148d5d67 100644 --- a/public/app/features/dashboard/components/PanelEditor/types.ts +++ b/public/app/features/dashboard/components/PanelEditor/types.ts @@ -72,4 +72,5 @@ export enum VisualizationSelectPaneTab { Visualizations, LibraryPanels, Suggestions, + Widgets, } diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 24514467686..60afb2173cc 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -22,6 +22,7 @@ import { PanelEditEnteredEvent, PanelEditExitedEvent } from 'app/types/events'; import { cancelVariables, templateVarsChangedInUrl } from '../../variables/state/actions'; import { findTemplateVarChanges } from '../../variables/utils'; +import { AddWidgetModal } from '../components/AddWidgetModal/AddWidgetModal'; import { DashNav } from '../components/DashNav'; import { DashboardFailed } from '../components/DashboardLoading/DashboardFailed'; import { DashboardLoading } from '../components/DashboardLoading/DashboardLoading'; @@ -50,6 +51,7 @@ export type DashboardPageRouteSearchParams = { editPanel?: string; viewPanel?: string; editview?: string; + addWidget?: boolean; panelType?: string; inspect?: string; from?: string; @@ -411,6 +413,7 @@ export class UnthemedDashboardPage extends PureComponent { sectionNav={sectionNav} /> )} + {queryParams.addWidget && config.featureToggles.vizAndWidgetSplit && } ); } diff --git a/public/app/features/dashboard/dashgrid/DashboardEmpty.test.tsx b/public/app/features/dashboard/dashgrid/DashboardEmpty.test.tsx index 7e207589daf..2aad35b21f1 100644 --- a/public/app/features/dashboard/dashgrid/DashboardEmpty.test.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardEmpty.test.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { locationService, reportInteraction } from '@grafana/runtime'; import { defaultDashboard } from '@grafana/schema'; +import config from 'app/core/config'; import { createDashboardModelFixture } from '../state/__fixtures__/dashboardFixtures'; import { onCreateNewPanel, onCreateNewRow, onAddLibraryPanel } from '../utils/dashboard'; @@ -41,6 +42,7 @@ function setup(options?: Partial) { } beforeEach(() => { + config.featureToggles = { vizAndWidgetSplit: false }; jest.clearAllMocks(); }); @@ -101,3 +103,22 @@ it('adds a library panel when clicked Import library panel', () => { expect(locationService.partial).not.toHaveBeenCalled(); expect(onAddLibraryPanel).toHaveBeenCalled(); }); + +it('renders page without Add Widget button when feature flag is disabled', () => { + setup(); + + expect(screen.getByRole('button', { name: 'Add visualization' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Add row' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Import library panel' })).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Add widget' })).not.toBeInTheDocument(); +}); + +it('renders page with Add Widget button when feature flag is enabled', () => { + config.featureToggles.vizAndWidgetSplit = true; + setup(); + + expect(screen.getByRole('button', { name: 'Add visualization' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Add row' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Import library panel' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Add widget' })).toBeInTheDocument(); +}); diff --git a/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx b/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx index f7a101660a4..8fb2ee8212b 100644 --- a/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { locationService, reportInteraction } from '@grafana/runtime'; +import { config, locationService, reportInteraction } from '@grafana/runtime'; import { Button, useStyles2 } from '@grafana/ui'; import { H1, H3, P } from '@grafana/ui/src/unstable'; import { Trans } from 'app/core/internationalization'; @@ -58,6 +58,32 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
+ {config.featureToggles.vizAndWidgetSplit && ( +
+
+

+ Add a widget +

+
+
+

+ Create lists, markdowns and other widgets +

+
+ +
+ )}

@@ -148,22 +174,30 @@ function getStyles(theme: GrafanaTheme2) { padding: theme.spacing.gridSize * 4, }), others: css({ + width: '100%', label: 'others-wrapper', alignItems: 'stretch', flexDirection: 'row', gap: theme.spacing.gridSize * 4, - [theme.breakpoints.down('sm')]: { + [theme.breakpoints.down('md')]: { flexDirection: 'column', }, }), + widgetContainer: css({ + label: 'widget-container', + padding: theme.spacing.gridSize * 3, + flex: 1, + }), rowContainer: css({ label: 'row-container', padding: theme.spacing.gridSize * 3, + flex: 1, }), libraryContainer: css({ label: 'library-container', padding: theme.spacing.gridSize * 3, + flex: 1, }), headerBig: css({ marginBottom: theme.spacing.gridSize * 2, diff --git a/public/app/features/dashboard/utils/dashboard.ts b/public/app/features/dashboard/utils/dashboard.ts index bdd342d3c4d..9d1da06aded 100644 --- a/public/app/features/dashboard/utils/dashboard.ts +++ b/public/app/features/dashboard/utils/dashboard.ts @@ -20,6 +20,19 @@ export function onCreateNewPanel(dashboard: DashboardModel, datasource?: string) return newPanel.id; } +export function onCreateNewWidgetPanel(dashboard: DashboardModel, widgetType: string): number | undefined { + const newPanel: Partial = { + type: widgetType, + title: 'Widget title', + gridPos: calculateNewPanelGridPos(dashboard), + datasource: null, + isNew: true, + }; + + dashboard.addPanel(newPanel); + return newPanel.id; +} + export function onCreateNewRow(dashboard: DashboardModel) { const newRow = { type: 'row', diff --git a/public/app/features/library-panels/components/LibraryPanelsView/LibraryPanelsView.tsx b/public/app/features/library-panels/components/LibraryPanelsView/LibraryPanelsView.tsx index c34855dbc52..7cfd999aade 100644 --- a/public/app/features/library-panels/components/LibraryPanelsView/LibraryPanelsView.tsx +++ b/public/app/features/library-panels/components/LibraryPanelsView/LibraryPanelsView.tsx @@ -21,6 +21,7 @@ interface LibraryPanelViewProps { panelFilter?: string[]; folderFilter?: string[]; perPage?: number; + isWidget?: boolean; } export const LibraryPanelsView = ({ @@ -33,6 +34,7 @@ export const LibraryPanelsView = ({ showSecondaryActions, currentPanelId: currentPanel, perPage: propsPerPage = 40, + isWidget, }: LibraryPanelViewProps) => { const styles = useStyles2(getPanelViewStyles); const [{ libraryPanels, page, perPage, numberOfPages, loadingState, currentPanelId }, dispatch] = useReducer( @@ -55,6 +57,7 @@ export const LibraryPanelsView = ({ page, perPage, currentPanelId, + isWidget, }) ), 300, diff --git a/public/app/features/library-panels/components/LibraryPanelsView/actions.ts b/public/app/features/library-panels/components/LibraryPanelsView/actions.ts index ada907caaf0..3578a44064c 100644 --- a/public/app/features/library-panels/components/LibraryPanelsView/actions.ts +++ b/public/app/features/library-panels/components/LibraryPanelsView/actions.ts @@ -3,6 +3,11 @@ import { Dispatch } from 'react'; import { from, merge, of, Subscription, timer } from 'rxjs'; import { catchError, finalize, mapTo, mergeMap, share, takeUntil } from 'rxjs/operators'; +import { PanelPluginMeta } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { LibraryPanel } from '@grafana/schema'; +import { getAllPanelPluginMeta } from 'app/features/panel/state/util'; + import { deleteLibraryPanel as apiDeleteLibraryPanel, getLibraryPanels } from '../../state/api'; import { initialLibraryPanelsViewState, initSearch, searchCompleted } from './reducer'; @@ -16,9 +21,29 @@ interface SearchArgs { panelFilter?: string[]; folderFilterUIDs?: string[]; currentPanelId?: string; + isWidget?: boolean; } export function searchForLibraryPanels(args: SearchArgs): DispatchResult { + // Functions to support filtering out library panels per plugin type that have skipDataQuery set to true + + const findPluginMeta = (pluginMeta: PanelPluginMeta, libraryPanel: LibraryPanel) => + pluginMeta.id === libraryPanel.type; + + const filterLibraryPanels = (libraryPanels: LibraryPanel[], isWidget: boolean) => { + const pluginMetaList = getAllPanelPluginMeta(); + + return libraryPanels.filter((libraryPanel) => { + const matchingPluginMeta = pluginMetaList.find((pluginMeta) => findPluginMeta(pluginMeta, libraryPanel)); + // widget mode filter + if (isWidget) { + return !!matchingPluginMeta?.skipDataQuery; + } + // non-widget mode filter + return !matchingPluginMeta?.skipDataQuery; + }); + }; + return function (dispatch) { const subscription = new Subscription(); const dataObservable = from( @@ -32,6 +57,17 @@ export function searchForLibraryPanels(args: SearchArgs): DispatchResult { folderFilterUIDs: args.folderFilterUIDs, }) ).pipe( + //filter out library panels per plugin type that have skipDataQuery set to true + mergeMap((libraryPanelsResult) => { + const { elements: libraryPanels } = libraryPanelsResult; + + if (config.featureToggles.vizAndWidgetSplit && args.isWidget !== undefined) { + const filteredLibraryPanels = filterLibraryPanels(libraryPanels, args.isWidget); + return of({ ...libraryPanelsResult, elements: filteredLibraryPanels }); + } + + return of({ ...libraryPanelsResult, elements: libraryPanels }); + }), mergeMap(({ perPage, elements: libraryPanels, page, totalCount }) => of(searchCompleted({ libraryPanels, page, perPage, totalCount })) ), diff --git a/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx b/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx index 91f2bddcb04..11d96ae8824 100644 --- a/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx +++ b/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx @@ -17,9 +17,10 @@ import { LibraryPanelsView } from '../LibraryPanelsView/LibraryPanelsView'; interface Props { panel: PanelModel; searchQuery: string; + isWidget?: boolean; } -export const PanelLibraryOptionsGroup = ({ panel, searchQuery }: Props) => { +export const PanelLibraryOptionsGroup = ({ panel, searchQuery, isWidget = false }: Props) => { const [showingAddPanelModal, setShowingAddPanelModal] = useState(false); const [changeToPanel, setChangeToPanel] = useState(undefined); const [panelFilter, setPanelFilter] = useState([]); @@ -43,7 +44,6 @@ export const PanelLibraryOptionsGroup = ({ panel, searchQuery }: Props) => { const onAddToPanelLibrary = () => setShowingAddPanelModal(true); const onDismissChangeToPanel = () => setChangeToPanel(undefined); - return ( {!panel.libraryPanel && ( @@ -54,7 +54,7 @@ export const PanelLibraryOptionsGroup = ({ panel, searchQuery }: Props) => { )} - +
{ panelFilter={panelFilter} onClickCard={setChangeToPanel} showSecondaryActions + isWidget={isWidget} />
diff --git a/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx b/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx index 4a586ded9c5..308af9b6850 100644 --- a/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx +++ b/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx @@ -42,6 +42,7 @@ export const PanelTypeCard = ({
diff --git a/public/app/features/panel/components/VizTypePicker/VizTypePicker.tsx b/public/app/features/panel/components/VizTypePicker/VizTypePicker.tsx index 76f6687e0e6..61e575ce82b 100644 --- a/public/app/features/panel/components/VizTypePicker/VizTypePicker.tsx +++ b/public/app/features/panel/components/VizTypePicker/VizTypePicker.tsx @@ -2,9 +2,10 @@ import { css } from '@emotion/css'; import React, { useMemo } from 'react'; import { GrafanaTheme2, PanelData, PanelPluginMeta } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { EmptySearchResult, useStyles2 } from '@grafana/ui'; -import { filterPluginList, getAllPanelPluginMeta } from '../../state/util'; +import { filterPluginList, getAllPanelPluginMeta, getVizPluginMeta, getWidgetPluginMeta } from '../../state/util'; import { VizTypePickerPlugin } from './VizTypePickerPlugin'; import { VizTypeChangeDetails } from './types'; @@ -15,13 +16,20 @@ export interface Props { onChange: (options: VizTypeChangeDetails) => void; searchQuery: string; onClose: () => void; + isWidget?: boolean; } -export function VizTypePicker({ searchQuery, onChange, current, data }: Props) { +export function VizTypePicker({ searchQuery, onChange, current, data, isWidget = false }: Props) { const styles = useStyles2(getStyles); const pluginsList: PanelPluginMeta[] = useMemo(() => { + if (config.featureToggles.vizAndWidgetSplit) { + if (isWidget) { + return getWidgetPluginMeta(); + } + return getVizPluginMeta(); + } return getAllPanelPluginMeta(); - }, []); + }, [isWidget]); const filteredPluginTypes = useMemo((): PanelPluginMeta[] => { return filterPluginList(pluginsList, searchQuery, current); diff --git a/public/app/features/panel/state/util.ts b/public/app/features/panel/state/util.ts index a12a748400a..d4892847ed7 100644 --- a/public/app/features/panel/state/util.ts +++ b/public/app/features/panel/state/util.ts @@ -10,15 +10,23 @@ export function getAllPanelPluginMeta(): PanelPluginMeta[] { .sort((a: PanelPluginMeta, b: PanelPluginMeta) => a.sort - b.sort); } +export function getWidgetPluginMeta(): PanelPluginMeta[] { + return getAllPanelPluginMeta().filter((panel) => !!panel.skipDataQuery); +} + +export function getVizPluginMeta(): PanelPluginMeta[] { + return getAllPanelPluginMeta().filter((panel) => !panel.skipDataQuery); +} + export function filterPluginList( pluginsList: PanelPluginMeta[], searchQuery: string, // Note: this will be an escaped regex string as it comes from `FilterInput` - current: PanelPluginMeta + current?: PanelPluginMeta ): PanelPluginMeta[] { if (!searchQuery.length) { return pluginsList.filter((p) => { if (p.state === PluginState.deprecated) { - return current.id === p.id; + return current?.id === p.id; } return true; }); @@ -30,7 +38,7 @@ export function filterPluginList( const isGraphQuery = 'graph'.startsWith(query); for (const item of pluginsList) { - if (item.state === PluginState.deprecated && current.id !== item.id) { + if (item.state === PluginState.deprecated && current?.id !== item.id) { continue; } diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index a9f8813e98b..6ac25367b48 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -120,7 +120,8 @@ "import": "Aus Bibliothek importieren", "paste-panel": "Panel einfügen", "row": "Zeile", - "visualization": "Visualisierung" + "visualization": "Visualisierung", + "widget": "" }, "empty": { "add-import-body": "Visualisierungen importieren, die mit anderen Dashboards geteilt werden.", @@ -131,7 +132,10 @@ "add-row-header": "Eine Zeile hinzufügen", "add-visualization-body": "Wählen Sie eine Datenquelle aus und visualisieren und fragen Sie dann Ihre Daten mit Diagrammen, Statistiken und Tabellen ab oder erstellen Sie Listen, Markierungen und andere Widgets.", "add-visualization-button": "Visualisierung hinzufügen", - "add-visualization-header": "Starten Sie Ihr neues Dashboard, indem Sie eine Visualisierung hinzufügen" + "add-visualization-header": "Starten Sie Ihr neues Dashboard, indem Sie eine Visualisierung hinzufügen", + "add-widget-body": "", + "add-widget-button": "", + "add-widget-header": "" }, "inspect": { "data-tab": "Daten", diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index d50e4de9673..ecccf6744f4 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -120,7 +120,8 @@ "import": "Import from library", "paste-panel": "Paste panel", "row": "Row", - "visualization": "Visualization" + "visualization": "Visualization", + "widget": "Widget" }, "empty": { "add-import-body": "Import visualizations that are shared with other dashboards.", @@ -131,7 +132,10 @@ "add-row-header": "Add a row", "add-visualization-body": "Select a data source and then query and visualize your data with charts, stats and tables or create lists, markdowns and other widgets.", "add-visualization-button": "Add visualization", - "add-visualization-header": "Start your new dashboard by adding a visualization" + "add-visualization-header": "Start your new dashboard by adding a visualization", + "add-widget-body": "Create lists, markdowns and other widgets", + "add-widget-button": "Add widget", + "add-widget-header": "Add a widget" }, "inspect": { "data-tab": "Data", diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index d0240a93691..ce1d21e8ff6 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -125,7 +125,8 @@ "import": "Importar de la biblioteca", "paste-panel": "Pegar panel", "row": "Fila", - "visualization": "Visualización" + "visualization": "Visualización", + "widget": "" }, "empty": { "add-import-body": "Importar las visualizaciones que se compartan con otros paneles de control.", @@ -136,7 +137,10 @@ "add-row-header": "Añadir una fila", "add-visualization-body": "Selecciona una fuente de datos y luego consulta y visualiza tus datos con gráficos, estadísticas y tablas o cree listas, anotaciones y otros widgets.", "add-visualization-button": "Añadir visualización", - "add-visualization-header": "Comienza tu nuevo panel de control añadiendo una visualización" + "add-visualization-header": "Comienza tu nuevo panel de control añadiendo una visualización", + "add-widget-body": "", + "add-widget-button": "", + "add-widget-header": "" }, "inspect": { "data-tab": "Datos", diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 6addb9ebce7..547011c0e4a 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -125,7 +125,8 @@ "import": "Importer depuis la bibliothèque", "paste-panel": "Coller le panneau", "row": "Ligne", - "visualization": "Visualisation" + "visualization": "Visualisation", + "widget": "" }, "empty": { "add-import-body": "Importer des visualisations partagées avec d'autres tableaux de bord.", @@ -136,7 +137,10 @@ "add-row-header": "Ajouter une ligne", "add-visualization-body": "Sélectionnez une source de données, puis examinez et visualisez vos données avec des graphiques, des statistiques et des tableaux ou créez des listes, des markdowns et d'autres widgets.", "add-visualization-button": "Ajouter une visualisation", - "add-visualization-header": "Commencez votre nouveau tableau de bord en ajoutant une visualisation" + "add-visualization-header": "Commencez votre nouveau tableau de bord en ajoutant une visualisation", + "add-widget-body": "", + "add-widget-button": "", + "add-widget-header": "" }, "inspect": { "data-tab": "Données", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 69c34921840..6f52fecca31 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -120,7 +120,8 @@ "import": "Ĩmpőřŧ ƒřőm ľįþřäřy", "paste-panel": "Päşŧę päʼnęľ", "row": "Ŗőŵ", - "visualization": "Vįşūäľįžäŧįőʼn" + "visualization": "Vįşūäľįžäŧįőʼn", + "widget": "Ŵįđģęŧ" }, "empty": { "add-import-body": "Ĩmpőřŧ vįşūäľįžäŧįőʼnş ŧĥäŧ äřę şĥäřęđ ŵįŧĥ őŧĥęř đäşĥþőäřđş.", @@ -131,7 +132,10 @@ "add-row-header": "Åđđ ä řőŵ", "add-visualization-body": "Ŝęľęčŧ ä đäŧä şőūřčę äʼnđ ŧĥęʼn qūęřy äʼnđ vįşūäľįžę yőūř đäŧä ŵįŧĥ čĥäřŧş, şŧäŧş äʼnđ ŧäþľęş őř čřęäŧę ľįşŧş, mäřĸđőŵʼnş äʼnđ őŧĥęř ŵįđģęŧş.", "add-visualization-button": "Åđđ vįşūäľįžäŧįőʼn", - "add-visualization-header": "Ŝŧäřŧ yőūř ʼnęŵ đäşĥþőäřđ þy äđđįʼnģ ä vįşūäľįžäŧįőʼn" + "add-visualization-header": "Ŝŧäřŧ yőūř ʼnęŵ đäşĥþőäřđ þy äđđįʼnģ ä vįşūäľįžäŧįőʼn", + "add-widget-body": "Cřęäŧę ľįşŧş, mäřĸđőŵʼnş äʼnđ őŧĥęř ŵįđģęŧş", + "add-widget-button": "Åđđ ŵįđģęŧ", + "add-widget-header": "Åđđ ä ŵįđģęŧ" }, "inspect": { "data-tab": "Đäŧä", diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 4960c8585bb..c367870cca7 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -115,7 +115,8 @@ "import": "从库导入", "paste-panel": "粘贴面板", "row": "行", - "visualization": "可视化" + "visualization": "可视化", + "widget": "" }, "empty": { "add-import-body": "导入与其他仪表板共享的可视化。", @@ -126,7 +127,10 @@ "add-row-header": "添加一行", "add-visualization-body": "选择一个数据源,然后用图表、统计信息和表格查询您的数据以及将其可视化,或创建列表、Markdown 和其他小部件。", "add-visualization-button": "添加可视化", - "add-visualization-header": "通过添加可视化开始您的新仪表板" + "add-visualization-header": "通过添加可视化开始您的新仪表板", + "add-widget-body": "", + "add-widget-button": "", + "add-widget-header": "" }, "inspect": { "data-tab": "数据",