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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user