소스 검색

Plugins: Unifying alpha state & options for all plugins (#16530)

* app pages

* app pages

* workign example

* started alpha support

* remove app stuff

* show warning on alpha/beta panels

* put app back on plugin file

* fix go

* add enum for PluginType and PluginIncludeType

* Refactoring and moving settings to plugins section

fixes #16529
Ryan McKinley 6 년 전
부모
커밋
3c21a121eb

+ 5 - 0
conf/defaults.ini

@@ -613,8 +613,13 @@ server_url =
 callback_url =
 callback_url =
 
 
 [panels]
 [panels]
+# here for to support old env variables, can remove after a few months
 enable_alpha = false
 enable_alpha = false
 disable_sanitize_html = false
 disable_sanitize_html = false
 
 
+[plugins]
+enable_alpha = false
+app_tls_skip_verify_insecure = false
+
 [enterprise]
 [enterprise]
 license_path =
 license_path =

+ 4 - 1
conf/sample.ini

@@ -540,7 +540,10 @@ log_queries =
 ;license_path =
 ;license_path =
 
 
 [panels]
 [panels]
-;enable_alpha = false
 # If set to true Grafana will allow script tags in text panels. Not recommended as it enable XSS vulnerabilities.
 # If set to true Grafana will allow script tags in text panels. Not recommended as it enable XSS vulnerabilities.
 ;disable_sanitize_html = false
 ;disable_sanitize_html = false
 
 
+[plugins]
+;enable_alpha = false
+;app_tls_skip_verify_insecure = false
+

+ 10 - 7
docs/sources/installation/configuration.md

@@ -651,26 +651,29 @@ This limit will protect the server from render overloading and make sure notific
 value is `5`.
 value is `5`.
 
 
 
 
-### evaluation_timeout_seconds 
+### evaluation_timeout_seconds
 
 
-Default setting for alert calculation timeout. Default value is `30` 
+Default setting for alert calculation timeout. Default value is `30`
 
 
 ### notification_timeout_seconds
 ### notification_timeout_seconds
 
 
-Default setting for alert notification timeout. Default value is `30` 
+Default setting for alert notification timeout. Default value is `30`
 
 
 ### max_attempts
 ### max_attempts
 
 
-Default setting for max attempts to sending alert notifications. Default value is `3` 
+Default setting for max attempts to sending alert notifications. Default value is `3`
 
 
 
 
 ## [panels]
 ## [panels]
 
 
-### enable_alpha
-Set to true if you want to test panels that are not yet ready for general usage.
-
 ### disable_sanitize_html
 ### disable_sanitize_html
+
 If set to true Grafana will allow script tags in text panels. Not recommended as it enable XSS vulnerabilities. Default
 If set to true Grafana will allow script tags in text panels. Not recommended as it enable XSS vulnerabilities. Default
 is false. This settings was introduced in Grafana v6.0.
 is false. This settings was introduced in Grafana v6.0.
 
 
+## [plugins]
+
+### enable_alpha
+
+Set to true if you want to test alpha plugins that are not yet ready for general usage.
 
 

+ 27 - 3
packages/grafana-ui/src/types/plugin.ts

@@ -1,10 +1,25 @@
+export enum PluginState {
+  alpha = 'alpha', // Only included it `enable_alpha` is true
+  beta = 'beta', // Will show a warning banner
+}
+
+export enum PluginType {
+  panel = 'panel',
+  datasource = 'datasource',
+  app = 'app',
+}
+
 export interface PluginMeta {
 export interface PluginMeta {
   id: string;
   id: string;
   name: string;
   name: string;
   info: PluginMetaInfo;
   info: PluginMetaInfo;
-  includes: PluginInclude[];
   module: string;
   module: string;
-  baseUrl: string;
+  includes?: PluginInclude[];
+  baseUrl?: string;
+
+  type: PluginType;
+  enabled?: boolean;
+  state?: PluginState;
 
 
   // Datasource-specific
   // Datasource-specific
   builtIn?: boolean;
   builtIn?: boolean;
@@ -24,8 +39,17 @@ interface PluginMetaQueryOptions {
   minInterval?: boolean;
   minInterval?: boolean;
 }
 }
 
 
+export enum PluginIncludeType {
+  dashboard = 'dashboard',
+  page = 'page',
+
+  // Only valid for apps
+  panel = 'panel',
+  datasource = 'datasource',
+}
+
 export interface PluginInclude {
 export interface PluginInclude {
-  type: string;
+  type: PluginIncludeType;
   name: string;
   name: string;
   path: string;
   path: string;
 }
 }

+ 1 - 2
pkg/api/app_routes.go

@@ -11,7 +11,6 @@ import (
 	"github.com/grafana/grafana/pkg/middleware"
 	"github.com/grafana/grafana/pkg/middleware"
 	m "github.com/grafana/grafana/pkg/models"
 	m "github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/plugins"
 	"github.com/grafana/grafana/pkg/plugins"
-	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/util"
 	"github.com/grafana/grafana/pkg/util"
 	macaron "gopkg.in/macaron.v1"
 	macaron "gopkg.in/macaron.v1"
 )
 )
@@ -21,7 +20,7 @@ var pluginProxyTransport *http.Transport
 func (hs *HTTPServer) initAppPluginRoutes(r *macaron.Macaron) {
 func (hs *HTTPServer) initAppPluginRoutes(r *macaron.Macaron) {
 	pluginProxyTransport = &http.Transport{
 	pluginProxyTransport = &http.Transport{
 		TLSClientConfig: &tls.Config{
 		TLSClientConfig: &tls.Config{
-			InsecureSkipVerify: setting.PluginAppsSkipVerifyTLS,
+			InsecureSkipVerify: hs.Cfg.PluginsAppsSkipVerifyTLS,
 			Renegotiation:      tls.RenegotiateFreelyAsClient,
 			Renegotiation:      tls.RenegotiateFreelyAsClient,
 		},
 		},
 		Proxy: http.ProxyFromEnvironment,
 		Proxy: http.ProxyFromEnvironment,

+ 2 - 1
pkg/api/frontendsettings.go

@@ -145,7 +145,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf
 
 
 	panels := map[string]interface{}{}
 	panels := map[string]interface{}{}
 	for _, panel := range enabledPlugins.Panels {
 	for _, panel := range enabledPlugins.Panels {
-		if panel.State == plugins.PluginStateAlpha && !hs.Cfg.EnableAlphaPanels {
+		if panel.State == plugins.PluginStateAlpha && !hs.Cfg.PluginsEnableAlpha {
 			continue
 			continue
 		}
 		}
 
 
@@ -162,6 +162,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf
 			"hideFromList": panel.HideFromList,
 			"hideFromList": panel.HideFromList,
 			"sort":         getPanelSort(panel.Id),
 			"sort":         getPanelSort(panel.Id),
 			"dataFormats":  panel.DataFormats,
 			"dataFormats":  panel.DataFormats,
+			"state":        panel.State,
 		}
 		}
 	}
 	}
 
 

+ 1 - 1
pkg/api/plugins.go

@@ -39,7 +39,7 @@ func (hs *HTTPServer) GetPluginList(c *m.ReqContext) Response {
 			continue
 			continue
 		}
 		}
 
 
-		if pluginDef.State == plugins.PluginStateAlpha && !hs.Cfg.EnableAlphaPanels {
+		if pluginDef.State == plugins.PluginStateAlpha && !hs.Cfg.PluginsEnableAlpha {
 			continue
 			continue
 		}
 		}
 
 

+ 13 - 10
pkg/setting/setting.go

@@ -142,9 +142,6 @@ var (
 	// Basic Auth
 	// Basic Auth
 	BasicAuthEnabled bool
 	BasicAuthEnabled bool
 
 
-	// Plugin settings
-	PluginAppsSkipVerifyTLS bool
-
 	// Session settings.
 	// Session settings.
 	SessionOptions         session.Options
 	SessionOptions         session.Options
 	SessionConnMaxLifetime int64
 	SessionConnMaxLifetime int64
@@ -233,7 +230,8 @@ type Cfg struct {
 	MetricsEndpointEnabled           bool
 	MetricsEndpointEnabled           bool
 	MetricsEndpointBasicAuthUsername string
 	MetricsEndpointBasicAuthUsername string
 	MetricsEndpointBasicAuthPassword string
 	MetricsEndpointBasicAuthPassword string
-	EnableAlphaPanels                bool
+	PluginsEnableAlpha               bool
+	PluginsAppsSkipVerifyTLS         bool
 	DisableSanitizeHtml              bool
 	DisableSanitizeHtml              bool
 	EnterpriseLicensePath            string
 	EnterpriseLicensePath            string
 
 
@@ -721,9 +719,6 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
 	authBasic := iniFile.Section("auth.basic")
 	authBasic := iniFile.Section("auth.basic")
 	BasicAuthEnabled = authBasic.Key("enabled").MustBool(true)
 	BasicAuthEnabled = authBasic.Key("enabled").MustBool(true)
 
 
-	// global plugin settings
-	PluginAppsSkipVerifyTLS = iniFile.Section("plugins").Key("app_tls_skip_verify_insecure").MustBool(false)
-
 	// Rendering
 	// Rendering
 	renderSec := iniFile.Section("rendering")
 	renderSec := iniFile.Section("rendering")
 	cfg.RendererUrl = renderSec.Key("server_url").String()
 	cfg.RendererUrl = renderSec.Key("server_url").String()
@@ -771,9 +766,17 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
 	explore := iniFile.Section("explore")
 	explore := iniFile.Section("explore")
 	ExploreEnabled = explore.Key("enabled").MustBool(true)
 	ExploreEnabled = explore.Key("enabled").MustBool(true)
 
 
-	panels := iniFile.Section("panels")
-	cfg.EnableAlphaPanels = panels.Key("enable_alpha").MustBool(false)
-	cfg.DisableSanitizeHtml = panels.Key("disable_sanitize_html").MustBool(false)
+	panelsSection := iniFile.Section("panels")
+	cfg.DisableSanitizeHtml = panelsSection.Key("disable_sanitize_html").MustBool(false)
+
+	pluginsSection := iniFile.Section("plugins")
+	cfg.PluginsEnableAlpha = pluginsSection.Key("enable_alpha").MustBool(false)
+	cfg.PluginsAppsSkipVerifyTLS = iniFile.Section("plugins").Key("app_tls_skip_verify_insecure").MustBool(false)
+
+	// check old location for this option
+	if panelsSection.Key("enable_alpha").MustBool(false) {
+		cfg.PluginsEnableAlpha = true
+	}
 
 
 	cfg.readSessionConfig()
 	cfg.readSessionConfig()
 	cfg.readSmtpSettings()
 	cfg.readSmtpSettings()

+ 2 - 1
public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx

@@ -7,7 +7,7 @@ import { AlertBox } from 'app/core/components/AlertBox/AlertBox';
 
 
 // Types
 // Types
 import { PanelPlugin, AppNotificationSeverity } from 'app/types';
 import { PanelPlugin, AppNotificationSeverity } from 'app/types';
-import { PanelProps, ReactPanelPlugin } from '@grafana/ui';
+import { PanelProps, ReactPanelPlugin, PluginType } from '@grafana/ui';
 
 
 interface Props {
 interface Props {
   pluginId: string;
   pluginId: string;
@@ -45,6 +45,7 @@ export function getPanelPluginNotFound(id: string): PanelPlugin {
     id: id,
     id: id,
     name: id,
     name: id,
     sort: 100,
     sort: 100,
+    type: PluginType.panel,
     module: '',
     module: '',
     baseUrl: '',
     baseUrl: '',
     dataFormats: [],
     dataFormats: [],

+ 2 - 0
public/app/features/dashboard/panel_editor/VisualizationTab.tsx

@@ -18,6 +18,7 @@ import { PanelModel } from '../state';
 import { DashboardModel } from '../state';
 import { DashboardModel } from '../state';
 import { PanelPlugin } from 'app/types/plugins';
 import { PanelPlugin } from 'app/types/plugins';
 import { VizPickerSearch } from './VizPickerSearch';
 import { VizPickerSearch } from './VizPickerSearch';
+import PluginStateinfo from 'app/features/plugins/PluginStateInfo';
 
 
 interface Props {
 interface Props {
   panel: PanelModel;
   panel: PanelModel;
@@ -238,6 +239,7 @@ export class VisualizationTab extends PureComponent<Props, State> {
               onClose={this.onCloseVizPicker}
               onClose={this.onCloseVizPicker}
             />
             />
           </FadeIn>
           </FadeIn>
+          <PluginStateinfo state={plugin.state} />
           {this.renderPanelOptions()}
           {this.renderPanelOptions()}
         </>
         </>
       </EditorTabBody>
       </EditorTabBody>

+ 2 - 32
public/app/features/datasources/settings/DataSourceSettingsPage.tsx

@@ -24,6 +24,7 @@ import { getRouteParamsId } from 'app/core/selectors/location';
 import { NavModel, Plugin, StoreState } from 'app/types/';
 import { NavModel, Plugin, StoreState } from 'app/types/';
 import { DataSourceSettings } from '@grafana/ui/src/types/';
 import { DataSourceSettings } from '@grafana/ui/src/types/';
 import { getDataSourceLoadingNav } from '../state/navModel';
 import { getDataSourceLoadingNav } from '../state/navModel';
+import PluginStateinfo from 'app/features/plugins/PluginStateInfo';
 
 
 export interface Props {
 export interface Props {
   navModel: NavModel;
   navModel: NavModel;
@@ -44,11 +45,6 @@ interface State {
   testingStatus?: string;
   testingStatus?: string;
 }
 }
 
 
-enum DataSourceStates {
-  Alpha = 'alpha',
-  Beta = 'beta',
-}
-
 export class DataSourceSettingsPage extends PureComponent<Props, State> {
 export class DataSourceSettingsPage extends PureComponent<Props, State> {
   constructor(props: Props) {
   constructor(props: Props) {
     super(props);
     super(props);
@@ -110,32 +106,6 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
     return this.props.dataSource.readOnly === true;
     return this.props.dataSource.readOnly === true;
   }
   }
 
 
-  shouldRenderInfoBox() {
-    const { state } = this.props.dataSourceMeta;
-
-    return state === DataSourceStates.Alpha || state === DataSourceStates.Beta;
-  }
-
-  getInfoText() {
-    const { dataSourceMeta } = this.props;
-
-    switch (dataSourceMeta.state) {
-      case DataSourceStates.Alpha:
-        return (
-          'This plugin is marked as being in alpha state, which means it is in early development phase and updates' +
-          ' will include breaking changes.'
-        );
-
-      case DataSourceStates.Beta:
-        return (
-          'This plugin is marked as being in a beta development state. This means it is in currently in active' +
-          ' development and could be missing important features.'
-        );
-    }
-
-    return null;
-  }
-
   renderIsReadOnlyMessage() {
   renderIsReadOnlyMessage() {
     return (
     return (
       <div className="grafana-info-box span8">
       <div className="grafana-info-box span8">
@@ -196,7 +166,7 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
             <div>
             <div>
               <form onSubmit={this.onSubmit}>
               <form onSubmit={this.onSubmit}>
                 {this.isReadOnly() && this.renderIsReadOnlyMessage()}
                 {this.isReadOnly() && this.renderIsReadOnlyMessage()}
-                {this.shouldRenderInfoBox() && <div className="grafana-info-box">{this.getInfoText()}</div>}
+                <PluginStateinfo state={dataSourceMeta.state} />
 
 
                 <BasicSettings
                 <BasicSettings
                   dataSourceName={dataSource.name}
                   dataSourceName={dataSource.name}

+ 20 - 20
public/app/features/datasources/settings/__snapshots__/DataSourceSettingsPage.test.tsx.snap

@@ -11,11 +11,9 @@ exports[`Render should render alpha info text 1`] = `
       <form
       <form
         onSubmit={[Function]}
         onSubmit={[Function]}
       >
       >
-        <div
-          className="grafana-info-box"
-        >
-          This plugin is marked as being in alpha state, which means it is in early development phase and updates will include breaking changes.
-        </div>
+        <PluginStateinfo
+          state="alpha"
+        />
         <BasicSettings
         <BasicSettings
           dataSourceName="gdev-cloudwatch"
           dataSourceName="gdev-cloudwatch"
           isDefault={false}
           isDefault={false}
@@ -49,6 +47,7 @@ exports[`Render should render alpha info text 1`] = `
           }
           }
           dataSourceMeta={
           dataSourceMeta={
             Object {
             Object {
+              "baseUrl": "path/to/plugin",
               "defaultNavUrl": "some/url",
               "defaultNavUrl": "some/url",
               "enabled": false,
               "enabled": false,
               "hasUpdate": false,
               "hasUpdate": false,
@@ -78,11 +77,11 @@ exports[`Render should render alpha info text 1`] = `
                 "version": "1",
                 "version": "1",
               },
               },
               "latestVersion": "1",
               "latestVersion": "1",
-              "module": Object {},
+              "module": "path/to/module",
               "name": "pretty cool plugin 1",
               "name": "pretty cool plugin 1",
               "pinned": false,
               "pinned": false,
               "state": "alpha",
               "state": "alpha",
-              "type": "",
+              "type": "panel",
             }
             }
           }
           }
           onModelChange={[Function]}
           onModelChange={[Function]}
@@ -113,11 +112,9 @@ exports[`Render should render beta info text 1`] = `
       <form
       <form
         onSubmit={[Function]}
         onSubmit={[Function]}
       >
       >
-        <div
-          className="grafana-info-box"
-        >
-          This plugin is marked as being in a beta development state. This means it is in currently in active development and could be missing important features.
-        </div>
+        <PluginStateinfo
+          state="beta"
+        />
         <BasicSettings
         <BasicSettings
           dataSourceName="gdev-cloudwatch"
           dataSourceName="gdev-cloudwatch"
           isDefault={false}
           isDefault={false}
@@ -151,6 +148,7 @@ exports[`Render should render beta info text 1`] = `
           }
           }
           dataSourceMeta={
           dataSourceMeta={
             Object {
             Object {
+              "baseUrl": "path/to/plugin",
               "defaultNavUrl": "some/url",
               "defaultNavUrl": "some/url",
               "enabled": false,
               "enabled": false,
               "hasUpdate": false,
               "hasUpdate": false,
@@ -180,11 +178,11 @@ exports[`Render should render beta info text 1`] = `
                 "version": "1",
                 "version": "1",
               },
               },
               "latestVersion": "1",
               "latestVersion": "1",
-              "module": Object {},
+              "module": "path/to/module",
               "name": "pretty cool plugin 1",
               "name": "pretty cool plugin 1",
               "pinned": false,
               "pinned": false,
               "state": "beta",
               "state": "beta",
-              "type": "",
+              "type": "panel",
             }
             }
           }
           }
           onModelChange={[Function]}
           onModelChange={[Function]}
@@ -215,6 +213,7 @@ exports[`Render should render component 1`] = `
       <form
       <form
         onSubmit={[Function]}
         onSubmit={[Function]}
       >
       >
+        <PluginStateinfo />
         <BasicSettings
         <BasicSettings
           dataSourceName="gdev-cloudwatch"
           dataSourceName="gdev-cloudwatch"
           isDefault={false}
           isDefault={false}
@@ -248,6 +247,7 @@ exports[`Render should render component 1`] = `
           }
           }
           dataSourceMeta={
           dataSourceMeta={
             Object {
             Object {
+              "baseUrl": "path/to/plugin",
               "defaultNavUrl": "some/url",
               "defaultNavUrl": "some/url",
               "enabled": false,
               "enabled": false,
               "hasUpdate": false,
               "hasUpdate": false,
@@ -277,11 +277,10 @@ exports[`Render should render component 1`] = `
                 "version": "1",
                 "version": "1",
               },
               },
               "latestVersion": "1",
               "latestVersion": "1",
-              "module": Object {},
+              "module": "path/to/module",
               "name": "pretty cool plugin 1",
               "name": "pretty cool plugin 1",
               "pinned": false,
               "pinned": false,
-              "state": "",
-              "type": "",
+              "type": "panel",
             }
             }
           }
           }
           onModelChange={[Function]}
           onModelChange={[Function]}
@@ -317,6 +316,7 @@ exports[`Render should render is ready only message 1`] = `
         >
         >
           This datasource was added by config and cannot be modified using the UI. Please contact your server admin to update this datasource.
           This datasource was added by config and cannot be modified using the UI. Please contact your server admin to update this datasource.
         </div>
         </div>
+        <PluginStateinfo />
         <BasicSettings
         <BasicSettings
           dataSourceName="gdev-cloudwatch"
           dataSourceName="gdev-cloudwatch"
           isDefault={false}
           isDefault={false}
@@ -350,6 +350,7 @@ exports[`Render should render is ready only message 1`] = `
           }
           }
           dataSourceMeta={
           dataSourceMeta={
             Object {
             Object {
+              "baseUrl": "path/to/plugin",
               "defaultNavUrl": "some/url",
               "defaultNavUrl": "some/url",
               "enabled": false,
               "enabled": false,
               "hasUpdate": false,
               "hasUpdate": false,
@@ -379,11 +380,10 @@ exports[`Render should render is ready only message 1`] = `
                 "version": "1",
                 "version": "1",
               },
               },
               "latestVersion": "1",
               "latestVersion": "1",
-              "module": Object {},
+              "module": "path/to/module",
               "name": "pretty cool plugin 1",
               "name": "pretty cool plugin 1",
               "pinned": false,
               "pinned": false,
-              "state": "",
-              "type": "",
+              "type": "panel",
             }
             }
           }
           }
           onModelChange={[Function]}
           onModelChange={[Function]}

+ 3 - 2
public/app/features/datasources/state/navModel.ts

@@ -1,5 +1,5 @@
 import { NavModel, NavModelItem } from 'app/types';
 import { NavModel, NavModelItem } from 'app/types';
-import { PluginMeta, DataSourceSettings } from '@grafana/ui/src/types';
+import { PluginMeta, DataSourceSettings, PluginType } from '@grafana/ui/src/types';
 import config from 'app/core/config';
 import config from 'app/core/config';
 
 
 export function buildNavModel(dataSource: DataSourceSettings, pluginMeta: PluginMeta): NavModelItem {
 export function buildNavModel(dataSource: DataSourceSettings, pluginMeta: PluginMeta): NavModelItem {
@@ -67,6 +67,7 @@ export function getDataSourceLoadingNav(pageName: string): NavModel {
     },
     },
     {
     {
       id: '1',
       id: '1',
+      type: PluginType.datasource,
       name: '',
       name: '',
       info: {
       info: {
         author: {
         author: {
@@ -83,7 +84,7 @@ export function getDataSourceLoadingNav(pageName: string): NavModel {
         updated: '',
         updated: '',
         version: '',
         version: '',
       },
       },
-      includes: [{ type: '', name: '', path: '' }],
+      includes: [],
       module: '',
       module: '',
       baseUrl: '',
       baseUrl: '',
     }
     }

+ 16 - 16
public/app/features/datasources/state/reducers.test.ts

@@ -14,22 +14,22 @@ import {
 } from './actions';
 } from './actions';
 import { getMockDataSources, getMockDataSource } from '../__mocks__/dataSourcesMocks';
 import { getMockDataSources, getMockDataSource } from '../__mocks__/dataSourcesMocks';
 import { LayoutModes } from 'app/core/components/LayoutSelector/LayoutSelector';
 import { LayoutModes } from 'app/core/components/LayoutSelector/LayoutSelector';
-import { DataSourcesState } from 'app/types';
-import { PluginMetaInfo } from '@grafana/ui';
-
-const mockPlugin = () => ({
-  defaultNavUrl: 'defaultNavUrl',
-  enabled: true,
-  hasUpdate: true,
-  id: 'id',
-  info: {} as PluginMetaInfo,
-  latestVersion: 'latestVersion',
-  name: 'name',
-  pinned: true,
-  state: 'state',
-  type: 'type',
-  module: {},
-});
+import { DataSourcesState, Plugin } from 'app/types';
+import { PluginMetaInfo, PluginType } from '@grafana/ui';
+
+const mockPlugin = () =>
+  ({
+    defaultNavUrl: 'defaultNavUrl',
+    enabled: true,
+    hasUpdate: true,
+    id: 'id',
+    info: {} as PluginMetaInfo,
+    latestVersion: 'latestVersion',
+    name: 'name',
+    pinned: true,
+    type: PluginType.datasource,
+    module: 'path/to/module',
+  } as Plugin);
 
 
 describe('dataSourcesReducer', () => {
 describe('dataSourcesReducer', () => {
   describe('when dataSourcesLoaded is dispatched', () => {
   describe('when dataSourcesLoaded is dispatched', () => {

+ 34 - 0
public/app/features/plugins/PluginStateInfo.tsx

@@ -0,0 +1,34 @@
+import React, { FC } from 'react';
+import { PluginState } from '@grafana/ui';
+
+interface Props {
+  state?: PluginState;
+}
+
+function getPluginStateInfoText(state?: PluginState): string | null {
+  switch (state) {
+    case PluginState.alpha:
+      return (
+        'This plugin is marked as being in alpha state, which means it is in early development phase and updates' +
+        ' will include breaking changes.'
+      );
+
+    case PluginState.beta:
+      return (
+        'This plugin is marked as being in a beta development state. This means it is in currently in active' +
+        ' development and could be missing important features.'
+      );
+  }
+  return null;
+}
+
+const PluginStateinfo: FC<Props> = props => {
+  const text = getPluginStateInfoText(props.state);
+  if (!text) {
+    return null;
+  }
+
+  return <div className="grafana-info-box">{text}</div>;
+};
+
+export default PluginStateinfo;

+ 6 - 4
public/app/features/plugins/__mocks__/pluginMocks.ts

@@ -1,4 +1,5 @@
 import { Plugin, PanelPlugin, PanelDataFormat } from 'app/types';
 import { Plugin, PanelPlugin, PanelDataFormat } from 'app/types';
+import { PluginType } from '@grafana/ui';
 
 
 export const getMockPlugins = (amount: number): Plugin[] => {
 export const getMockPlugins = (amount: number): Plugin[] => {
   const plugins = [];
   const plugins = [];
@@ -36,6 +37,7 @@ export const getMockPlugins = (amount: number): Plugin[] => {
 export const getPanelPlugin = (options: Partial<PanelPlugin>): PanelPlugin => {
 export const getPanelPlugin = (options: Partial<PanelPlugin>): PanelPlugin => {
   return {
   return {
     id: options.id,
     id: options.id,
+    type: PluginType.panel,
     name: options.id,
     name: options.id,
     sort: options.sort || 1,
     sort: options.sort || 1,
     dataFormats: [PanelDataFormat.TimeSeries],
     dataFormats: [PanelDataFormat.TimeSeries],
@@ -81,9 +83,9 @@ export const getMockPlugin = () => {
     },
     },
     latestVersion: '1',
     latestVersion: '1',
     name: 'pretty cool plugin 1',
     name: 'pretty cool plugin 1',
+    baseUrl: 'path/to/plugin',
     pinned: false,
     pinned: false,
-    state: '',
-    type: '',
-    module: {},
-  };
+    type: PluginType.panel,
+    module: 'path/to/module',
+  } as Plugin;
 };
 };

+ 4 - 2
public/app/features/plugins/__snapshots__/PluginListItem.test.tsx.snap

@@ -15,8 +15,9 @@ exports[`Render should render component 1`] = `
         className="card-item-type"
         className="card-item-type"
       >
       >
         <i
         <i
-          className="icon-gf icon-gf-"
+          className="icon-gf icon-gf-panel"
         />
         />
+        panel
       </div>
       </div>
     </div>
     </div>
     <div
     <div
@@ -63,8 +64,9 @@ exports[`Render should render has plugin section 1`] = `
         className="card-item-type"
         className="card-item-type"
       >
       >
         <i
         <i
-          className="icon-gf icon-gf-"
+          className="icon-gf icon-gf-panel"
         />
         />
+        panel
       </div>
       </div>
       <div
       <div
         className="card-item-notice"
         className="card-item-notice"

+ 3 - 13
public/app/types/plugins.ts

@@ -1,10 +1,7 @@
-import { AngularPanelPlugin, ReactPanelPlugin, PluginMetaInfo } from '@grafana/ui/src/types';
+import { AngularPanelPlugin, ReactPanelPlugin, PluginMetaInfo, PluginMeta } from '@grafana/ui/src/types';
 
 
-export interface PanelPlugin {
-  id: string;
-  name: string;
+export interface PanelPlugin extends PluginMeta {
   hideFromList?: boolean;
   hideFromList?: boolean;
-  module: string;
   baseUrl: string;
   baseUrl: string;
   info: PluginMetaInfo;
   info: PluginMetaInfo;
   sort: number;
   sort: number;
@@ -19,18 +16,11 @@ export enum PanelDataFormat {
   TimeSeries = 'time_series',
   TimeSeries = 'time_series',
 }
 }
 
 
-export interface Plugin {
+export interface Plugin extends PluginMeta {
   defaultNavUrl: string;
   defaultNavUrl: string;
-  enabled: boolean;
   hasUpdate: boolean;
   hasUpdate: boolean;
-  id: string;
-  info: PluginMetaInfo;
   latestVersion: string;
   latestVersion: string;
-  name: string;
   pinned: boolean;
   pinned: boolean;
-  state: string;
-  type: string;
-  module: any;
 }
 }
 
 
 export interface PluginDashboard {
 export interface PluginDashboard {