Skip to content

Commit

Permalink
Merge branch 'data-sources-list-to-react'
Browse files Browse the repository at this point in the history
  • Loading branch information
torkelo committed Oct 2, 2018
2 parents 8e9c0a4 + 8fd1d8a commit f37a60d
Show file tree
Hide file tree
Showing 23 changed files with 831 additions and 129 deletions.
22 changes: 22 additions & 0 deletions public/app/features/datasources/DataSourceList.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { shallow } from 'enzyme';
import DataSourcesList from './DataSourcesList';
import { getMockDataSources } from './__mocks__/dataSourcesMocks';
import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';

const setup = () => {
const props = {
dataSources: getMockDataSources(3),
layoutMode: LayoutModes.Grid,
};

return shallow(<DataSourcesList {...props} />);
};

describe('Render', () => {
it('should render component', () => {
const wrapper = setup();

expect(wrapper).toMatchSnapshot();
});
});
23 changes: 23 additions & 0 deletions public/app/features/datasources/DataSourcesActionBar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import { shallow } from 'enzyme';
import { DataSourcesActionBar, Props } from './DataSourcesActionBar';
import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';

const setup = (propOverrides?: object) => {
const props: Props = {
layoutMode: LayoutModes.Grid,
searchQuery: '',
setDataSourcesLayoutMode: jest.fn(),
setDataSourcesSearchQuery: jest.fn(),
};

return shallow(<DataSourcesActionBar {...props} />);
};

