mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Previews: remove dashboard previews UI (#66146)
* remove dashboard previews ui * remove dashboard previews ui * remove layout prop * remove layout prop
This commit is contained in:
parent
4a2d86750e
commit
d9b4aa07f7
@ -1,94 +0,0 @@
|
|||||||
import { css } from '@emotion/css';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { config } from '@grafana/runtime/src';
|
|
||||||
import { Alert, useStyles2 } from '@grafana/ui';
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
showPreviews?: boolean;
|
|
||||||
/** On click handler for alert button, mostly used for dismissing the alert */
|
|
||||||
onRemove?: (event: React.MouseEvent) => void;
|
|
||||||
topSpacing?: number;
|
|
||||||
bottomSpacing?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MessageLink = ({ text }: { text: string }) => (
|
|
||||||
<a
|
|
||||||
href="https://grafana.com/grafana/plugins/grafana-image-renderer"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="external-link"
|
|
||||||
>
|
|
||||||
{text}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Message = ({ requiredImageRendererPluginVersion }: { requiredImageRendererPluginVersion?: string }) => {
|
|
||||||
if (requiredImageRendererPluginVersion) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
You must update the <MessageLink text="Grafana image renderer plugin" /> to version{' '}
|
|
||||||
{requiredImageRendererPluginVersion} to enable dashboard previews. Please contact your Grafana administrator to
|
|
||||||
update the plugin.
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
You must install the <MessageLink text="Grafana image renderer plugin" /> to enable dashboard previews. Please
|
|
||||||
contact your Grafana administrator to install the plugin.
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PreviewsSystemRequirements = ({ showPreviews, onRemove, topSpacing, bottomSpacing }: Props) => {
|
|
||||||
const styles = useStyles2(getStyles);
|
|
||||||
|
|
||||||
const previewsEnabled = config.featureToggles.dashboardPreviews;
|
|
||||||
const rendererAvailable = config.rendererAvailable;
|
|
||||||
|
|
||||||
const {
|
|
||||||
systemRequirements: { met: systemRequirementsMet, requiredImageRendererPluginVersion },
|
|
||||||
thumbnailsExist,
|
|
||||||
} = config.dashboardPreviews;
|
|
||||||
|
|
||||||
const arePreviewsEnabled = previewsEnabled && showPreviews;
|
|
||||||
const areRequirementsMet = (rendererAvailable && systemRequirementsMet) || thumbnailsExist;
|
|
||||||
const shouldDisplayRequirements = arePreviewsEnabled && !areRequirementsMet;
|
|
||||||
|
|
||||||
const title = requiredImageRendererPluginVersion
|
|
||||||
? 'Image renderer plugin needs to be updated'
|
|
||||||
: 'Image renderer plugin not installed';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{shouldDisplayRequirements && (
|
|
||||||
<div className={styles.wrapper}>
|
|
||||||
<Alert
|
|
||||||
className={styles.alert}
|
|
||||||
topSpacing={topSpacing}
|
|
||||||
bottomSpacing={bottomSpacing}
|
|
||||||
severity="info"
|
|
||||||
title={title}
|
|
||||||
onRemove={onRemove}
|
|
||||||
>
|
|
||||||
<Message requiredImageRendererPluginVersion={requiredImageRendererPluginVersion} />
|
|
||||||
</Alert>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStyles = () => {
|
|
||||||
return {
|
|
||||||
wrapper: css`
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
`,
|
|
||||||
alert: css`
|
|
||||||
max-width: 800px;
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
};
|
|
@ -3,7 +3,7 @@ import React, { FormEvent } from 'react';
|
|||||||
|
|
||||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import { HorizontalGroup, RadioButtonGroup, useStyles2, Checkbox, Button } from '@grafana/ui';
|
import { Button, Checkbox, HorizontalGroup, RadioButtonGroup, useStyles2 } from '@grafana/ui';
|
||||||
import { SortPicker } from 'app/core/components/Select/SortPicker';
|
import { SortPicker } from 'app/core/components/Select/SortPicker';
|
||||||
import { TagFilter, TermCount } from 'app/core/components/TagFilter/TagFilter';
|
import { TagFilter, TermCount } from 'app/core/components/TagFilter/TagFilter';
|
||||||
import { t, Trans } from 'app/core/internationalization';
|
import { t, Trans } from 'app/core/internationalization';
|
||||||
@ -11,20 +11,10 @@ import { t, Trans } from 'app/core/internationalization';
|
|||||||
import { SearchLayout, SearchState } from '../../types';
|
import { SearchLayout, SearchState } from '../../types';
|
||||||
|
|
||||||
function getLayoutOptions() {
|
function getLayoutOptions() {
|
||||||
const layoutOptions = [
|
return [
|
||||||
{ value: SearchLayout.Folders, icon: 'folder', ariaLabel: t('search.actions.view-as-folders', 'View by folders') },
|
{ value: SearchLayout.Folders, icon: 'folder', ariaLabel: t('search.actions.view-as-folders', 'View by folders') },
|
||||||
{ value: SearchLayout.List, icon: 'list-ul', ariaLabel: t('search.actions.view-as-list', 'View as list') },
|
{ value: SearchLayout.List, icon: 'list-ul', ariaLabel: t('search.actions.view-as-list', 'View as list') },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (config.featureToggles.dashboardPreviews) {
|
|
||||||
layoutOptions.push({
|
|
||||||
value: SearchLayout.Grid,
|
|
||||||
icon: 'apps',
|
|
||||||
ariaLabel: t('search.actions.view-as-grid', 'Grid view'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return layoutOptions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -54,9 +44,6 @@ export function getValidQueryLayout(q: SearchState): SearchLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (layout === SearchLayout.Grid && !config.featureToggles.dashboardPreviews) {
|
|
||||||
return SearchLayout.List;
|
|
||||||
}
|
|
||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
||||||
import React, { KeyboardEvent } from 'react';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
import { ArrayVector, DataFrame, DataFrameView, FieldType } from '@grafana/data';
|
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
|
||||||
|
|
||||||
import { DashboardQueryResult, QueryResponse } from '../../service';
|
|
||||||
import { DashboardSearchItemType } from '../../types';
|
|
||||||
|
|
||||||
import { SearchResultsGrid } from './SearchResultsGrid';
|
|
||||||
|
|
||||||
describe('SearchResultsGrid', () => {
|
|
||||||
const dashboardsData: DataFrame = {
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'kind',
|
|
||||||
type: FieldType.string,
|
|
||||||
config: {},
|
|
||||||
values: new ArrayVector([DashboardSearchItemType.DashDB]),
|
|
||||||
},
|
|
||||||
{ name: 'name', type: FieldType.string, config: {}, values: new ArrayVector(['My dashboard 1', 'dash2']) },
|
|
||||||
{ name: 'uid', type: FieldType.string, config: {}, values: new ArrayVector(['my-dashboard-1', 'dash-2']) },
|
|
||||||
{ name: 'url', type: FieldType.string, config: {}, values: new ArrayVector(['/my-dashbaord-1', '/dash-2']) },
|
|
||||||
],
|
|
||||||
length: 2,
|
|
||||||
};
|
|
||||||
const mockSearchResult: QueryResponse = {
|
|
||||||
isItemLoaded: jest.fn(),
|
|
||||||
loadMoreItems: jest.fn(),
|
|
||||||
totalRows: dashboardsData.length,
|
|
||||||
view: new DataFrameView<DashboardQueryResult>(dashboardsData),
|
|
||||||
};
|
|
||||||
|
|
||||||
const baseProps = {
|
|
||||||
response: mockSearchResult,
|
|
||||||
width: 800,
|
|
||||||
height: 600,
|
|
||||||
clearSelection: jest.fn(),
|
|
||||||
onTagSelected: jest.fn(),
|
|
||||||
keyboardEvents: new Observable<KeyboardEvent<Element>>(),
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should render grid of dashboards', () => {
|
|
||||||
render(<SearchResultsGrid {...baseProps} />);
|
|
||||||
expect(screen.getByTestId(selectors.components.Search.dashboardCard('My dashboard 1'))).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId(selectors.components.Search.dashboardCard('dash2'))).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not render checkboxes for non-editable results', async () => {
|
|
||||||
render(<SearchResultsGrid {...baseProps} />);
|
|
||||||
expect(screen.queryByRole('checkbox')).toBeNull();
|
|
||||||
await waitFor(() => expect(screen.queryAllByRole('checkbox')).toHaveLength(0));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render checkboxes for editable results ', async () => {
|
|
||||||
const mockSelectionToggle = jest.fn();
|
|
||||||
const mockSelection = jest.fn();
|
|
||||||
render(<SearchResultsGrid {...baseProps} selection={mockSelection} selectionToggle={mockSelectionToggle} />);
|
|
||||||
|
|
||||||
await waitFor(() => expect(screen.queryAllByRole('checkbox')).toHaveLength(2));
|
|
||||||
fireEvent.click(await screen.findByRole('checkbox', { name: /Select dashboard dash2/i }));
|
|
||||||
expect(mockSelectionToggle).toHaveBeenCalledWith('dashboard', 'dash-2');
|
|
||||||
expect(mockSelectionToggle).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,134 +0,0 @@
|
|||||||
import { css } from '@emotion/css';
|
|
||||||
import React from 'react';
|
|
||||||
import { FixedSizeGrid } from 'react-window';
|
|
||||||
import InfiniteLoader from 'react-window-infinite-loader';
|
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
|
||||||
import { config } from '@grafana/runtime';
|
|
||||||
import { useStyles2 } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { SearchCard } from '../../components/SearchCard';
|
|
||||||
import { useSearchKeyboardNavigation } from '../../hooks/useSearchKeyboardSelection';
|
|
||||||
import { queryResultToViewItem } from '../../service/utils';
|
|
||||||
import { DashboardViewItem } from '../../types';
|
|
||||||
|
|
||||||
import { SearchResultsProps } from './SearchResultsTable';
|
|
||||||
|
|
||||||
export const SearchResultsGrid = ({
|
|
||||||
response,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
selection,
|
|
||||||
selectionToggle,
|
|
||||||
onTagSelected,
|
|
||||||
onClickItem,
|
|
||||||
keyboardEvents,
|
|
||||||
}: SearchResultsProps) => {
|
|
||||||
const styles = useStyles2(getStyles);
|
|
||||||
|
|
||||||
// Hacked to reuse existing SearchCard (and old DashboardSectionItem)
|
|
||||||
const itemProps = {
|
|
||||||
editable: selection != null,
|
|
||||||
onToggleChecked: (item: DashboardViewItem) => {
|
|
||||||
if (selectionToggle) {
|
|
||||||
selectionToggle(item.kind, item.uid);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onTagSelected,
|
|
||||||
onClick: onClickItem,
|
|
||||||
};
|
|
||||||
|
|
||||||
const itemCount = response.totalRows ?? response.view.length;
|
|
||||||
const view = response.view;
|
|
||||||
const numColumns = Math.ceil(width / 320);
|
|
||||||
const cellWidth = width / numColumns;
|
|
||||||
const cellHeight = (cellWidth - 64) * 0.75 + 56 + 8;
|
|
||||||
const numRows = Math.ceil(itemCount / numColumns);
|
|
||||||
const highlightIndex = useSearchKeyboardNavigation(keyboardEvents, numColumns, response);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InfiniteLoader isItemLoaded={response.isItemLoaded} itemCount={itemCount} loadMoreItems={response.loadMoreItems}>
|
|
||||||
{({ onItemsRendered, ref }) => (
|
|
||||||
<FixedSizeGrid
|
|
||||||
ref={ref}
|
|
||||||
onItemsRendered={(v) => {
|
|
||||||
onItemsRendered({
|
|
||||||
visibleStartIndex: v.visibleRowStartIndex * numColumns,
|
|
||||||
visibleStopIndex: v.visibleRowStopIndex * numColumns,
|
|
||||||
overscanStartIndex: v.overscanRowStartIndex * numColumns,
|
|
||||||
overscanStopIndex: v.overscanColumnStopIndex * numColumns,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
columnCount={numColumns}
|
|
||||||
columnWidth={cellWidth}
|
|
||||||
rowCount={numRows}
|
|
||||||
rowHeight={cellHeight}
|
|
||||||
className={styles.wrapper}
|
|
||||||
innerElementType="ul"
|
|
||||||
height={height}
|
|
||||||
width={width - 2}
|
|
||||||
>
|
|
||||||
{({ columnIndex, rowIndex, style }) => {
|
|
||||||
const index = rowIndex * numColumns + columnIndex;
|
|
||||||
if (index >= view.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const item = view.get(index);
|
|
||||||
const kind = item.kind ?? 'dashboard';
|
|
||||||
|
|
||||||
const facade = queryResultToViewItem(item, view);
|
|
||||||
|
|
||||||
if (kind === 'panel') {
|
|
||||||
const type = item.panel_type;
|
|
||||||
facade.icon = 'public/img/icons/unicons/graph-bar.svg';
|
|
||||||
if (type) {
|
|
||||||
const info = config.panels[type];
|
|
||||||
if (info?.name) {
|
|
||||||
const v = info.info?.logos.small;
|
|
||||||
if (v && v.endsWith('.svg')) {
|
|
||||||
facade.icon = v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let className = styles.virtualizedGridItemWrapper;
|
|
||||||
if (rowIndex === highlightIndex.y && columnIndex === highlightIndex.x) {
|
|
||||||
className += ' ' + styles.selectedItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The wrapper div is needed as the inner SearchItem has margin-bottom spacing
|
|
||||||
// And without this wrapper there is no room for that margin
|
|
||||||
return item ? (
|
|
||||||
<li style={style} className={className}>
|
|
||||||
<SearchCard
|
|
||||||
key={item.uid}
|
|
||||||
{...itemProps}
|
|
||||||
item={facade}
|
|
||||||
isSelected={selection ? selection(facade.kind, facade.uid) : false}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
) : null;
|
|
||||||
}}
|
|
||||||
</FixedSizeGrid>
|
|
||||||
)}
|
|
||||||
</InfiniteLoader>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
|
||||||
virtualizedGridItemWrapper: css`
|
|
||||||
padding: 4px;
|
|
||||||
`,
|
|
||||||
wrapper: css`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
> ul {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
selectedItem: css`
|
|
||||||
box-shadow: inset 1px 1px 3px 3px ${theme.colors.primary.border};
|
|
||||||
`,
|
|
||||||
});
|
|
@ -10,7 +10,6 @@ import { useStyles2, Spinner, Button } from '@grafana/ui';
|
|||||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||||
import { FolderDTO } from 'app/types';
|
import { FolderDTO } from 'app/types';
|
||||||
|
|
||||||
import { PreviewsSystemRequirements } from '../../components/PreviewsSystemRequirements';
|
|
||||||
import { getGrafanaSearcher } from '../../service';
|
import { getGrafanaSearcher } from '../../service';
|
||||||
import { getSearchStateManager } from '../../state/SearchStateManager';
|
import { getSearchStateManager } from '../../state/SearchStateManager';
|
||||||
import { SearchLayout, DashboardViewItem } from '../../types';
|
import { SearchLayout, DashboardViewItem } from '../../types';
|
||||||
@ -21,7 +20,6 @@ import { FolderSection } from './FolderSection';
|
|||||||
import { ManageActions } from './ManageActions';
|
import { ManageActions } from './ManageActions';
|
||||||
import { RootFolderView } from './RootFolderView';
|
import { RootFolderView } from './RootFolderView';
|
||||||
import { SearchResultsCards } from './SearchResultsCards';
|
import { SearchResultsCards } from './SearchResultsCards';
|
||||||
import { SearchResultsGrid } from './SearchResultsGrid';
|
|
||||||
import { SearchResultsTable, SearchResultsProps } from './SearchResultsTable';
|
import { SearchResultsTable, SearchResultsProps } from './SearchResultsTable';
|
||||||
|
|
||||||
export type SearchViewProps = {
|
export type SearchViewProps = {
|
||||||
@ -133,10 +131,6 @@ export const SearchView = ({ showManage, folderDTO, hidePseudoFolders, keyboardE
|
|||||||
onClickItem: stateManager.onSearchItemClicked,
|
onClickItem: stateManager.onSearchItemClicked,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (layout === SearchLayout.Grid) {
|
|
||||||
return <SearchResultsGrid {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (width < 800) {
|
if (width < 800) {
|
||||||
return <SearchResultsCards {...props} />;
|
return <SearchResultsCards {...props} />;
|
||||||
}
|
}
|
||||||
@ -193,13 +187,6 @@ export const SearchView = ({ showManage, folderDTO, hidePseudoFolders, keyboardE
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{layout === SearchLayout.Grid && (
|
|
||||||
<PreviewsSystemRequirements
|
|
||||||
bottomSpacing={3}
|
|
||||||
showPreviews={true}
|
|
||||||
onRemove={() => stateManager.onLayoutChange(SearchLayout.List)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{renderResults()}
|
{renderResults()}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { config, reportInteraction } from '@grafana/runtime';
|
import { reportInteraction } from '@grafana/runtime';
|
||||||
import { InspectTab } from 'app/features/inspector/types';
|
import { InspectTab } from 'app/features/inspector/types';
|
||||||
|
|
||||||
import { EventTrackingNamespace, SearchLayout } from '../types';
|
import { EventTrackingNamespace, SearchLayout } from '../types';
|
||||||
@ -40,12 +40,7 @@ export const reportPanelInspectInteraction = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getQuerySearchContext = (query: QueryProps) => {
|
const getQuerySearchContext = (query: QueryProps) => {
|
||||||
const showPreviews = query.layout === SearchLayout.Grid;
|
|
||||||
const previewsEnabled = Boolean(config.featureToggles.panelTitleSearch);
|
|
||||||
const previews = previewsEnabled ? (showPreviews ? 'on' : 'off') : 'feature_disabled';
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
previews,
|
|
||||||
layout: query.layout,
|
layout: query.layout,
|
||||||
starredFilter: query.starred ?? false,
|
starredFilter: query.starred ?? false,
|
||||||
sort: query.sortValue ?? '',
|
sort: query.sortValue ?? '',
|
||||||
|
@ -102,7 +102,6 @@ export type OnToggleChecked = (item: DashboardViewItem) => void;
|
|||||||
export enum SearchLayout {
|
export enum SearchLayout {
|
||||||
List = 'list',
|
List = 'list',
|
||||||
Folders = 'folders',
|
Folders = 'folders',
|
||||||
Grid = 'grid', // preview
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchQueryParams {
|
export interface SearchQueryParams {
|
||||||
|
@ -20,12 +20,11 @@ import { getBackendSrv } from 'app/core/services/backend_srv';
|
|||||||
import impressionSrv from 'app/core/services/impression_srv';
|
import impressionSrv from 'app/core/services/impression_srv';
|
||||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||||
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
import { SearchCard } from 'app/features/search/components/SearchCard';
|
|
||||||
import { DashboardSearchItem } from 'app/features/search/types';
|
import { DashboardSearchItem } from 'app/features/search/types';
|
||||||
import { getVariablesUrlParams } from 'app/features/variables/getAllVariableValuesForUrl';
|
import { getVariablesUrlParams } from 'app/features/variables/getAllVariableValuesForUrl';
|
||||||
import { useDispatch } from 'app/types';
|
import { useDispatch } from 'app/types';
|
||||||
|
|
||||||
import { PanelLayout, PanelOptions } from './panelcfg.gen';
|
import { PanelOptions } from './panelcfg.gen';
|
||||||
import { getStyles } from './styles';
|
import { getStyles } from './styles';
|
||||||
|
|
||||||
type Dashboard = DashboardSearchItem & { id?: number; isSearchResult?: boolean; isRecent?: boolean };
|
type Dashboard = DashboardSearchItem & { id?: number; isSearchResult?: boolean; isRecent?: boolean };
|
||||||
@ -130,7 +129,7 @@ export function DashList(props: PanelProps<PanelOptions>) {
|
|||||||
];
|
];
|
||||||
}, [dashboards]);
|
}, [dashboards]);
|
||||||
|
|
||||||
const { showStarred, showRecentlyViewed, showHeadings, showSearch, layout } = props.options;
|
const { showStarred, showRecentlyViewed, showHeadings, showSearch } = props.options;
|
||||||
|
|
||||||
const dashboardGroups: DashboardGroup[] = [
|
const dashboardGroups: DashboardGroup[] = [
|
||||||
{
|
{
|
||||||
@ -198,16 +197,6 @@ export function DashList(props: PanelProps<PanelOptions>) {
|
|||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderPreviews = (dashboards: Dashboard[]) => (
|
|
||||||
<ul className={css.gridContainer}>
|
|
||||||
{dashboards.map((dash) => (
|
|
||||||
<li key={dash.uid}>
|
|
||||||
<SearchCard item={{ ...dash, kind: 'dashboard' }} />
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomScrollbar autoHeightMin="100%" autoHeightMax="100%">
|
<CustomScrollbar autoHeightMin="100%" autoHeightMax="100%">
|
||||||
{dashboardGroups.map(
|
{dashboardGroups.map(
|
||||||
@ -215,7 +204,7 @@ export function DashList(props: PanelProps<PanelOptions>) {
|
|||||||
show && (
|
show && (
|
||||||
<div className={css.dashlistSection} key={`dash-group-${i}`}>
|
<div className={css.dashlistSection} key={`dash-group-${i}`}>
|
||||||
{showHeadings && <h6 className={css.dashlistSectionHeader}>{header}</h6>}
|
{showHeadings && <h6 className={css.dashlistSectionHeader}>{header}</h6>}
|
||||||
{layout === PanelLayout.Previews ? renderPreviews(dashboards) : renderList(dashboards)}
|
{renderList(dashboards)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { PanelModel, PanelPlugin } from '@grafana/data';
|
import { PanelModel, PanelPlugin } from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
|
||||||
import { TagsInput } from '@grafana/ui';
|
import { TagsInput } from '@grafana/ui';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -11,24 +10,10 @@ import {
|
|||||||
} from '../../../core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker';
|
} from '../../../core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker';
|
||||||
|
|
||||||
import { DashList } from './DashList';
|
import { DashList } from './DashList';
|
||||||
import { defaultPanelOptions, PanelLayout, PanelOptions } from './panelcfg.gen';
|
import { defaultPanelOptions, PanelOptions } from './panelcfg.gen';
|
||||||
|
|
||||||
export const plugin = new PanelPlugin<PanelOptions>(DashList)
|
export const plugin = new PanelPlugin<PanelOptions>(DashList)
|
||||||
.setPanelOptions((builder) => {
|
.setPanelOptions((builder) => {
|
||||||
if (config.featureToggles.dashboardPreviews) {
|
|
||||||
builder.addRadio({
|
|
||||||
path: 'layout',
|
|
||||||
name: 'Layout',
|
|
||||||
defaultValue: PanelLayout.List,
|
|
||||||
settings: {
|
|
||||||
options: [
|
|
||||||
{ value: PanelLayout.List, label: 'List' },
|
|
||||||
{ value: PanelLayout.Previews, label: 'Preview' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.addBooleanSwitch({
|
.addBooleanSwitch({
|
||||||
path: 'keepTime',
|
path: 'keepTime',
|
||||||
|
@ -22,9 +22,7 @@ composableKinds: PanelCfg: {
|
|||||||
{
|
{
|
||||||
schemas: [
|
schemas: [
|
||||||
{
|
{
|
||||||
PanelLayout: "list" | "previews" @cuetsy(kind="enum")
|
|
||||||
PanelOptions: {
|
PanelOptions: {
|
||||||
layout?: PanelLayout | *"list"
|
|
||||||
keepTime: bool | *false
|
keepTime: bool | *false
|
||||||
includeVars: bool | *false
|
includeVars: bool | *false
|
||||||
showStarred: bool | *true
|
showStarred: bool | *true
|
||||||
|
@ -10,16 +10,10 @@
|
|||||||
|
|
||||||
export const PanelCfgModelVersion = Object.freeze([0, 0]);
|
export const PanelCfgModelVersion = Object.freeze([0, 0]);
|
||||||
|
|
||||||
export enum PanelLayout {
|
|
||||||
List = 'list',
|
|
||||||
Previews = 'previews',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PanelOptions {
|
export interface PanelOptions {
|
||||||
folderId?: number;
|
folderId?: number;
|
||||||
includeVars: boolean;
|
includeVars: boolean;
|
||||||
keepTime: boolean;
|
keepTime: boolean;
|
||||||
layout?: PanelLayout;
|
|
||||||
maxItems: number;
|
maxItems: number;
|
||||||
query: string;
|
query: string;
|
||||||
showHeadings: boolean;
|
showHeadings: boolean;
|
||||||
@ -32,7 +26,6 @@ export interface PanelOptions {
|
|||||||
export const defaultPanelOptions: Partial<PanelOptions> = {
|
export const defaultPanelOptions: Partial<PanelOptions> = {
|
||||||
includeVars: false,
|
includeVars: false,
|
||||||
keepTime: false,
|
keepTime: false,
|
||||||
layout: PanelLayout.List,
|
|
||||||
maxItems: 10,
|
maxItems: 10,
|
||||||
query: '',
|
query: '',
|
||||||
showHeadings: true,
|
showHeadings: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user