mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
AddDatasourcePage: Refactoring & more Phantom plugins (#21261)
* WIP: New types and state refactoring * Added buildCategories & tests * Added phantom plugins * fixed tests * Minor changes, force enterprise plugins to enterprise category * Fixed tests
This commit is contained in:
parent
f93f1c4b51
commit
e78074992b
@ -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<Props> {
|
||||
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<Props> {
|
||||
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 (
|
||||
<List
|
||||
items={types}
|
||||
items={plugins}
|
||||
getItemKey={item => item.id.toString()}
|
||||
renderItem={item => (
|
||||
<DataSourceTypeCard
|
||||
@ -100,35 +63,21 @@ class NewDataSourcePage extends PureComponent<Props> {
|
||||
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 => (
|
||||
<div className="add-data-source-category" key={category.id}>
|
||||
<div className="add-data-source-category__header">{category.title}</div>
|
||||
{this.renderTypes(categories[category.id])}
|
||||
{this.renderPlugins(category.plugins)}
|
||||
</div>
|
||||
))}
|
||||
<div className="add-data-source-more">
|
||||
<a
|
||||
className="btn btn-inverse"
|
||||
href="https://grafana.com/plugins?type=datasource&utm_source=new-data-source"
|
||||
href="https://grafana.com/plugins?type=datasource&utm_source=grafana_add_ds"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
@ -140,7 +89,7 @@ class NewDataSourcePage extends PureComponent<Props> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { navModel, isLoading, searchQuery, dataSourceTypes } = this.props;
|
||||
const { navModel, isLoading, searchQuery, plugins } = this.props;
|
||||
|
||||
return (
|
||||
<Page navModel={navModel}>
|
||||
@ -162,8 +111,8 @@ class NewDataSourcePage extends PureComponent<Props> {
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
{searchQuery && this.renderTypes(dataSourceTypes)}
|
||||
{!searchQuery && this.renderGroupedList()}
|
||||
{searchQuery && this.renderPlugins(plugins)}
|
||||
{!searchQuery && this.renderCategories()}
|
||||
</div>
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
@ -179,15 +128,18 @@ interface DataSourceTypeCardProps {
|
||||
|
||||
const DataSourceTypeCard: FC<DataSourceTypeCardProps> = 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 (
|
||||
<div
|
||||
className="add-data-source-item"
|
||||
className={mainClassName}
|
||||
onClick={onClick}
|
||||
aria-label={e2e.pages.AddDataSource.selectors.dataSourcePlugins(plugin.name)}
|
||||
>
|
||||
@ -200,44 +152,20 @@ const DataSourceTypeCard: FC<DataSourceTypeCardProps> = props => {
|
||||
{learnMoreLink && (
|
||||
<a
|
||||
className="btn btn-inverse"
|
||||
href={`${learnMoreLink}?utm_source=grafana_add_ds`}
|
||||
href={`${learnMoreLink.url}?utm_source=grafana_add_ds`}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
onClick={onLearnMoreClick}
|
||||
>
|
||||
Learn more <i className="fa fa-external-link add-datasource-item-actions__btn-icon" />
|
||||
{learnMoreLink.name} <i className="fa fa-external-link add-datasource-item-actions__btn-icon" />
|
||||
</a>
|
||||
)}
|
||||
{canSelect && <button className="btn btn-primary">Select</button>}
|
||||
{!isPhantom && <button className="btn btn-primary">Select</button>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
|
@ -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",
|
||||
},
|
||||
],
|
||||
|
@ -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<DataSourceSettings>('LOAD_DATA_SOURCE').create();
|
||||
|
||||
export const dataSourcesLoaded = actionCreatorFactory<DataSourceSettings[]>('LOAD_DATA_SOURCES').create();
|
||||
|
||||
export const dataSourceMetaLoaded = actionCreatorFactory<DataSourcePluginMeta>('LOAD_DATA_SOURCE_META').create();
|
||||
|
||||
export const dataSourceTypesLoad = actionCreatorFactory('LOAD_DATA_SOURCE_TYPES').create();
|
||||
|
||||
export const dataSourceTypesLoaded = actionCreatorFactory<DataSourcePluginMeta[]>('LOADED_DATA_SOURCE_TYPES').create();
|
||||
|
||||
export const dataSourcePluginsLoad = actionCreatorFactory('LOAD_DATA_SOURCE_PLUGINS').create();
|
||||
export const dataSourcePluginsLoaded = actionCreatorFactory<DataSourceTypesLoadedPayload>(
|
||||
'LOADED_DATA_SOURCE_PLUGINS'
|
||||
).create();
|
||||
export const setDataSourcesSearchQuery = actionCreatorFactory<string>('SET_DATA_SOURCES_SEARCH_QUERY').create();
|
||||
|
||||
export const setDataSourcesLayoutMode = actionCreatorFactory<LayoutMode>('SET_DATA_SOURCES_LAYOUT_MODE').create();
|
||||
|
||||
export const setDataSourceTypeSearchQuery = actionCreatorFactory<string>('SET_DATA_SOURCE_TYPE_SEARCH_QUERY').create();
|
||||
|
||||
export const setDataSourceName = actionCreatorFactory<string>('SET_DATA_SOURCE_NAME').create();
|
||||
|
||||
export const setIsDefault = actionCreatorFactory<boolean>('SET_IS_DEFAULT').create();
|
||||
|
||||
export type Action =
|
||||
| UpdateNavIndexAction
|
||||
| ActionOf<DataSourceSettings>
|
||||
| ActionOf<DataSourceSettings[]>
|
||||
| ActionOf<DataSourcePluginMeta>
|
||||
| ActionOf<DataSourcePluginMeta[]>
|
||||
| ActionOf<LocationUpdate>;
|
||||
|
||||
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action>;
|
||||
export interface DataSourceTypesLoadedPayload {
|
||||
plugins: DataSourcePluginMeta[];
|
||||
categories: DataSourcePluginCategory[];
|
||||
}
|
||||
|
||||
export function loadDataSources(): ThunkResult<void> {
|
||||
return async dispatch => {
|
||||
@ -84,11 +70,12 @@ export function addDataSource(plugin: DataSourcePluginMeta): ThunkResult<void> {
|
||||
};
|
||||
}
|
||||
|
||||
export function loadDataSourceTypes(): ThunkResult<void> {
|
||||
export function loadDataSourcePlugins(): ThunkResult<void> {
|
||||
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 }));
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
175
public/app/features/datasources/state/buildCategories.ts
Normal file
175
public/app/features/datasources/state/buildCategories.ts
Normal file
@ -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<string, DataSourcePluginCategory> = {};
|
||||
const pluginIndex: Record<string, DataSourcePluginMeta> = {};
|
||||
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',
|
||||
},
|
||||
};
|
||||
}
|
@ -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 });
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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,
|
||||
}),
|
||||
})
|
||||
|
@ -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);
|
||||
});
|
||||
};
|
||||
|
@ -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>): 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;
|
||||
}
|
||||
|
@ -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": [
|
||||
|
@ -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/"
|
||||
|
@ -39,7 +39,7 @@
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"name": "Prometheus",
|
||||
"name": "Learn more",
|
||||
"url": "https://prometheus.io/"
|
||||
}
|
||||
]
|
||||
|
@ -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[];
|
||||
}
|
||||
|
17
public/img/plugins/appdynamics.svg
Normal file
17
public/img/plugins/appdynamics.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="45.155556mm" height="45.155556mm" viewBox="0 0 160 160" id="svg2" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs id="defs4"></defs>
|
||||
<metadata id="metadata7">
|
||||
|
||||
|
||||
image/svg+xml
|
||||
|
||||
|
||||
|
||||
|
||||
</metadata>
|
||||
<g id="layer1" transform="translate(-28.571445,-58.076511)">
|
||||
<rect style="opacity:1;fill:#43af49;fill-opacity:1;stroke:none;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" id="rect4229" width="160" height="160" x="28.571445" y="58.076511"></rect>
|
||||
<path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 31.214848,129.47656 8.574218,-0.24414 30.628907,-40.91992 9.433593,-6.529297 9.433594,6.529297 30.62695,40.91992 8.57617,0.24414 L 79.846221,31.941075 C 63.384998,64.975987 46.182247,99.463396 31.214848,129.47656 Z" transform="translate(28.571445,58.076511)" id="path4252"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/img/plugins/datadog.png
Normal file
BIN
public/img/plugins/datadog.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.8 KiB |
BIN
public/img/plugins/dynatrace.png
Normal file
BIN
public/img/plugins/dynatrace.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
public/img/plugins/oracle.png
Normal file
BIN
public/img/plugins/oracle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 73 KiB |
22
public/img/plugins/servicenow.svg
Normal file
22
public/img/plugins/servicenow.svg
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 500 150" style="enable-background:new 0 0 500 150;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#293E40;}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#81B5A1;}
|
||||
.st2{fill:#293E40;}
|
||||
</style>
|
||||
<path class="st0" d="M8.3,141V12.7h33.2v10.2c9.9-8.3,23-13.1,36-13.1c16.9,0,33.2,7.7,44.3,20.7c8.6,10.2,13.1,23,13.1,45V141
|
||||
h-34.4V73c1-8.9-1.6-17.5-7.3-24.2c-5.4-4.8-12.1-7.3-19.5-7c-12.1,0.3-23.6,6.1-30.9,15.6V141"/>
|
||||
<path class="st1" d="M223,122.5c-9.9,0.3-19.5-3.8-26.5-10.8c-7-7-10.8-16.6-10.8-26.5c-0.6-13.7,6.4-26.8,18.2-33.8
|
||||
c11.8-7.3,26.8-7.3,38.6,0c11.8,7,18.5,20.1,17.9,33.8c0.3,9.9-3.5,19.5-10.5,26.5C242.8,119,233.2,122.8,223,122.5 M223,9.9
|
||||
c-30.6,0-58.4,18.8-69.5,47.2c-11.5,28.7-4.5,61.2,17.9,82.3c5.4,5.1,13.7,5.7,19.5,1.3c18.8-14.4,45-14.4,63.8,0
|
||||
c6.1,4.5,14,3.8,19.5-1.3c22-21,29.3-53.3,18.2-82C281,29,253.6,10.2,223,9.9z"/>
|
||||
<polyline class="st0" points="365.2,141 339.7,141 288.7,12.7 322.8,12.7 350.9,86.1 378.3,12.7 406.7,12.7 433.8,86.1 461.9,12.7
|
||||
496,12.7 445.3,141 419.8,141 392.3,67.9 365.2,141 "/>
|
||||
<polyline class="st2" points="477.5,124.7 477.5,127.2 473,127.2 473,141 469.8,141 469.8,127.2 465.7,127.2 465.7,124.7
|
||||
477.5,124.7 "/>
|
||||
<polyline class="st2" points="488,134.3 493.8,124.7 496,124.7 496,141 493.1,141 493.1,131.7 489,138.1 486.8,138.1 482.9,131.7
|
||||
482.9,141 479.7,141 479.7,124.7 482,124.7 488,134.3 "/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/img/plugins/splunk_logo_128.png
Normal file
BIN
public/img/plugins/splunk_logo_128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
Loading…
Reference in New Issue
Block a user