diff --git a/public/app/features/datasources/NewDataSourcePage.tsx b/public/app/features/datasources/NewDataSourcePage.tsx index a9b6588a35c..90786496e68 100644 --- a/public/app/features/datasources/NewDataSourcePage.tsx +++ b/public/app/features/datasources/NewDataSourcePage.tsx @@ -1,56 +1,33 @@ import React, { FC, PureComponent } from 'react'; +import classNames from 'classnames'; import { connect } from 'react-redux'; import { hot } from 'react-hot-loader'; -import { DataSourcePluginMeta, NavModel, PluginType } from '@grafana/data'; +import { DataSourcePluginMeta, NavModel } from '@grafana/data'; import { List } from '@grafana/ui'; import { e2e } from '@grafana/e2e'; import Page from 'app/core/components/Page/Page'; -import { StoreState } from 'app/types'; -import { addDataSource, loadDataSourceTypes, setDataSourceTypeSearchQuery } from './state/actions'; -import { getDataSourceTypes } from './state/selectors'; +import { StoreState, DataSourcePluginCategory } from 'app/types'; +import { addDataSource, loadDataSourcePlugins, setDataSourceTypeSearchQuery } from './state/actions'; +import { getDataSourcePlugins } from './state/selectors'; import { FilterInput } from 'app/core/components/FilterInput/FilterInput'; export interface Props { navModel: NavModel; - dataSourceTypes: DataSourcePluginMeta[]; + plugins: DataSourcePluginMeta[]; + categories: DataSourcePluginCategory[]; isLoading: boolean; addDataSource: typeof addDataSource; - loadDataSourceTypes: typeof loadDataSourceTypes; + loadDataSourcePlugins: typeof loadDataSourcePlugins; searchQuery: string; setDataSourceTypeSearchQuery: typeof setDataSourceTypeSearchQuery; } -interface DataSourceCategories { - [key: string]: DataSourcePluginMeta[]; -} - -interface DataSourceCategoryInfo { - id: string; - title: string; -} - class NewDataSourcePage extends PureComponent { searchInput: HTMLElement; - categoryInfoList: DataSourceCategoryInfo[] = [ - { id: 'tsdb', title: 'Time series databases' }, - { id: 'logging', title: 'Logging & document databases' }, - { id: 'sql', title: 'SQL' }, - { id: 'cloud', title: 'Cloud' }, - { id: 'other', title: 'Others' }, - ]; - - sortingRules: { [id: string]: number } = { - prometheus: 100, - graphite: 95, - loki: 90, - mysql: 80, - postgres: 79, - gcloud: -1, - }; componentDidMount() { - this.props.loadDataSourceTypes(); + this.props.loadDataSourcePlugins(); this.searchInput.focus(); } @@ -62,28 +39,14 @@ class NewDataSourcePage extends PureComponent { this.props.setDataSourceTypeSearchQuery(value); }; - renderTypes(types: DataSourcePluginMeta[]) { - if (!types) { + renderPlugins(plugins: DataSourcePluginMeta[]) { + if (!plugins || !plugins.length) { return null; } - // apply custom sort ranking - types.sort((a, b) => { - const aSort = this.sortingRules[a.id] || 0; - const bSort = this.sortingRules[b.id] || 0; - if (aSort > bSort) { - return -1; - } - if (aSort < bSort) { - return 1; - } - - return a.name > b.name ? -1 : 1; - }); - return ( item.id.toString()} renderItem={item => ( { evt.stopPropagation(); }; - renderGroupedList() { - const { dataSourceTypes } = this.props; - - if (dataSourceTypes.length === 0) { - return null; - } - - const categories = dataSourceTypes.reduce((accumulator, item) => { - const category = item.category || 'other'; - const list = accumulator[category] || []; - list.push(item); - accumulator[category] = list; - return accumulator; - }, {} as DataSourceCategories); - - categories['cloud'].push(getGrafanaCloudPhantomPlugin()); + renderCategories() { + const { categories } = this.props; return ( <> - {this.categoryInfoList.map(category => ( + {categories.map(category => (
{category.title}
- {this.renderTypes(categories[category.id])} + {this.renderPlugins(category.plugins)}
))}
@@ -140,7 +89,7 @@ class NewDataSourcePage extends PureComponent { } render() { - const { navModel, isLoading, searchQuery, dataSourceTypes } = this.props; + const { navModel, isLoading, searchQuery, plugins } = this.props; return ( @@ -162,8 +111,8 @@ class NewDataSourcePage extends PureComponent {
- {searchQuery && this.renderTypes(dataSourceTypes)} - {!searchQuery && this.renderGroupedList()} + {searchQuery && this.renderPlugins(plugins)} + {!searchQuery && this.renderCategories()}
@@ -179,15 +128,18 @@ interface DataSourceTypeCardProps { const DataSourceTypeCard: FC = props => { const { plugin, onLearnMoreClick } = props; - const canSelect = plugin.id !== 'gcloud'; - const onClick = canSelect ? props.onClick : () => {}; + const isPhantom = plugin.module === 'phantom'; + const onClick = !isPhantom ? props.onClick : () => {}; // find first plugin info link - const learnMoreLink = plugin.info.links && plugin.info.links.length > 0 ? plugin.info.links[0].url : null; + const learnMoreLink = plugin.info.links && plugin.info.links.length > 0 ? plugin.info.links[0] : null; + const mainClassName = classNames('add-data-source-item', { + 'add-data-source-item--phantom': isPhantom, + }); return (
@@ -200,44 +152,20 @@ const DataSourceTypeCard: FC = props => { {learnMoreLink && ( - Learn more + {learnMoreLink.name} )} - {canSelect && } + {!isPhantom && }
); }; -function getGrafanaCloudPhantomPlugin(): DataSourcePluginMeta { - return { - id: 'gcloud', - name: 'Grafana Cloud', - type: PluginType.datasource, - module: '', - baseUrl: '', - info: { - description: 'Hosted Graphite, Prometheus and Loki', - logos: { small: 'public/img/grafana_icon.svg', large: 'asd' }, - author: { name: 'Grafana Labs' }, - links: [ - { - url: 'https://grafana.com/products/cloud/', - name: 'Learn more', - }, - ], - screenshots: [], - updated: '2019-05-10', - version: '1.0.0', - }, - }; -} - export function getNavModel(): NavModel { const main = { icon: 'gicon gicon-add-datasources', @@ -256,15 +184,16 @@ export function getNavModel(): NavModel { function mapStateToProps(state: StoreState) { return { navModel: getNavModel(), - dataSourceTypes: getDataSourceTypes(state.dataSources), + plugins: getDataSourcePlugins(state.dataSources), searchQuery: state.dataSources.dataSourceTypeSearchQuery, + categories: state.dataSources.categories, isLoading: state.dataSources.isLoadingDataSources, }; } const mapDispatchToProps = { addDataSource, - loadDataSourceTypes, + loadDataSourcePlugins, setDataSourceTypeSearchQuery, }; diff --git a/public/app/features/datasources/settings/__snapshots__/DataSourceSettingsPage.test.tsx.snap b/public/app/features/datasources/settings/__snapshots__/DataSourceSettingsPage.test.tsx.snap index 063ebf8418d..057e0daf935 100644 --- a/public/app/features/datasources/settings/__snapshots__/DataSourceSettingsPage.test.tsx.snap +++ b/public/app/features/datasources/settings/__snapshots__/DataSourceSettingsPage.test.tsx.snap @@ -83,6 +83,7 @@ exports[`Render should render alpha info text 1`] = ` }, "screenshots": Array [ Object { + "name": "test", "path": "screenshot", }, ], @@ -272,6 +273,7 @@ exports[`Render should render is ready only message 1`] = ` }, "screenshots": Array [ Object { + "name": "test", "path": "screenshot", }, ], diff --git a/public/app/features/datasources/state/actions.ts b/public/app/features/datasources/state/actions.ts index 4ca15f1c2db..35ed973da95 100644 --- a/public/app/features/datasources/state/actions.ts +++ b/public/app/features/datasources/state/actions.ts @@ -1,47 +1,33 @@ -import { ThunkAction } from 'redux-thunk'; import config from '../../../core/config'; import { getBackendSrv } from '@grafana/runtime'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { LayoutMode } from 'app/core/components/LayoutSelector/LayoutSelector'; -import { updateLocation, updateNavIndex, UpdateNavIndexAction } from 'app/core/actions'; +import { updateLocation, updateNavIndex } from 'app/core/actions'; import { buildNavModel } from './navModel'; import { DataSourceSettings, DataSourcePluginMeta } from '@grafana/data'; -import { StoreState } from 'app/types'; -import { LocationUpdate } from '@grafana/runtime'; +import { ThunkResult, DataSourcePluginCategory } from 'app/types'; import { actionCreatorFactory } from 'app/core/redux'; -import { ActionOf } from 'app/core/redux/actionCreatorFactory'; import { getPluginSettings } from 'app/features/plugins/PluginSettingsCache'; import { importDataSourcePlugin } from 'app/features/plugins/plugin_loader'; +import { buildCategories } from './buildCategories'; export const dataSourceLoaded = actionCreatorFactory('LOAD_DATA_SOURCE').create(); - export const dataSourcesLoaded = actionCreatorFactory('LOAD_DATA_SOURCES').create(); - export const dataSourceMetaLoaded = actionCreatorFactory('LOAD_DATA_SOURCE_META').create(); - -export const dataSourceTypesLoad = actionCreatorFactory('LOAD_DATA_SOURCE_TYPES').create(); - -export const dataSourceTypesLoaded = actionCreatorFactory('LOADED_DATA_SOURCE_TYPES').create(); - +export const dataSourcePluginsLoad = actionCreatorFactory('LOAD_DATA_SOURCE_PLUGINS').create(); +export const dataSourcePluginsLoaded = actionCreatorFactory( + 'LOADED_DATA_SOURCE_PLUGINS' +).create(); export const setDataSourcesSearchQuery = actionCreatorFactory('SET_DATA_SOURCES_SEARCH_QUERY').create(); - export const setDataSourcesLayoutMode = actionCreatorFactory('SET_DATA_SOURCES_LAYOUT_MODE').create(); - export const setDataSourceTypeSearchQuery = actionCreatorFactory('SET_DATA_SOURCE_TYPE_SEARCH_QUERY').create(); - export const setDataSourceName = actionCreatorFactory('SET_DATA_SOURCE_NAME').create(); - export const setIsDefault = actionCreatorFactory('SET_IS_DEFAULT').create(); -export type Action = - | UpdateNavIndexAction - | ActionOf - | ActionOf - | ActionOf - | ActionOf - | ActionOf; - -type ThunkResult = ThunkAction; +export interface DataSourceTypesLoadedPayload { + plugins: DataSourcePluginMeta[]; + categories: DataSourcePluginCategory[]; +} export function loadDataSources(): ThunkResult { return async dispatch => { @@ -84,11 +70,12 @@ export function addDataSource(plugin: DataSourcePluginMeta): ThunkResult { }; } -export function loadDataSourceTypes(): ThunkResult { +export function loadDataSourcePlugins(): ThunkResult { return async dispatch => { - dispatch(dataSourceTypesLoad()); - const result = await getBackendSrv().get('/api/plugins', { enabled: 1, type: 'datasource' }); - dispatch(dataSourceTypesLoaded(result as DataSourcePluginMeta[])); + dispatch(dataSourcePluginsLoad()); + const plugins = await getBackendSrv().get('/api/plugins', { enabled: 1, type: 'datasource' }); + const categories = buildCategories(plugins); + dispatch(dataSourcePluginsLoaded({ plugins, categories })); }; } diff --git a/public/app/features/datasources/state/buildCategories.test.ts b/public/app/features/datasources/state/buildCategories.test.ts new file mode 100644 index 00000000000..f445fd53085 --- /dev/null +++ b/public/app/features/datasources/state/buildCategories.test.ts @@ -0,0 +1,56 @@ +import { buildCategories } from './buildCategories'; +import { getMockPlugin } from '../../plugins/__mocks__/pluginMocks'; +import { DataSourcePluginMeta } from '@grafana/data'; + +const plugins: DataSourcePluginMeta[] = [ + { + ...getMockPlugin({ id: 'graphite' }), + category: 'tsdb', + }, + { + ...getMockPlugin({ id: 'prometheus' }), + category: 'tsdb', + }, + { + ...getMockPlugin({ id: 'elasticsearch' }), + category: 'logging', + }, + { + ...getMockPlugin({ id: 'loki' }), + category: 'logging', + }, + { + ...getMockPlugin({ id: 'azure' }), + category: 'cloud', + }, +]; + +describe('buildCategories', () => { + const categories = buildCategories(plugins); + + it('should group plugins into categories', () => { + expect(categories.length).toBe(6); + expect(categories[0].title).toBe('Time series databases'); + expect(categories[0].plugins.length).toBe(2); + expect(categories[1].title).toBe('Logging & document databases'); + }); + + it('should sort plugins according to hard coded sorting rules', () => { + expect(categories[1].plugins[0].id).toBe('loki'); + }); + + it('should add phantom plugin for Grafana cloud', () => { + expect(categories[3].title).toBe('Cloud'); + expect(categories[3].plugins.length).toBe(2); + expect(categories[3].plugins[1].id).toBe('gcloud'); + }); + + it('should set module to phantom on phantom plugins', () => { + expect(categories[4].plugins[0].module).toBe('phantom'); + }); + + it('should add enterprise phantom plugins', () => { + expect(categories[4].title).toBe('Enterprise plugins'); + expect(categories[4].plugins.length).toBe(5); + }); +}); diff --git a/public/app/features/datasources/state/buildCategories.ts b/public/app/features/datasources/state/buildCategories.ts new file mode 100644 index 00000000000..9ff5341e405 --- /dev/null +++ b/public/app/features/datasources/state/buildCategories.ts @@ -0,0 +1,175 @@ +import { DataSourcePluginMeta, PluginType } from '@grafana/data'; +import { DataSourcePluginCategory } from 'app/types'; + +export function buildCategories(plugins: DataSourcePluginMeta[]): DataSourcePluginCategory[] { + const categories: DataSourcePluginCategory[] = [ + { id: 'tsdb', title: 'Time series databases', plugins: [] }, + { id: 'logging', title: 'Logging & document databases', plugins: [] }, + { id: 'sql', title: 'SQL', plugins: [] }, + { id: 'cloud', title: 'Cloud', plugins: [] }, + { id: 'enterprise', title: 'Enterprise plugins', plugins: [] }, + { id: 'other', title: 'Others', plugins: [] }, + ]; + + const categoryIndex: Record = {}; + const pluginIndex: Record = {}; + const enterprisePlugins = getEnterprisePhantomPlugins(); + + // build indices + for (const category of categories) { + categoryIndex[category.id] = category; + } + + for (const plugin of plugins) { + // Force category for enterprise plugins + if (enterprisePlugins.find(item => item.id === plugin.id)) { + plugin.category = 'enterprise'; + } + + // Fix link name + if (plugin.info.links) { + for (const link of plugin.info.links) { + link.name = 'Learn more'; + } + } + + const category = categories.find(item => item.id === plugin.category) || categoryIndex['other']; + category.plugins.push(plugin); + // add to plugin index + pluginIndex[plugin.id] = plugin; + } + + for (const category of categories) { + // add phantom plugin + if (category.id === 'cloud') { + category.plugins.push(getGrafanaCloudPhantomPlugin()); + } + + // add phantom plugins + if (category.id === 'enterprise') { + for (const plugin of enterprisePlugins) { + if (!pluginIndex[plugin.id]) { + category.plugins.push(plugin); + } + } + } + + sortPlugins(category.plugins); + } + + return categories; +} + +function sortPlugins(plugins: DataSourcePluginMeta[]) { + const sortingRules: { [id: string]: number } = { + prometheus: 100, + graphite: 95, + loki: 90, + mysql: 80, + postgres: 79, + gcloud: -1, + }; + + plugins.sort((a, b) => { + const aSort = sortingRules[a.id] || 0; + const bSort = sortingRules[b.id] || 0; + if (aSort > bSort) { + return -1; + } + if (aSort < bSort) { + return 1; + } + + return a.name > b.name ? -1 : 1; + }); +} + +function getEnterprisePhantomPlugins(): DataSourcePluginMeta[] { + return [ + getPhantomPlugin({ + id: 'grafana-splunk-datasource', + name: 'Splunk', + description: 'Visualize & explore Splunk logs', + imgUrl: 'public/img/plugins/splunk_logo_128.png', + }), + getPhantomPlugin({ + id: 'grafana-oracle-datasource', + name: 'Oracle', + description: 'Visualize & explore Oracle SQL', + imgUrl: 'public/img/plugins/oracle.png', + }), + getPhantomPlugin({ + id: 'grafana-dynatrace-datasource', + name: 'Dynatrace', + description: 'Visualize & explore Dynatrace data', + imgUrl: 'public/img/plugins/dynatrace.png', + }), + getPhantomPlugin({ + id: 'grafana-servicenow-datasource', + description: 'ServiceNow integration & data source', + name: 'ServiceNow', + imgUrl: 'public/img/plugins/servicenow.svg', + }), + getPhantomPlugin({ + id: 'grafana-datadog-datasource', + description: 'DataDog integration & data source', + name: 'DataDog', + imgUrl: 'public/img/plugins/datadog.png', + }), + ]; +} + +function getGrafanaCloudPhantomPlugin(): DataSourcePluginMeta { + return { + id: 'gcloud', + name: 'Grafana Cloud', + type: PluginType.datasource, + module: 'phantom', + baseUrl: '', + info: { + description: 'Hosted Graphite, Prometheus and Loki', + logos: { small: 'public/img/grafana_icon.svg', large: 'asd' }, + author: { name: 'Grafana Labs' }, + links: [ + { + url: 'https://grafana.com/products/cloud/', + name: 'Learn more', + }, + ], + screenshots: [], + updated: '2019-05-10', + version: '1.0.0', + }, + }; +} + +interface GetPhantomPluginOptions { + id: string; + name: string; + description: string; + imgUrl: string; +} + +function getPhantomPlugin(options: GetPhantomPluginOptions): DataSourcePluginMeta { + return { + id: options.id, + name: options.name, + type: PluginType.datasource, + module: 'phantom', + baseUrl: '', + info: { + description: options.description, + logos: { small: options.imgUrl, large: options.imgUrl }, + author: { name: 'Grafana Labs' }, + links: [ + { + url: 'https://grafana.com/grafana/plugins/' + options.id, + name: 'Install now', + }, + ], + screenshots: [], + updated: '2019-05-10', + version: '1.0.0', + }, + }; +} diff --git a/public/app/features/datasources/state/reducers.test.ts b/public/app/features/datasources/state/reducers.test.ts index 63495ba0f0a..61e79c94de9 100644 --- a/public/app/features/datasources/state/reducers.test.ts +++ b/public/app/features/datasources/state/reducers.test.ts @@ -5,8 +5,8 @@ import { dataSourceLoaded, setDataSourcesSearchQuery, setDataSourcesLayoutMode, - dataSourceTypesLoad, - dataSourceTypesLoaded, + dataSourcePluginsLoad, + dataSourcePluginsLoaded, setDataSourceTypeSearchQuery, dataSourceMetaLoaded, setDataSourceName, @@ -76,12 +76,12 @@ describe('dataSourcesReducer', () => { describe('when dataSourceTypesLoad is dispatched', () => { it('then state should be correct', () => { - const state: DataSourcesState = { ...initialState, dataSourceTypes: [mockPlugin()] }; + const state: DataSourcesState = { ...initialState, plugins: [mockPlugin()] }; reducerTester() .givenReducer(dataSourcesReducer, state) - .whenActionIsDispatched(dataSourceTypesLoad()) - .thenStateShouldEqual({ ...initialState, dataSourceTypes: [], isLoadingDataSources: true }); + .whenActionIsDispatched(dataSourcePluginsLoad()) + .thenStateShouldEqual({ ...initialState, isLoadingDataSources: true }); }); }); @@ -92,8 +92,8 @@ describe('dataSourcesReducer', () => { reducerTester() .givenReducer(dataSourcesReducer, state) - .whenActionIsDispatched(dataSourceTypesLoaded(dataSourceTypes)) - .thenStateShouldEqual({ ...initialState, dataSourceTypes, isLoadingDataSources: false }); + .whenActionIsDispatched(dataSourcePluginsLoaded({ plugins: dataSourceTypes, categories: [] })) + .thenStateShouldEqual({ ...initialState, plugins: dataSourceTypes, isLoadingDataSources: false }); }); }); diff --git a/public/app/features/datasources/state/reducers.ts b/public/app/features/datasources/state/reducers.ts index 4871f4f9be2..52361c1823a 100644 --- a/public/app/features/datasources/state/reducers.ts +++ b/public/app/features/datasources/state/reducers.ts @@ -5,8 +5,8 @@ import { dataSourcesLoaded, setDataSourcesSearchQuery, setDataSourcesLayoutMode, - dataSourceTypesLoad, - dataSourceTypesLoaded, + dataSourcePluginsLoad, + dataSourcePluginsLoaded, setDataSourceTypeSearchQuery, dataSourceMetaLoaded, setDataSourceName, @@ -17,11 +17,12 @@ import { reducerFactory } from 'app/core/redux'; export const initialState: DataSourcesState = { dataSources: [], + plugins: [], + categories: [], dataSource: {} as DataSourceSettings, layoutMode: LayoutModes.List, searchQuery: '', dataSourcesCount: 0, - dataSourceTypes: [], dataSourceTypeSearchQuery: '', hasFetched: false, isLoadingDataSources: false, @@ -51,14 +52,15 @@ export const dataSourcesReducer = reducerFactory(initialState) mapper: (state, action) => ({ ...state, layoutMode: action.payload }), }) .addMapper({ - filter: dataSourceTypesLoad, - mapper: state => ({ ...state, dataSourceTypes: [], isLoadingDataSources: true }), + filter: dataSourcePluginsLoad, + mapper: state => ({ ...state, plugins: [], isLoadingDataSources: true }), }) .addMapper({ - filter: dataSourceTypesLoaded, + filter: dataSourcePluginsLoaded, mapper: (state, action) => ({ ...state, - dataSourceTypes: action.payload, + plugins: action.payload.plugins, + categories: action.payload.categories, isLoadingDataSources: false, }), }) diff --git a/public/app/features/datasources/state/selectors.ts b/public/app/features/datasources/state/selectors.ts index d9b939e272f..9a0a6a0ef42 100644 --- a/public/app/features/datasources/state/selectors.ts +++ b/public/app/features/datasources/state/selectors.ts @@ -10,10 +10,10 @@ export const getDataSources = (state: DataSourcesState) => { }); }; -export const getDataSourceTypes = (state: DataSourcesState) => { +export const getDataSourcePlugins = (state: DataSourcesState) => { const regex = new RegExp(state.dataSourceTypeSearchQuery, 'i'); - return state.dataSourceTypes.filter((type: DataSourcePluginMeta) => { + return state.plugins.filter((type: DataSourcePluginMeta) => { return regex.test(type.name); }); }; diff --git a/public/app/features/plugins/__mocks__/pluginMocks.ts b/public/app/features/plugins/__mocks__/pluginMocks.ts index a0517eec91b..d45c5a1dfae 100644 --- a/public/app/features/plugins/__mocks__/pluginMocks.ts +++ b/public/app/features/plugins/__mocks__/pluginMocks.ts @@ -1,3 +1,4 @@ +import { defaultsDeep } from 'lodash'; import { PanelPluginMeta, PluginMeta, PluginType, PanelPlugin, PanelProps } from '@grafana/data'; import { ComponentType } from 'enzyme'; @@ -67,8 +68,8 @@ export const getPanelPlugin = ( return plugin; }; -export const getMockPlugin = () => { - return { +export function getMockPlugin(overrides?: Partial): PluginMeta { + const defaults: PluginMeta = { defaultNavUrl: 'some/url', enabled: false, hasUpdate: false, @@ -81,7 +82,7 @@ export const getMockPlugin = () => { description: 'pretty decent plugin', links: [{ name: 'project', url: 'one link' }], logos: { small: 'small/logo', large: 'large/logo' }, - screenshots: [{ path: `screenshot` }], + screenshots: [{ path: `screenshot`, name: 'test' }], updated: '2018-09-26', version: '1', }, @@ -91,5 +92,7 @@ export const getMockPlugin = () => { pinned: false, type: PluginType.panel, module: 'path/to/module', - } as PluginMeta; -}; + }; + + return defaultsDeep(overrides || {}, defaults) as PluginMeta; +} diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/plugin.json b/public/app/plugins/datasource/grafana-azure-monitor-datasource/plugin.json index b625a5bb272..ed2808a8dae 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/plugin.json +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/plugin.json @@ -16,7 +16,7 @@ "large": "img/logo.jpg" }, "links": [ - { "name": "Project site", "url": "https://github.com/grafana/azure-monitor-datasource" }, + { "name": "Learn more", "url": "https://github.com/grafana/azure-monitor-datasource" }, { "name": "Apache License", "url": "https://github.com/grafana/azure-monitor-datasource/blob/master/LICENSE" } ], "screenshots": [ diff --git a/public/app/plugins/datasource/graphite/plugin.json b/public/app/plugins/datasource/graphite/plugin.json index 5671bcd1939..d9a34fc199f 100644 --- a/public/app/plugins/datasource/graphite/plugin.json +++ b/public/app/plugins/datasource/graphite/plugin.json @@ -29,7 +29,7 @@ "large": "img/graphite_logo.png" }, "links": [ - { "name": "Graphite", "url": "https://graphiteapp.org/" }, + { "name": "Learn more", "url": "https://graphiteapp.org/" }, { "name": "Graphite 1.1 Release", "url": "https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old-dog-new-tricks/" diff --git a/public/app/plugins/datasource/prometheus/plugin.json b/public/app/plugins/datasource/prometheus/plugin.json index ba114454948..dcbeb2f98e1 100644 --- a/public/app/plugins/datasource/prometheus/plugin.json +++ b/public/app/plugins/datasource/prometheus/plugin.json @@ -39,7 +39,7 @@ }, "links": [ { - "name": "Prometheus", + "name": "Learn more", "url": "https://prometheus.io/" } ] diff --git a/public/app/types/datasources.ts b/public/app/types/datasources.ts index e036a0104dc..2db8bd2626a 100644 --- a/public/app/types/datasources.ts +++ b/public/app/types/datasources.ts @@ -7,9 +7,16 @@ export interface DataSourcesState { dataSourceTypeSearchQuery: string; layoutMode: LayoutMode; dataSourcesCount: number; - dataSourceTypes: DataSourcePluginMeta[]; dataSource: DataSourceSettings; dataSourceMeta: DataSourcePluginMeta; hasFetched: boolean; isLoadingDataSources: boolean; + plugins: DataSourcePluginMeta[]; + categories: DataSourcePluginCategory[]; +} + +export interface DataSourcePluginCategory { + id: string; + title: string; + plugins: DataSourcePluginMeta[]; } diff --git a/public/img/plugins/appdynamics.svg b/public/img/plugins/appdynamics.svg new file mode 100644 index 00000000000..36525b44890 --- /dev/null +++ b/public/img/plugins/appdynamics.svg @@ -0,0 +1,17 @@ + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/public/img/plugins/datadog.png b/public/img/plugins/datadog.png new file mode 100644 index 00000000000..723d7164977 Binary files /dev/null and b/public/img/plugins/datadog.png differ diff --git a/public/img/plugins/dynatrace.png b/public/img/plugins/dynatrace.png new file mode 100644 index 00000000000..f59bd57fdc1 Binary files /dev/null and b/public/img/plugins/dynatrace.png differ diff --git a/public/img/plugins/oracle.png b/public/img/plugins/oracle.png new file mode 100644 index 00000000000..0c432d21b5a Binary files /dev/null and b/public/img/plugins/oracle.png differ diff --git a/public/img/plugins/servicenow.svg b/public/img/plugins/servicenow.svg new file mode 100644 index 00000000000..555b6edbc9d --- /dev/null +++ b/public/img/plugins/servicenow.svg @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/public/img/plugins/splunk_logo_128.png b/public/img/plugins/splunk_logo_128.png new file mode 100644 index 00000000000..069fc38014f Binary files /dev/null and b/public/img/plugins/splunk_logo_128.png differ