mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Datasources: Refactor the list page (#51438)
* refactor(Data Sources): rename file to follow naming convention * refactor: use react-redux hooks for interacting with the store * tests: update data-sources list related test files * refactor: extract datasource list page contents * refactor: pass dataSources to the DataSourcesList as a prop * refactor: use proper typing for navIndex mocks
This commit is contained in:
parent
689639cdb0
commit
99de3313f7
22560
.betterer.results
22560
.betterer.results
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,7 @@ import { locationService } from '@grafana/runtime';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
|
||||
import DataConnectionsPage from './DataConnectionsPage';
|
||||
import navIndex from './__mocks__/store.navIndex.mock';
|
||||
import { navIndex } from './__mocks__/store.navIndex.mock';
|
||||
import { ROUTE_BASE_ID, ROUTES } from './constants';
|
||||
|
||||
const renderPage = (path = `/${ROUTE_BASE_ID}`): RenderResult => {
|
||||
|
@ -1,8 +1,10 @@
|
||||
export default {
|
||||
import { NavIndex, NavSection } from '@grafana/data';
|
||||
|
||||
export const navIndex: NavIndex = {
|
||||
dashboards: {
|
||||
id: 'dashboards',
|
||||
text: 'Dashboards',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Manage dashboards and folders',
|
||||
icon: 'apps',
|
||||
url: '/dashboards',
|
||||
@ -74,7 +76,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'dashboards',
|
||||
text: 'Dashboards',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Manage dashboards and folders',
|
||||
icon: 'apps',
|
||||
url: '/dashboards',
|
||||
@ -147,7 +149,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'dashboards',
|
||||
text: 'Dashboards',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Manage dashboards and folders',
|
||||
icon: 'apps',
|
||||
url: '/dashboards',
|
||||
@ -220,7 +222,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'dashboards',
|
||||
text: 'Dashboards',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Manage dashboards and folders',
|
||||
icon: 'apps',
|
||||
url: '/dashboards',
|
||||
@ -293,7 +295,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'dashboards',
|
||||
text: 'Dashboards',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Manage dashboards and folders',
|
||||
icon: 'apps',
|
||||
url: '/dashboards',
|
||||
@ -366,7 +368,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'alerting',
|
||||
text: 'Alerting',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Alert rules and notifications',
|
||||
icon: 'bell',
|
||||
url: '/alerting/list',
|
||||
@ -436,7 +438,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'dashboards',
|
||||
text: 'Dashboards',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Manage dashboards and folders',
|
||||
icon: 'apps',
|
||||
url: '/dashboards',
|
||||
@ -512,7 +514,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'dashboards',
|
||||
text: 'Dashboards',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Manage dashboards and folders',
|
||||
icon: 'apps',
|
||||
url: '/dashboards',
|
||||
@ -588,7 +590,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'dashboards',
|
||||
text: 'Dashboards',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Manage dashboards and folders',
|
||||
icon: 'apps',
|
||||
url: '/dashboards',
|
||||
@ -661,7 +663,7 @@ export default {
|
||||
explore: {
|
||||
id: 'explore',
|
||||
text: 'Explore',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Explore your data',
|
||||
icon: 'compass',
|
||||
url: '/explore',
|
||||
@ -670,7 +672,7 @@ export default {
|
||||
alerting: {
|
||||
id: 'alerting',
|
||||
text: 'Alerting',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Alert rules and notifications',
|
||||
icon: 'bell',
|
||||
url: '/alerting/list',
|
||||
@ -737,7 +739,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'alerting',
|
||||
text: 'Alerting',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Alert rules and notifications',
|
||||
icon: 'bell',
|
||||
url: '/alerting/list',
|
||||
@ -805,7 +807,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'alerting',
|
||||
text: 'Alerting',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Alert rules and notifications',
|
||||
icon: 'bell',
|
||||
url: '/alerting/list',
|
||||
@ -873,7 +875,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'alerting',
|
||||
text: 'Alerting',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Alert rules and notifications',
|
||||
icon: 'bell',
|
||||
url: '/alerting/list',
|
||||
@ -941,7 +943,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'alerting',
|
||||
text: 'Alerting',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Alert rules and notifications',
|
||||
icon: 'bell',
|
||||
url: '/alerting/list',
|
||||
@ -1009,7 +1011,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'alerting',
|
||||
text: 'Alerting',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Alert rules and notifications',
|
||||
icon: 'bell',
|
||||
url: '/alerting/list',
|
||||
@ -1077,7 +1079,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'alerting',
|
||||
text: 'Alerting',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Alert rules and notifications',
|
||||
icon: 'bell',
|
||||
url: '/alerting/list',
|
||||
@ -1148,7 +1150,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'alerting',
|
||||
text: 'Alerting',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
subTitle: 'Alert rules and notifications',
|
||||
icon: 'bell',
|
||||
url: '/alerting/list',
|
||||
@ -1211,7 +1213,7 @@ export default {
|
||||
'data-connections': {
|
||||
id: 'data-connections',
|
||||
text: 'Data Connections',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
icon: 'link',
|
||||
url: '/data-connections',
|
||||
sortWeight: -1500,
|
||||
@ -1259,7 +1261,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'data-connections',
|
||||
text: 'Data Connections',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
icon: 'link',
|
||||
url: '/data-connections',
|
||||
sortWeight: -1500,
|
||||
@ -1304,7 +1306,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'data-connections',
|
||||
text: 'Data Connections',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
icon: 'link',
|
||||
url: '/data-connections',
|
||||
sortWeight: -1500,
|
||||
@ -1349,7 +1351,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'data-connections',
|
||||
text: 'Data Connections',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
icon: 'link',
|
||||
url: '/data-connections',
|
||||
sortWeight: -1500,
|
||||
@ -1394,7 +1396,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'data-connections',
|
||||
text: 'Data Connections',
|
||||
section: 'core',
|
||||
section: NavSection.Core,
|
||||
icon: 'link',
|
||||
url: '/data-connections',
|
||||
sortWeight: -1500,
|
||||
@ -1433,7 +1435,7 @@ export default {
|
||||
'plugin-page-basic-app': {
|
||||
id: 'plugin-page-basic-app',
|
||||
text: 'Basic App',
|
||||
section: 'plugin',
|
||||
section: NavSection.Plugin,
|
||||
img: 'public/plugins/basic-app/img/logo.svg',
|
||||
url: '/a/basic-app/one',
|
||||
sortWeight: -1400,
|
||||
@ -1467,7 +1469,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'plugin-page-grafana-synthetic-monitoring-app',
|
||||
text: 'Synthetic Monitoring',
|
||||
section: 'plugin',
|
||||
section: NavSection.Plugin,
|
||||
img: 'public/plugins/grafana-synthetic-monitoring-app/img/logo.svg',
|
||||
url: '/a/grafana-synthetic-monitoring-app/home',
|
||||
sortWeight: -1400,
|
||||
@ -1502,7 +1504,7 @@ export default {
|
||||
'plugin-page-cloudflare-app': {
|
||||
id: 'plugin-page-cloudflare-app',
|
||||
text: 'Cloudflare Grafana App',
|
||||
section: 'plugin',
|
||||
section: NavSection.Plugin,
|
||||
img: 'public/plugins/cloudflare-app/img/cf_icon.png',
|
||||
sortWeight: -1400,
|
||||
children: [
|
||||
@ -1519,7 +1521,7 @@ export default {
|
||||
'plugin-page-grafana-easystart-app': {
|
||||
id: 'plugin-page-grafana-easystart-app',
|
||||
text: 'Integrations and Connections',
|
||||
section: 'plugin',
|
||||
section: NavSection.Plugin,
|
||||
img: 'public/plugins/grafana-easystart-app/img/logo.svg',
|
||||
url: '/a/grafana-easystart-app',
|
||||
sortWeight: -1400,
|
||||
@ -1527,7 +1529,7 @@ export default {
|
||||
'plugin-page-redis-explorer-app': {
|
||||
id: 'plugin-page-redis-explorer-app',
|
||||
text: 'Redis Explorer',
|
||||
section: 'plugin',
|
||||
section: NavSection.Plugin,
|
||||
img: 'public/plugins/redis-explorer-app/img/logo.svg',
|
||||
url: '/a/redis-explorer-app/',
|
||||
sortWeight: -1400,
|
||||
@ -1567,7 +1569,7 @@ export default {
|
||||
'plugin-page-grafana-synthetic-monitoring-app': {
|
||||
id: 'plugin-page-grafana-synthetic-monitoring-app',
|
||||
text: 'Synthetic Monitoring',
|
||||
section: 'plugin',
|
||||
section: NavSection.Plugin,
|
||||
img: 'public/plugins/grafana-synthetic-monitoring-app/img/logo.svg',
|
||||
url: '/a/grafana-synthetic-monitoring-app/home',
|
||||
sortWeight: -1400,
|
||||
@ -1601,7 +1603,7 @@ export default {
|
||||
'plugin-page-grafana-k6-app': {
|
||||
id: 'plugin-page-grafana-k6-app',
|
||||
text: 'k6 Cloud App',
|
||||
section: 'plugin',
|
||||
section: NavSection.Plugin,
|
||||
img: 'public/plugins/grafana-k6-app/img/logo.svg',
|
||||
url: '/a/grafana-k6-app',
|
||||
sortWeight: -1400,
|
||||
@ -1609,7 +1611,7 @@ export default {
|
||||
cfg: {
|
||||
id: 'cfg',
|
||||
text: 'Configuration',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
subTitle: 'Organization: Main Org.',
|
||||
icon: 'cog',
|
||||
url: '/datasources',
|
||||
@ -1668,7 +1670,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'cfg',
|
||||
text: 'Configuration',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
subTitle: 'Organization: Main Org.',
|
||||
icon: 'cog',
|
||||
url: '/datasources',
|
||||
@ -1728,7 +1730,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'cfg',
|
||||
text: 'Configuration',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
subTitle: 'Organization: Main Org.',
|
||||
icon: 'cog',
|
||||
url: '/datasources',
|
||||
@ -1788,7 +1790,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'cfg',
|
||||
text: 'Configuration',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
subTitle: 'Organization: Main Org.',
|
||||
icon: 'cog',
|
||||
url: '/datasources',
|
||||
@ -1848,7 +1850,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'cfg',
|
||||
text: 'Configuration',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
subTitle: 'Organization: Main Org.',
|
||||
icon: 'cog',
|
||||
url: '/datasources',
|
||||
@ -1908,7 +1910,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'cfg',
|
||||
text: 'Configuration',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
subTitle: 'Organization: Main Org.',
|
||||
icon: 'cog',
|
||||
url: '/datasources',
|
||||
@ -1968,7 +1970,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'cfg',
|
||||
text: 'Configuration',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
subTitle: 'Organization: Main Org.',
|
||||
icon: 'cog',
|
||||
url: '/datasources',
|
||||
@ -2022,7 +2024,7 @@ export default {
|
||||
admin: {
|
||||
id: 'admin',
|
||||
text: 'Server Admin',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
subTitle: 'Manage all users and orgs',
|
||||
icon: 'shield',
|
||||
url: '/admin/users',
|
||||
@ -2069,7 +2071,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'admin',
|
||||
text: 'Server Admin',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
subTitle: 'Manage all users and orgs',
|
||||
icon: 'shield',
|
||||
url: '/admin/users',
|
||||
@ -2117,7 +2119,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'admin',
|
||||
text: 'Server Admin',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
subTitle: 'Manage all users and orgs',
|
||||
icon: 'shield',
|
||||
url: '/admin/users',
|
||||
@ -2165,7 +2167,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'admin',
|
||||
text: 'Server Admin',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
subTitle: 'Manage all users and orgs',
|
||||
icon: 'shield',
|
||||
url: '/admin/users',
|
||||
@ -2213,7 +2215,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'admin',
|
||||
text: 'Server Admin',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
subTitle: 'Manage all users and orgs',
|
||||
icon: 'shield',
|
||||
url: '/admin/users',
|
||||
@ -2261,7 +2263,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'admin',
|
||||
text: 'Server Admin',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
subTitle: 'Manage all users and orgs',
|
||||
icon: 'shield',
|
||||
url: '/admin/users',
|
||||
@ -2304,7 +2306,7 @@ export default {
|
||||
profile: {
|
||||
id: 'profile',
|
||||
text: 'admin',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
img: '/avatar/46d229b033af06a191ff2267bca9ae56',
|
||||
url: '/profile',
|
||||
sortWeight: -1100,
|
||||
@ -2345,7 +2347,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'profile',
|
||||
text: 'admin',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
img: '/avatar/46d229b033af06a191ff2267bca9ae56',
|
||||
url: '/profile',
|
||||
sortWeight: -1100,
|
||||
@ -2387,7 +2389,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'profile',
|
||||
text: 'admin',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
img: '/avatar/46d229b033af06a191ff2267bca9ae56',
|
||||
url: '/profile',
|
||||
sortWeight: -1100,
|
||||
@ -2429,7 +2431,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'profile',
|
||||
text: 'admin',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
img: '/avatar/46d229b033af06a191ff2267bca9ae56',
|
||||
url: '/profile',
|
||||
sortWeight: -1100,
|
||||
@ -2473,7 +2475,7 @@ export default {
|
||||
parentItem: {
|
||||
id: 'profile',
|
||||
text: 'admin',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
img: '/avatar/46d229b033af06a191ff2267bca9ae56',
|
||||
url: '/profile',
|
||||
sortWeight: -1100,
|
||||
@ -2510,7 +2512,7 @@ export default {
|
||||
help: {
|
||||
id: 'help',
|
||||
text: 'Help',
|
||||
section: 'config',
|
||||
section: NavSection.Config,
|
||||
subTitle: 'Grafana v9.0.0-pre (abb5c6109a)',
|
||||
icon: 'question-circle',
|
||||
url: '#',
|
||||
|
@ -1,18 +1,30 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { LayoutModes } from '@grafana/data';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
import { DataSourcesState } from 'app/types';
|
||||
|
||||
import DataSourcesList from './DataSourcesList';
|
||||
import { getMockDataSources } from './__mocks__/dataSourcesMocks';
|
||||
import { initialState } from './state/reducers';
|
||||
|
||||
const setup = () => {
|
||||
const props = {
|
||||
dataSources: getMockDataSources(3),
|
||||
layoutMode: LayoutModes.Grid,
|
||||
};
|
||||
const setup = (stateOverride?: Partial<DataSourcesState>) => {
|
||||
const store = configureStore({
|
||||
dataSources: {
|
||||
...initialState,
|
||||
dataSources: getMockDataSources(3),
|
||||
layoutMode: LayoutModes.Grid,
|
||||
...stateOverride,
|
||||
},
|
||||
});
|
||||
|
||||
return render(<DataSourcesList {...props} />);
|
||||
return render(
|
||||
<Provider store={store}>
|
||||
<DataSourcesList dataSources={getMockDataSources(3)} />
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('DataSourcesList', () => {
|
@ -1,17 +1,16 @@
|
||||
// Libraries
|
||||
import { css } from '@emotion/css';
|
||||
import React, { FC } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
// Types
|
||||
import { DataSourceSettings, LayoutMode } from '@grafana/data';
|
||||
import { DataSourceSettings } from '@grafana/data';
|
||||
import { Card, Tag, useStyles } from '@grafana/ui';
|
||||
|
||||
export interface Props {
|
||||
export type Props = {
|
||||
dataSources: DataSourceSettings[];
|
||||
layoutMode: LayoutMode;
|
||||
}
|
||||
};
|
||||
|
||||
export const DataSourcesList: FC<Props> = ({ dataSources, layoutMode }) => {
|
||||
export const DataSourcesList = ({ dataSources }: Props) => {
|
||||
const styles = useStyles(getStyles);
|
||||
|
||||
return (
|
||||
|
26
public/app/features/datasources/DataSourcesListHeader.tsx
Normal file
26
public/app/features/datasources/DataSourcesListHeader.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import PageActionBar from 'app/core/components/PageActionBar/PageActionBar';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { AccessControlAction, StoreState } from 'app/types';
|
||||
|
||||
import { setDataSourcesSearchQuery } from './state/reducers';
|
||||
import { getDataSourcesSearchQuery } from './state/selectors';
|
||||
|
||||
export const DataSourcesListHeader = () => {
|
||||
const dispatch = useDispatch();
|
||||
const setSearchQuery = useCallback((q: string) => dispatch(setDataSourcesSearchQuery(q)), [dispatch]);
|
||||
const searchQuery = useSelector(({ dataSources }: StoreState) => getDataSourcesSearchQuery(dataSources));
|
||||
const canCreateDataSource = contextSrv.hasPermission(AccessControlAction.DataSourcesCreate);
|
||||
|
||||
const linkButton = {
|
||||
href: 'datasources/new',
|
||||
title: 'Add data source',
|
||||
disabled: !canCreateDataSource,
|
||||
};
|
||||
|
||||
return (
|
||||
<PageActionBar searchQuery={searchQuery} setSearchQuery={setSearchQuery} linkButton={linkButton} key="action-bar" />
|
||||
);
|
||||
};
|
@ -1,11 +1,15 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { DataSourceSettings, NavModel, LayoutModes } from '@grafana/data';
|
||||
import { DataSourceSettings, LayoutModes } from '@grafana/data';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
import { DataSourcesState } from 'app/types';
|
||||
|
||||
import { DataSourcesListPage, Props } from './DataSourcesListPage';
|
||||
import { DataSourcesListPage } from './DataSourcesListPage';
|
||||
import { getMockDataSources } from './__mocks__/dataSourcesMocks';
|
||||
import { setDataSourcesLayoutMode, setDataSourcesSearchQuery } from './state/reducers';
|
||||
import navIndex from './__mocks__/store.navIndex.mock';
|
||||
import { initialState } from './state/reducers';
|
||||
|
||||
jest.mock('app/core/core', () => {
|
||||
return {
|
||||
@ -15,29 +19,30 @@ jest.mock('app/core/core', () => {
|
||||
};
|
||||
});
|
||||
|
||||
const setup = (propOverrides?: object) => {
|
||||
const props: Props = {
|
||||
dataSources: [] as DataSourceSettings[],
|
||||
layoutMode: LayoutModes.Grid,
|
||||
loadDataSources: jest.fn(),
|
||||
navModel: {
|
||||
main: {
|
||||
text: 'Configuration',
|
||||
},
|
||||
node: {
|
||||
text: 'Data Sources',
|
||||
},
|
||||
} as NavModel,
|
||||
dataSourcesCount: 0,
|
||||
searchQuery: '',
|
||||
setDataSourcesSearchQuery,
|
||||
setDataSourcesLayoutMode,
|
||||
hasFetched: false,
|
||||
};
|
||||
const getMock = jest.fn().mockResolvedValue([]);
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
jest.mock('app/core/services/backend_srv', () => ({
|
||||
...jest.requireActual('app/core/services/backend_srv'),
|
||||
getBackendSrv: () => ({ get: getMock }),
|
||||
}));
|
||||
|
||||
return render(<DataSourcesListPage {...props} />);
|
||||
const setup = (stateOverride?: Partial<DataSourcesState>) => {
|
||||
const store = configureStore({
|
||||
dataSources: {
|
||||
...initialState,
|
||||
dataSources: [] as DataSourceSettings[],
|
||||
layoutMode: LayoutModes.Grid,
|
||||
hasFetched: false,
|
||||
...stateOverride,
|
||||
},
|
||||
navIndex,
|
||||
});
|
||||
|
||||
return render(
|
||||
<Provider store={store}>
|
||||
<DataSourcesListPage />
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('Render', () => {
|
||||
|
@ -1,98 +1,22 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IconName } from '@grafana/ui';
|
||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import PageActionBar from 'app/core/components/PageActionBar/PageActionBar';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import { StoreState, AccessControlAction } from 'app/types';
|
||||
import { StoreState } from 'app/types';
|
||||
|
||||
import DataSourcesList from './DataSourcesList';
|
||||
import { loadDataSources } from './state/actions';
|
||||
import { setDataSourcesLayoutMode, setDataSourcesSearchQuery } from './state/reducers';
|
||||
import {
|
||||
getDataSources,
|
||||
getDataSourcesCount,
|
||||
getDataSourcesLayoutMode,
|
||||
getDataSourcesSearchQuery,
|
||||
} from './state/selectors';
|
||||
import { DataSourcesListPageContent } from './DataSourcesListPageContent';
|
||||
|
||||
function mapStateToProps(state: StoreState) {
|
||||
return {
|
||||
navModel: getNavModel(state.navIndex, 'datasources'),
|
||||
dataSources: getDataSources(state.dataSources),
|
||||
layoutMode: getDataSourcesLayoutMode(state.dataSources),
|
||||
dataSourcesCount: getDataSourcesCount(state.dataSources),
|
||||
searchQuery: getDataSourcesSearchQuery(state.dataSources),
|
||||
hasFetched: state.dataSources.hasFetched,
|
||||
};
|
||||
}
|
||||
export const DataSourcesListPage = () => {
|
||||
const navModel = useSelector(({ navIndex }: StoreState) => getNavModel(navIndex, 'datasources'));
|
||||
|
||||
const mapDispatchToProps = {
|
||||
loadDataSources,
|
||||
setDataSourcesSearchQuery,
|
||||
setDataSourcesLayoutMode,
|
||||
return (
|
||||
<Page navModel={navModel}>
|
||||
<Page.Contents>
|
||||
<DataSourcesListPageContent />
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export type Props = ConnectedProps<typeof connector>;
|
||||
|
||||
const emptyListModel = {
|
||||
title: 'No data sources defined',
|
||||
buttonIcon: 'database' as IconName,
|
||||
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.props.loadDataSources();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dataSources, dataSourcesCount, navModel, layoutMode, searchQuery, setDataSourcesSearchQuery, hasFetched } =
|
||||
this.props;
|
||||
|
||||
const canCreateDataSource = contextSrv.hasPermission(AccessControlAction.DataSourcesCreate);
|
||||
|
||||
const linkButton = {
|
||||
href: 'datasources/new',
|
||||
title: 'Add data source',
|
||||
disabled: !canCreateDataSource,
|
||||
};
|
||||
|
||||
const emptyList = {
|
||||
...emptyListModel,
|
||||
buttonDisabled: !canCreateDataSource,
|
||||
};
|
||||
|
||||
return (
|
||||
<Page navModel={navModel}>
|
||||
<Page.Contents isLoading={!hasFetched}>
|
||||
<>
|
||||
{hasFetched && dataSourcesCount === 0 && <EmptyListCTA {...emptyList} />}
|
||||
{hasFetched &&
|
||||
dataSourcesCount > 0 && [
|
||||
<PageActionBar
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={(query) => setDataSourcesSearchQuery(query)}
|
||||
linkButton={linkButton}
|
||||
key="action-bar"
|
||||
/>,
|
||||
<DataSourcesList dataSources={dataSources} layoutMode={layoutMode} key="list" />,
|
||||
]}
|
||||
</>
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connector(DataSourcesListPage);
|
||||
export default DataSourcesListPage;
|
||||
|
@ -0,0 +1,58 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { IconName } from '@grafana/ui';
|
||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
import PageLoader from 'app/core/components/PageLoader/PageLoader';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { StoreState, AccessControlAction } from 'app/types';
|
||||
|
||||
import DataSourcesList from './DataSourcesList';
|
||||
import { DataSourcesListHeader } from './DataSourcesListHeader';
|
||||
import { loadDataSources } from './state/actions';
|
||||
import { getDataSourcesCount, getDataSources } from './state/selectors';
|
||||
|
||||
const buttonIcon: IconName = 'database';
|
||||
const emptyListModel = {
|
||||
title: 'No data sources defined',
|
||||
buttonIcon,
|
||||
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 const DataSourcesListPageContent = () => {
|
||||
const dispatch = useDispatch();
|
||||
const dataSources = useSelector((state: StoreState) => getDataSources(state.dataSources));
|
||||
const dataSourcesCount = useSelector(({ dataSources }: StoreState) => getDataSourcesCount(dataSources));
|
||||
const hasFetched = useSelector(({ dataSources }: StoreState) => dataSources.hasFetched);
|
||||
const canCreateDataSource = contextSrv.hasPermission(AccessControlAction.DataSourcesCreate);
|
||||
const emptyList = {
|
||||
...emptyListModel,
|
||||
buttonDisabled: !canCreateDataSource,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasFetched) {
|
||||
dispatch(loadDataSources());
|
||||
}
|
||||
}, [dispatch, hasFetched]);
|
||||
|
||||
if (!hasFetched) {
|
||||
return <PageLoader />;
|
||||
}
|
||||
|
||||
if (dataSourcesCount === 0) {
|
||||
return <EmptyListCTA {...emptyList} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataSourcesListHeader />
|
||||
<DataSourcesList dataSources={dataSources} />
|
||||
</>
|
||||
);
|
||||
};
|
2633
public/app/features/datasources/__mocks__/store.navIndex.mock.ts
Normal file
2633
public/app/features/datasources/__mocks__/store.navIndex.mock.ts
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user