describe('Render', () => {
it('should render component', () => {
const wrapper = setup();

expect(wrapper).toMatchSnapshot();
});
});
62 changes: 62 additions & 0 deletions public/app/features/datasources/DataSourcesActionBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import LayoutSelector, { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector';
import { setDataSourcesLayoutMode, setDataSourcesSearchQuery } from './state/actions';
import { getDataSourcesLayoutMode, getDataSourcesSearchQuery } from './state/selectors';

export interface Props {
searchQuery: string;
layoutMode: LayoutMode;
setDataSourcesLayoutMode: typeof setDataSourcesLayoutMode;
setDataSourcesSearchQuery: typeof setDataSourcesSearchQuery;
}

export class DataSourcesActionBar extends PureComponent<Props> {
onSearchQueryChange = event => {
this.props.setDataSourcesSearchQuery(event.target.value);
};

render() {
const { searchQuery, layoutMode, setDataSourcesLayoutMode } = this.props;

return (
<div className="page-action-bar">
<div className="gf-form gf-form--grow">
<label className="gf-form--has-input-icon">
<input
type="text"
className="gf-form-input width-20"
value={searchQuery}
onChange={this.onSearchQueryChange}
placeholder="Filter by name or type"
/>
<i className="gf-form-input-icon fa fa-search" />
</label>
<LayoutSelector
mode={layoutMode}
onLayoutModeChanged={(mode: LayoutMode) => setDataSourcesLayoutMode(mode)}
/>
</div>
<div className="page-action-bar__spacer" />
<a className="page-header__cta btn btn-success" href="datasources/new">
<i className="fa fa-plus" />
Add data source
</a>
</div>
);
}
}

function mapStateToProps(state) {
return {
searchQuery: getDataSourcesSearchQuery(state.dataSources),
layoutMode: getDataSourcesLayoutMode(state.dataSources),
};
}

const mapDispatchToProps = {
setDataSourcesLayoutMode,
setDataSourcesSearchQuery,
};

export default connect(mapStateToProps, mapDispatchToProps)(DataSourcesActionBar);
34 changes: 34 additions & 0 deletions public/app/features/datasources/DataSourcesList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, { PureComponent } from 'react';
import classNames from 'classnames/bind';
import DataSourcesListItem from './DataSourcesListItem';
import { DataSource } from 'app/types';
import { LayoutMode, LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';

export interface Props {
dataSources: DataSource[];
layoutMode: LayoutMode;
}

export class DataSourcesList extends PureComponent<Props> {
render() {
const { dataSources, layoutMode } = this.props;

const listStyle = classNames({
'card-section': true,
'card-list-layout-grid': layoutMode === LayoutModes.Grid,
'card-list-layout-list': layoutMode === LayoutModes.List,
});

return (
<section className={listStyle}>
<ol className="card-list">
{dataSources.map((dataSource, index) => {
return <DataSourcesListItem dataSource={dataSource} key={`${dataSource.id}-${index}`} />;
})}
</ol>
</section>
);
}
}

export default DataSourcesList;
20 changes: 20 additions & 0 deletions public/app/features/datasources/DataSourcesListItem.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import { shallow } from 'enzyme';
import DataSourcesListItem from './DataSourcesListItem';
import { getMockDataSource } from './__mocks__/dataSourcesMocks';

const setup = () => {
const props = {
dataSource: getMockDataSource(),
};

return shallow(<DataSourcesListItem {...props} />);
};

describe('Render', () => {
it('should render component', () => {
const wrapper = setup();

expect(wrapper).toMatchSnapshot();
});
});
35 changes: 35 additions & 0 deletions public/app/features/datasources/DataSourcesListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React, { PureComponent } from 'react';
import { DataSource } from 'app/types';

export interface Props {
dataSource: DataSource;
}

export class DataSourcesListItem extends PureComponent<Props> {
render() {
const { dataSource } = this.props;
return (
<li className="card-item-wrapper">
<a className="card-item" href={`datasources/edit/${dataSource.id}`}>
<div className="card-item-header">
<div className="card-item-type">{dataSource.type}</div>
</div>
<div className="card-item-body">
<figure className="card-item-figure">
<img src={dataSource.typeLogoUrl} />
</figure>
<div className="card-item-details">
<div className="card-item-name">
{dataSource.name}
{dataSource.isDefault && <span className="btn btn-secondary btn-mini">default</span>}
</div>
<div className="card-item-sub-name">{dataSource.url}</div>
</div>
</div>
</a>
</li>
);
}
}

export default DataSourcesListItem;
37 changes: 37 additions & 0 deletions public/app/features/datasources/DataSourcesListPage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import { shallow } from 'enzyme';
import { DataSourcesListPage, Props } from './DataSourcesListPage';
import { DataSource, NavModel } from 'app/types';
import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
import { getMockDataSources } from './__mocks__/dataSourcesMocks';

const setup = (propOverrides?: object) => {
const props: Props = {
dataSources: [] as DataSource[],
layoutMode: LayoutModes.Grid,
loadDataSources: jest.fn(),
navModel: {} as NavModel,
dataSourcesCount: 0,
};

Object.assign(props, propOverrides);

return shallow(<DataSourcesListPage {...props} />);
};

describe('Render', () => {
it('should render component', () => {
const wrapper = setup();

expect(wrapper).toMatchSnapshot();
});

it('should render action bar and datasources', () => {
const wrapper = setup({
dataSources: getMockDataSources(5),
dataSourcesCount: 5,
});

expect(wrapper).toMatchSnapshot();
});
});
76 changes: 76 additions & 0 deletions public/app/features/datasources/DataSourcesListPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { hot } from 'react-hot-loader';
import PageHeader from '../../core/components/PageHeader/PageHeader';
import DataSourcesActionBar from './DataSourcesActionBar';
import DataSourcesList from './DataSourcesList';
import { loadDataSources } from './state/actions';
import { getDataSources, getDataSourcesCount, getDataSourcesLayoutMode } from './state/selectors';
import { getNavModel } from '../../core/selectors/navModel';
import { DataSource, NavModel } from 'app/types';
import { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector';
import EmptyListCTA from '../../core/components/EmptyListCTA/EmptyListCTA';

export interface Props {
navModel: NavModel;
dataSources: DataSource[];
dataSourcesCount: number;
layoutMode: LayoutMode;
loadDataSources: typeof loadDataSources;
}

const emptyListModel = {
title: 'There are no data sources defined yet',
buttonIcon: 'gicon gicon-add-datasources',
buttonLink: 'datasources/new',
buttonTitle: 'Add data source',
proTip: 'You can also define data sources through configuration files.',
proTipLink: 'http://docs.grafana.org/administration/provisioning/#datasources?utm_source=grafana_ds_list',
proTipLinkTitle: 'Learn more',
proTipTarget: '_blank',
};

export class DataSourcesListPage extends PureComponent<Props> {
componentDidMount() {
this.fetchDataSources();
}

async fetchDataSources() {
return await this.props.loadDataSources();
}

render() {
const { dataSources, dataSourcesCount, navModel, layoutMode } = this.props;

return (
<div>
<PageHeader model={navModel} />
<div className="page-container page-body">
{dataSourcesCount === 0 ? (
<EmptyListCTA model={emptyListModel} />
) : (
[
<DataSourcesActionBar key="action-bar" />,
<DataSourcesList dataSources={dataSources} layoutMode={layoutMode} key="list" />,
]
)}
</div>
</div>
);
}
}

function mapStateToProps(state) {
return {
navModel: getNavModel(state.navIndex, 'datasources'),
dataSources: getDataSources(state.dataSources),
layoutMode: getDataSourcesLayoutMode(state.dataSources),
dataSourcesCount: getDataSourcesCount(state.dataSources),
};
}

const mapDispatchToProps = {
loadDataSources,
};

export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DataSourcesListPage));
45 changes: 45 additions & 0 deletions public/app/features/datasources/__mocks__/dataSourcesMocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { DataSource } from 'app/types';

export const getMockDataSources = (amount: number): DataSource[] => {
const dataSources = [];

for (let i = 0; i <= amount; i++) {
dataSources.push({
access: '',
basicAuth: false,
database: `database-${i}`,
id: i,
isDefault: false,
jsonData: { authType: 'credentials', defaultRegion: 'eu-west-2' },
name: `dataSource-${i}`,
orgId: 1,
password: '',
readOnly: false,
type: 'cloudwatch',
typeLogoUrl: 'public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png',
url: '',
user: '',
});
}

return dataSources;
};

export const getMockDataSource = (): DataSource => {
return {
access: '',
basicAuth: false,
database: '',
id: 13,
isDefault: false,
jsonData: { authType: 'credentials', defaultRegion: 'eu-west-2' },
name: 'gdev-cloudwatch',
orgId: 1,
password: '',
readOnly: false,
type: 'cloudwatch',
typeLogoUrl: 'public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png',
url: '',
user: '',
};
};
Loading

0 comments on commit f37a60d

Please sign in to comment.