mirror of
https://github.com/grafana/grafana.git
synced 2025-02-13 00:55:47 -06:00
4f0fa776be
* Refactor: Adds Redux Toolkit package * Refactor: Uses configureStore from Redux Toolkit * Refactor: Migrates applicationReducer * Refactor: Migrates appNotificationsReducer * Refactor: Migrates locationReducer * Refactor: Migrates navModelReducer * Refactor: Migrates teamsReducer and teamReducer * Refactor: Migrates cleanUpAction * Refactor: Migrates alertRulesReducer * Refactor: Cleans up recursiveCleanState * Refactor: Switched to Angular compatible reducers * Refactor: Migrates folderReducer * Refactor: Migrates dashboardReducer * Migrates panelEditorReducer * Refactor: Migrates dataSourcesReducer * Refactor: Migrates usersReducer * Refactor: Migrates organizationReducer * Refactor: Migrates pluginsReducer * Refactor: Migrates ldapReducer and ldapUserReducer * Refactor: Migrates apiKeysReducer * Refactor: Migrates exploreReducer and itemReducer * Refactor: Removes actionCreatorFactory and reducerFactory * Refactor: Moves mocks to test section * Docs: Removes sections about home grown framework * Update contribute/style-guides/redux.md Co-Authored-By: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Refactor: Cleans up some code * Refactor: Adds state typings * Refactor: Cleans up typings * Refactor: Adds comment about ImmerJs autoFreeze Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>
202 lines
6.1 KiB
TypeScript
202 lines
6.1 KiB
TypeScript
import React, { FC, PureComponent } from 'react';
|
|
import classNames from 'classnames';
|
|
import { connect } from 'react-redux';
|
|
import { hot } from 'react-hot-loader';
|
|
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 { DataSourcePluginCategory, StoreState } from 'app/types';
|
|
import { addDataSource, loadDataSourcePlugins } from './state/actions';
|
|
import { getDataSourcePlugins } from './state/selectors';
|
|
import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
|
|
import { setDataSourceTypeSearchQuery } from './state/reducers';
|
|
|
|
export interface Props {
|
|
navModel: NavModel;
|
|
plugins: DataSourcePluginMeta[];
|
|
categories: DataSourcePluginCategory[];
|
|
isLoading: boolean;
|
|
addDataSource: typeof addDataSource;
|
|
loadDataSourcePlugins: typeof loadDataSourcePlugins;
|
|
searchQuery: string;
|
|
setDataSourceTypeSearchQuery: typeof setDataSourceTypeSearchQuery;
|
|
}
|
|
|
|
class NewDataSourcePage extends PureComponent<Props> {
|
|
searchInput: HTMLElement;
|
|
|
|
componentDidMount() {
|
|
this.props.loadDataSourcePlugins();
|
|
this.searchInput.focus();
|
|
}
|
|
|
|
onDataSourceTypeClicked = (plugin: DataSourcePluginMeta) => {
|
|
this.props.addDataSource(plugin);
|
|
};
|
|
|
|
onSearchQueryChange = (value: string) => {
|
|
this.props.setDataSourceTypeSearchQuery(value);
|
|
};
|
|
|
|
renderPlugins(plugins: DataSourcePluginMeta[]) {
|
|
if (!plugins || !plugins.length) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<List
|
|
items={plugins}
|
|
getItemKey={item => item.id.toString()}
|
|
renderItem={item => (
|
|
<DataSourceTypeCard
|
|
plugin={item}
|
|
onClick={() => this.onDataSourceTypeClicked(item)}
|
|
onLearnMoreClick={this.onLearnMoreClick}
|
|
/>
|
|
)}
|
|
/>
|
|
);
|
|
}
|
|
|
|
onLearnMoreClick = (evt: React.SyntheticEvent<HTMLElement>) => {
|
|
evt.stopPropagation();
|
|
};
|
|
|
|
renderCategories() {
|
|
const { categories } = this.props;
|
|
|
|
return (
|
|
<>
|
|
{categories.map(category => (
|
|
<div className="add-data-source-category" key={category.id}>
|
|
<div className="add-data-source-category__header">{category.title}</div>
|
|
{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=grafana_add_ds"
|
|
target="_blank"
|
|
rel="noopener"
|
|
>
|
|
Find more data source plugins on grafana.com
|
|
</a>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
render() {
|
|
const { navModel, isLoading, searchQuery, plugins } = this.props;
|
|
|
|
return (
|
|
<Page navModel={navModel}>
|
|
<Page.Contents isLoading={isLoading}>
|
|
<div className="page-action-bar">
|
|
<div className="gf-form gf-form--grow">
|
|
<FilterInput
|
|
ref={elem => (this.searchInput = elem)}
|
|
labelClassName="gf-form--has-input-icon"
|
|
inputClassName="gf-form-input width-30"
|
|
value={searchQuery}
|
|
onChange={this.onSearchQueryChange}
|
|
placeholder="Filter by name or type"
|
|
/>
|
|
</div>
|
|
<div className="page-action-bar__spacer" />
|
|
<a className="btn btn-secondary" href="datasources">
|
|
Cancel
|
|
</a>
|
|
</div>
|
|
<div>
|
|
{searchQuery && this.renderPlugins(plugins)}
|
|
{!searchQuery && this.renderCategories()}
|
|
</div>
|
|
</Page.Contents>
|
|
</Page>
|
|
);
|
|
}
|
|
}
|
|
|
|
interface DataSourceTypeCardProps {
|
|
plugin: DataSourcePluginMeta;
|
|
onClick: () => void;
|
|
onLearnMoreClick: (evt: React.SyntheticEvent<HTMLElement>) => void;
|
|
}
|
|
|
|
const DataSourceTypeCard: FC<DataSourceTypeCardProps> = props => {
|
|
const { plugin, onLearnMoreClick } = props;
|
|
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] : null;
|
|
const mainClassName = classNames('add-data-source-item', {
|
|
'add-data-source-item--phantom': isPhantom,
|
|
});
|
|
|
|
return (
|
|
<div
|
|
className={mainClassName}
|
|
onClick={onClick}
|
|
aria-label={e2e.pages.AddDataSource.selectors.dataSourcePlugins(plugin.name)}
|
|
>
|
|
<img className="add-data-source-item-logo" src={plugin.info.logos.small} />
|
|
<div className="add-data-source-item-text-wrapper">
|
|
<span className="add-data-source-item-text">{plugin.name}</span>
|
|
{plugin.info.description && <span className="add-data-source-item-desc">{plugin.info.description}</span>}
|
|
</div>
|
|
<div className="add-data-source-item-actions">
|
|
{learnMoreLink && (
|
|
<a
|
|
className="btn btn-inverse"
|
|
href={`${learnMoreLink.url}?utm_source=grafana_add_ds`}
|
|
target="_blank"
|
|
rel="noopener"
|
|
onClick={onLearnMoreClick}
|
|
>
|
|
{learnMoreLink.name} <i className="fa fa-external-link add-datasource-item-actions__btn-icon" />
|
|
</a>
|
|
)}
|
|
{!isPhantom && <button className="btn btn-primary">Select</button>}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export function getNavModel(): NavModel {
|
|
const main = {
|
|
icon: 'gicon gicon-add-datasources',
|
|
id: 'datasource-new',
|
|
text: 'Add data source',
|
|
href: 'datasources/new',
|
|
subTitle: 'Choose a data source type',
|
|
};
|
|
|
|
return {
|
|
main: main,
|
|
node: main,
|
|
};
|
|
}
|
|
|
|
function mapStateToProps(state: StoreState) {
|
|
return {
|
|
navModel: getNavModel(),
|
|
plugins: getDataSourcePlugins(state.dataSources),
|
|
searchQuery: state.dataSources.dataSourceTypeSearchQuery,
|
|
categories: state.dataSources.categories,
|
|
isLoading: state.dataSources.isLoadingDataSources,
|
|
};
|
|
}
|
|
|
|
const mapDispatchToProps = {
|
|
addDataSource,
|
|
loadDataSourcePlugins,
|
|
setDataSourceTypeSearchQuery,
|
|
};
|
|
|
|
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(NewDataSourcePage));
|