LibraryPanels: Adds initial section and Page to Dashboard submenu (#32876)

* LibraryPanels: Adds initial section and Page to Dashboard submenu

* Refactor: adds perPage as prop

* Chore: renames OrgActionBar

* Chore: updates after PR comments

* Chore: updates snapshot
This commit is contained in:
Hugo Häggmark 2021-04-12 09:30:29 +02:00 committed by GitHub
parent 0c71fdac3d
commit 7d07599dc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 87 additions and 19 deletions

View File

@ -165,6 +165,12 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto
Url: hs.Cfg.AppSubURL + "/dashboard/snapshots",
Icon: "camera",
})
dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{
Text: "Global panels",
Id: "library-panels",
Url: hs.Cfg.AppSubURL + "/library-panels",
Icon: "reusable-panel",
})
}
navTree = append(navTree, &dtos.NavLink{

View File

@ -1,6 +1,6 @@
import React from 'react';
import { shallow } from 'enzyme';
import OrgActionBar, { Props } from './OrgActionBar';
import PageActionBar, { Props } from './PageActionBar';
const setup = (propOverrides?: object) => {
const props: Props = {
@ -12,7 +12,7 @@ const setup = (propOverrides?: object) => {
Object.assign(props, propOverrides);
return shallow(<OrgActionBar {...props} />);
return shallow(<PageActionBar {...props} />);
};
describe('Render', () => {

View File

@ -5,14 +5,15 @@ import { LinkButton } from '@grafana/ui';
export interface Props {
searchQuery: string;
setSearchQuery: (value: string) => void;
linkButton: { href: string; title: string };
linkButton?: { href: string; title: string };
target?: string;
placeholder?: string;
}
export default class OrgActionBar extends PureComponent<Props> {
export default class PageActionBar extends PureComponent<Props> {
render() {
const { searchQuery, linkButton, setSearchQuery, target } = this.props;
const linkProps = { href: linkButton.href };
const { searchQuery, linkButton, setSearchQuery, target, placeholder = 'Search by name or type' } = this.props;
const linkProps = { href: linkButton?.href };
if (target) {
(linkProps as any).target = target;
@ -21,10 +22,10 @@ export default class OrgActionBar extends PureComponent<Props> {
return (
<div className="page-action-bar">
<div className="gf-form gf-form--grow">
<FilterInput value={searchQuery} onChange={setSearchQuery} placeholder={'Search by name or type'} />
<FilterInput value={searchQuery} onChange={setSearchQuery} placeholder={placeholder} />
</div>
<div className="page-action-bar__spacer" />
<LinkButton {...linkProps}>{linkButton.title}</LinkButton>
{linkButton && <LinkButton {...linkProps}>{linkButton.title}</LinkButton>}
</div>
);
}

View File

@ -12,3 +12,5 @@ export const LS_PANEL_COPY_KEY = 'panel-copy';
export const PANEL_BORDER = 2;
export const EDIT_PANEL_ID = 23763571993;
export const DEFAULT_PER_PAGE_PAGINATION = 8;

View File

@ -4,7 +4,7 @@ import { connect } from 'react-redux';
import { hot } from 'react-hot-loader';
// Components
import Page from 'app/core/components/Page/Page';
import OrgActionBar from 'app/core/components/OrgActionBar/OrgActionBar';
import PageActionBar from 'app/core/components/PageActionBar/PageActionBar';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import DataSourcesList from './DataSourcesList';
// Types
@ -75,7 +75,7 @@ export class DataSourcesListPage extends PureComponent<Props> {
{hasFetched && dataSourcesCount === 0 && <EmptyListCTA {...emptyListModel} />}
{hasFetched &&
dataSourcesCount > 0 && [
<OrgActionBar
<PageActionBar
searchQuery={searchQuery}
setSearchQuery={(query) => setDataSourcesSearchQuery(query)}
linkButton={linkButton}

View File

@ -16,7 +16,7 @@ exports[`Render should render action bar and datasources 1`] = `
<PageContents
isLoading={false}
>
<OrgActionBar
<PageActionBar
key="action-bar"
linkButton={
Object {

View File

@ -0,0 +1,49 @@
import React, { FC, useState } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { GrafanaRouteComponentProps } from '../../core/navigation/types';
import { StoreState } from '../../types';
import { getNavModel } from '../../core/selectors/navModel';
import Page from '../../core/components/Page/Page';
import { LibraryPanelsView } from './components/LibraryPanelsView/LibraryPanelsView';
import { useAsync } from 'react-use';
import { getLibraryPanels } from './state/api';
import PageActionBar from '../../core/components/PageActionBar/PageActionBar';
import { DEFAULT_PER_PAGE_PAGINATION } from 'app/core/constants';
const mapStateToProps = (state: StoreState) => ({
navModel: getNavModel(state.navIndex, 'library-panels'),
});
const connector = connect(mapStateToProps, undefined);
interface OwnProps extends GrafanaRouteComponentProps {}
type Props = OwnProps & ConnectedProps<typeof connector>;
export const LibraryPanelsPage: FC<Props> = ({ navModel }) => {
const [searchQuery, setSearchQuery] = useState('');
const { value: searchResult, loading } = useAsync(async () => {
return getLibraryPanels();
});
const hasLibraryPanels = Boolean(searchResult?.libraryPanels.length);
return (
<Page navModel={navModel}>
<Page.Contents isLoading={loading}>
{hasLibraryPanels && (
<PageActionBar searchQuery={searchQuery} setSearchQuery={setSearchQuery} placeholder={'Search by name'} />
)}
<LibraryPanelsView
onClickCard={() => undefined}
searchString={searchQuery}
currentPanelId={undefined}
showSecondaryActions={true}
perPage={DEFAULT_PER_PAGE_PAGINATION}
/>
</Page.Contents>
</Page>
);
};
export default connect(mapStateToProps)(LibraryPanelsPage);

View File

@ -1,7 +1,7 @@
import React, { useMemo, useReducer } from 'react';
import { useDebounce } from 'react-use';
import { css, cx } from '@emotion/css';
import { Pagination, stylesFactory, useStyles } from '@grafana/ui';
import { Pagination, useStyles } from '@grafana/ui';
import { GrafanaTheme, LoadingState } from '@grafana/data';
import { LibraryPanelCard } from '../LibraryPanelCard/LibraryPanelCard';
@ -15,6 +15,7 @@ interface LibraryPanelViewProps {
showSecondaryActions?: boolean;
currentPanelId?: string;
searchString: string;
perPage?: number;
}
export const LibraryPanelsView: React.FC<LibraryPanelViewProps> = ({
@ -23,6 +24,7 @@ export const LibraryPanelsView: React.FC<LibraryPanelViewProps> = ({
searchString,
showSecondaryActions,
currentPanelId: currentPanel,
perPage: propsPerPage = 40,
}) => {
const styles = useStyles(getPanelViewStyles);
const [{ libraryPanels, page, perPage, numberOfPages, loadingState, currentPanelId }, dispatch] = useReducer(
@ -30,6 +32,7 @@ export const LibraryPanelsView: React.FC<LibraryPanelViewProps> = ({
{
...initialLibraryPanelsViewState,
currentPanelId: currentPanel,
perPage: propsPerPage,
}
);
const asyncDispatch = useMemo(() => asyncDispatcher(dispatch), [dispatch]);
@ -75,7 +78,7 @@ export const LibraryPanelsView: React.FC<LibraryPanelViewProps> = ({
);
};
const getPanelViewStyles = stylesFactory((theme: GrafanaTheme) => {
const getPanelViewStyles = (theme: GrafanaTheme) => {
return {
container: css`
display: flex;
@ -96,6 +99,7 @@ const getPanelViewStyles = stylesFactory((theme: GrafanaTheme) => {
`,
pagination: css`
align-self: center;
margin-top: ${theme.spacing.sm};
`,
};
});
};

View File

@ -1,5 +1,5 @@
import React, { FC, useState } from 'react';
import { MapStateToProps, connect } from 'react-redux';
import { connect, MapStateToProps } from 'react-redux';
import { NavModel, SelectableValue, urlUtil } from '@grafana/data';
import Page from 'app/core/components/Page/Page';
import { StoreState } from 'app/types';
@ -10,7 +10,7 @@ import { getBackendSrv, locationService } from '@grafana/runtime';
import { PlaylistDTO } from './types';
import { Button, Card, Checkbox, Field, LinkButton, Modal, RadioButtonGroup, VerticalGroup } from '@grafana/ui';
import { contextSrv } from 'app/core/core';
import OrgActionBar from 'app/core/components/OrgActionBar/OrgActionBar';
import PageActionBar from 'app/core/components/PageActionBar/PageActionBar';
import EmptyListCTA from '../../core/components/EmptyListCTA/EmptyListCTA';
interface ConnectedProps {
@ -73,7 +73,7 @@ export const PlaylistPage: FC<Props> = ({ navModel }) => {
<Page navModel={navModel}>
<Page.Contents isLoading={loading}>
{hasPlaylists && (
<OrgActionBar
<PageActionBar
searchQuery={searchQuery}
linkButton={{ title: 'New playlist', href: '/playlists/new' }}
setSearchQuery={setSearchQuery}

View File

@ -2,7 +2,7 @@ import React from 'react';
import { hot } from 'react-hot-loader';
import { connect } from 'react-redux';
import Page from 'app/core/components/Page/Page';
import OrgActionBar from 'app/core/components/OrgActionBar/OrgActionBar';
import PageActionBar from 'app/core/components/PageActionBar/PageActionBar';
import PluginList from './PluginList';
import { loadPlugins } from './state/actions';
import { getNavModel } from 'app/core/selectors/navModel';
@ -44,7 +44,7 @@ export const PluginListPage: React.FC<Props> = ({
<Page navModel={navModel} aria-label={selectors.pages.PluginsList.page}>
<Page.Contents isLoading={!hasFetched}>
<>
<OrgActionBar
<PageActionBar
searchQuery={searchQuery}
setSearchQuery={(query) => setPluginsSearchQuery(query)}
linkButton={linkButton}

View File

@ -447,6 +447,12 @@ export function getAppRoutes(): RouteDescriptor[] {
() => import(/* webpackChunkName: "PlaylistEditPage"*/ 'app/features/playlist/PlaylistEditPage')
),
},
{
path: '/library-panels',
component: SafeDynamicImport(
() => import(/* webpackChunkName: "LibraryPanelsPage"*/ 'app/features/library-panels/LibraryPanelsPage')
),
},
{
path: '/sandbox/benchmarks',
component: SafeDynamicImport(