grafana/public/app/features/datasources/NewDataSourcePage.tsx
Hugo Häggmark 4f0fa776be
Chore: Migrates reducers and actions to Redux Toolkit (#21287)
* 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>
2020-01-13 08:03:22 +01:00

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));