Data source list: Use Card component (#31326)

* Replace DataSourcesListItem with Card

* Add tests

* Remove unused styles

* Make card heading semi bold

* Make heading semi-bold

* Show type name instead of type id

* Fix key warning

* Update Card

* Fix tests

* Make typeName optional

* remove styling that was just a test

* Make typeName non-optional and fix tests

* Update list key

Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
Alex Khomenko
2021-02-22 14:02:10 +02:00
committed by GitHub
parent 6e549bc95d
commit 6db4b40d5b
26 changed files with 90 additions and 272 deletions

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { shallow } from 'enzyme';
import { render, screen } from '@testing-library/react';
import DataSourcesList from './DataSourcesList';
import { getMockDataSources } from './__mocks__/dataSourcesMocks';
import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
@@ -10,13 +10,20 @@ const setup = () => {
layoutMode: LayoutModes.Grid,
};
return shallow(<DataSourcesList {...props} />);
return render(<DataSourcesList {...props} />);
};
describe('Render', () => {
it('should render component', () => {
const wrapper = setup();
describe('DataSourcesList', () => {
it('should render list of datasources', () => {
setup();
expect(screen.getAllByRole('listitem')).toHaveLength(3);
expect(screen.getAllByRole('heading')).toHaveLength(3);
});
expect(wrapper).toMatchSnapshot();
it('should render all elements in the list item', () => {
setup();
expect(screen.getByRole('heading', { name: 'dataSource-0' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'dataSource-0 dataSource-0' })).toBeInTheDocument();
expect(screen.getByAltText('dataSource-0')).toBeInTheDocument();
});
});

View File

@@ -1,39 +1,50 @@
// Libraries
import React, { PureComponent } from 'react';
import classNames from 'classnames';
// Components
import DataSourcesListItem from './DataSourcesListItem';
import React, { FC } from 'react';
// Types
import { DataSourceSettings } from '@grafana/data';
import { LayoutMode, LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
import { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector';
import { Card, Tag, useStyles } from '@grafana/ui';
import { css } from 'emotion';
export interface Props {
dataSources: DataSourceSettings[];
layoutMode: LayoutMode;
}
export class DataSourcesList extends PureComponent<Props> {
render() {
const { dataSources, layoutMode } = this.props;
export const DataSourcesList: FC<Props> = ({ dataSources, layoutMode }) => {
const styles = useStyles(getStyles);
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>
);
}
}
return (
<ul className={styles.list}>
{dataSources.map((dataSource, index) => {
return (
<li key={dataSource.id}>
<Card heading={dataSource.name} href={`datasources/edit/${dataSource.id}`}>
<Card.Figure>
<img src={dataSource.typeLogoUrl} alt={dataSource.name} />
</Card.Figure>
<Card.Meta>
{[
dataSource.typeName,
dataSource.url,
dataSource.isDefault && <Tag key="default-tag" name={'default'} colorIndex={1} />,
]}
</Card.Meta>
</Card>
</li>
);
})}
</ul>
);
};
export default DataSourcesList;
const getStyles = () => {
return {
list: css`
list-style: none;
`,
};
};

View File

@@ -1,20 +0,0 @@
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();
});
});

View File

@@ -1,36 +0,0 @@
import React, { PureComponent } from 'react';
import { DataSourceSettings } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
export interface Props {
dataSource: DataSourceSettings;
}
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} alt={dataSource.name} />
</figure>
<div className="card-item-details">
<div className="card-item-name" aria-label={selectors.pages.DataSources.dataSources(dataSource.name)}>
{dataSource.name}
{dataSource.isDefault && <span className="btn btn-secondary btn-small card-item-label">default</span>}
</div>
<div className="card-item-sub-name">{dataSource.url}</div>
</div>
</div>
</a>
</li>
);
}
}
export default DataSourcesListItem;

View File

@@ -49,11 +49,7 @@ const emptyListModel = {
export class DataSourcesListPage extends PureComponent<Props> {
componentDidMount() {
this.fetchDataSources();
}
async fetchDataSources() {
return await this.props.loadDataSources();
this.props.loadDataSources();
}
render() {

View File

@@ -3,7 +3,7 @@ import { DataSourceSettings } from '@grafana/data';
export const getMockDataSources = (amount: number) => {
const dataSources = [];
for (let i = 0; i <= amount; i++) {
for (let i = 0; i < amount; i++) {
dataSources.push({
access: '',
basicAuth: false,
@@ -37,6 +37,7 @@ export const getMockDataSource = (): DataSourceSettings => {
isDefault: false,
jsonData: { authType: 'credentials', defaultRegion: 'eu-west-2' },
name: 'gdev-cloudwatch',
typeName: 'Cloudwatch',
orgId: 1,
password: '',
readOnly: false,

View File

@@ -1,108 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render component 1`] = `
<section
className="card-section card-list-layout-grid"
>
<ol
className="card-list"
>
<DataSourcesListItem
dataSource={
Object {
"access": "",
"basicAuth": false,
"database": "database-0",
"id": 0,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "dataSource-0",
"orgId": 1,
"password": "",
"readOnly": false,
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"url": "",
"user": "",
}
}
key="0-0"
/>
<DataSourcesListItem
dataSource={
Object {
"access": "",
"basicAuth": false,
"database": "database-1",
"id": 1,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "dataSource-1",
"orgId": 1,
"password": "",
"readOnly": false,
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"url": "",
"user": "",
}
}
key="1-1"
/>
<DataSourcesListItem
dataSource={
Object {
"access": "",
"basicAuth": false,
"database": "database-2",
"id": 2,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "dataSource-2",
"orgId": 1,
"password": "",
"readOnly": false,
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"url": "",
"user": "",
}
}
key="2-2"
/>
<DataSourcesListItem
dataSource={
Object {
"access": "",
"basicAuth": false,
"database": "database-3",
"id": 3,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "dataSource-3",
"orgId": 1,
"password": "",
"readOnly": false,
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"url": "",
"user": "",
}
}
key="3-3"
/>
</ol>
</section>
`;

View File

@@ -1,47 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render component 1`] = `
<li
className="card-item-wrapper"
>
<a
className="card-item"
href="datasources/edit/13"
>
<div
className="card-item-header"
>
<div
className="card-item-type"
>
cloudwatch
</div>
</div>
<div
className="card-item-body"
>
<figure
className="card-item-figure"
>
<img
alt="gdev-cloudwatch"
src="public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png"
/>
</figure>
<div
className="card-item-details"
>
<div
aria-label="Data source list item gdev-cloudwatch"
className="card-item-name"
>
gdev-cloudwatch
</div>
<div
className="card-item-sub-name"
/>
</div>
</div>
</a>
</li>
`;

View File

@@ -125,25 +125,6 @@ exports[`Render should render action bar and datasources 1`] = `
"url": "",
"user": "",
},
Object {
"access": "",
"basicAuth": false,
"database": "database-5",
"id": 5,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "dataSource-5",
"orgId": 1,
"password": "",
"readOnly": false,
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"url": "",
"user": "",
},
]
}
key="list"

View File

@@ -7,6 +7,7 @@ export function createDatasourceSettings<T>(jsonData: T): DataSourceSettings<T>
name: 'datasource-test',
typeLogoUrl: '',
type: 'datasource',
typeName: 'Datasource',
access: 'server',
url: 'http://localhost',
password: '',

View File

@@ -48,6 +48,7 @@ exports[`Render should render alpha info text 1`] = `
"secureJsonFields": Object {},
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"typeName": "Cloudwatch",
"url": "",
"user": "",
"withCredentials": false,
@@ -81,6 +82,7 @@ exports[`Render should render alpha info text 1`] = `
"secureJsonFields": Object {},
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"typeName": "Cloudwatch",
"url": "",
"user": "",
"withCredentials": false,
@@ -198,6 +200,7 @@ exports[`Render should render beta info text 1`] = `
"secureJsonFields": Object {},
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"typeName": "Cloudwatch",
"url": "",
"user": "",
"withCredentials": false,
@@ -257,6 +260,7 @@ exports[`Render should render component 1`] = `
"secureJsonFields": Object {},
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"typeName": "Cloudwatch",
"url": "",
"user": "",
"withCredentials": false,
@@ -321,6 +325,7 @@ exports[`Render should render is ready only message 1`] = `
"secureJsonFields": Object {},
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"typeName": "Cloudwatch",
"url": "",
"user": "",
"withCredentials": false,
@@ -354,6 +359,7 @@ exports[`Render should render is ready only message 1`] = `
"secureJsonFields": Object {},
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"typeName": "Cloudwatch",
"url": "",
"user": "",
"withCredentials": false,

View File

@@ -83,6 +83,7 @@ export function getDataSourceLoadingNav(pageName: string): NavModel {
password: '',
readOnly: false,
type: 'Loading',
typeName: 'Loading',
typeLogoUrl: 'public/img/icn-datasource.svg',
url: '',
user: '',

View File

@@ -40,7 +40,7 @@ const mockPlugin = () =>
describe('dataSourcesReducer', () => {
describe('when dataSourcesLoaded is dispatched', () => {
it('then state should be correct', () => {
const dataSources = getMockDataSources(0);
const dataSources = getMockDataSources(1);
reducerTester<DataSourcesState>()
.givenReducer(dataSourcesReducer, initialState)