mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboards: Remove emptyDashboardPage feature flag (#81188)
* remove emptyDashboardPage feature toggle from DashNav * remove emptyDashboardPage feature toggle from NewDashboardWithDS * remove emptyDashboardPage feature toggle from DashboardGrid * remove emptyDashboardPage feature toggle from DashboardModel * remove emptyDashboardPage feature toggle from initDashboard * remove unused AddPanelWidged component * remove add-panel type from test * remove emptyDashboardPage feature flag from registry.go
This commit is contained in:
parent
d66d7a9642
commit
9ba13dd309
@ -2434,22 +2434,6 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
[0, 0, 0, "Do not use any type assertions.", "1"],
|
||||||
[0, 0, 0, "Do not use any type assertions.", "2"]
|
[0, 0, 0, "Do not use any type assertions.", "2"]
|
||||||
],
|
],
|
||||||
"public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx:5381": [
|
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
|
||||||
],
|
|
||||||
"public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx:5381": [
|
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
|
||||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"],
|
|
||||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"],
|
|
||||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "3"],
|
|
||||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "4"],
|
|
||||||
[0, 0, 0, "Styles should be written using objects.", "5"],
|
|
||||||
[0, 0, 0, "Styles should be written using objects.", "6"],
|
|
||||||
[0, 0, 0, "Styles should be written using objects.", "7"],
|
|
||||||
[0, 0, 0, "Styles should be written using objects.", "8"],
|
|
||||||
[0, 0, 0, "Styles should be written using objects.", "9"],
|
|
||||||
[0, 0, 0, "Styles should be written using objects.", "10"]
|
|
||||||
],
|
|
||||||
"public/app/features/dashboard/components/AddWidgetModal/AddWidgetModal.tsx:5381": [
|
"public/app/features/dashboard/components/AddWidgetModal/AddWidgetModal.tsx:5381": [
|
||||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
[0, 0, 0, "Styles should be written using objects.", "0"],
|
||||||
[0, 0, 0, "Styles should be written using objects.", "1"],
|
[0, 0, 0, "Styles should be written using objects.", "1"],
|
||||||
|
@ -32,7 +32,6 @@ Some features are enabled by default. You can disable these feature by setting t
|
|||||||
| `athenaAsyncQueryDataSupport` | Enable async query data support for Athena | Yes |
|
| `athenaAsyncQueryDataSupport` | Enable async query data support for Athena | Yes |
|
||||||
| `cloudwatchNewRegionsHandler` | Refactor of /regions endpoint, no user-facing changes | Yes |
|
| `cloudwatchNewRegionsHandler` | Refactor of /regions endpoint, no user-facing changes | Yes |
|
||||||
| `nestedFolderPicker` | Enables the new folder picker to work with nested folders. Requires the nestedFolders feature toggle | Yes |
|
| `nestedFolderPicker` | Enables the new folder picker to work with nested folders. Requires the nestedFolders feature toggle | Yes |
|
||||||
| `emptyDashboardPage` | Enable the redesigned user interface of a dashboard page that includes no panels | Yes |
|
|
||||||
| `disablePrometheusExemplarSampling` | Disable Prometheus exemplar sampling | |
|
| `disablePrometheusExemplarSampling` | Disable Prometheus exemplar sampling | |
|
||||||
| `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view | Yes |
|
| `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view | Yes |
|
||||||
| `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | Yes |
|
| `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | Yes |
|
||||||
|
@ -54,7 +54,6 @@ export interface FeatureToggles {
|
|||||||
accessControlOnCall?: boolean;
|
accessControlOnCall?: boolean;
|
||||||
nestedFolders?: boolean;
|
nestedFolders?: boolean;
|
||||||
nestedFolderPicker?: boolean;
|
nestedFolderPicker?: boolean;
|
||||||
emptyDashboardPage?: boolean;
|
|
||||||
disablePrometheusExemplarSampling?: boolean;
|
disablePrometheusExemplarSampling?: boolean;
|
||||||
alertingBacktesting?: boolean;
|
alertingBacktesting?: boolean;
|
||||||
editPanelCSVDragAndDrop?: boolean;
|
editPanelCSVDragAndDrop?: boolean;
|
||||||
|
@ -313,17 +313,6 @@ var (
|
|||||||
AllowSelfServe: true,
|
AllowSelfServe: true,
|
||||||
Created: time.Date(2023, time.July, 24, 12, 0, 0, 0, time.UTC),
|
Created: time.Date(2023, time.July, 24, 12, 0, 0, 0, time.UTC),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "emptyDashboardPage",
|
|
||||||
Description: "Enable the redesigned user interface of a dashboard page that includes no panels",
|
|
||||||
Stage: FeatureStageGeneralAvailability,
|
|
||||||
FrontendOnly: true,
|
|
||||||
Expression: "true", // enabled by default
|
|
||||||
Owner: grafanaDashboardsSquad,
|
|
||||||
AllowSelfServe: false,
|
|
||||||
HideFromAdminPage: true,
|
|
||||||
Created: time.Date(2023, time.March, 28, 12, 0, 0, 0, time.UTC),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "disablePrometheusExemplarSampling",
|
Name: "disablePrometheusExemplarSampling",
|
||||||
Description: "Disable Prometheus exemplar sampling",
|
Description: "Disable Prometheus exemplar sampling",
|
||||||
|
@ -35,7 +35,6 @@ mysqlAnsiQuotes,experimental,@grafana/backend-platform,2022-10-12,false,false,fa
|
|||||||
accessControlOnCall,preview,@grafana/identity-access-team,2022-10-19,false,false,false,false
|
accessControlOnCall,preview,@grafana/identity-access-team,2022-10-19,false,false,false,false
|
||||||
nestedFolders,preview,@grafana/backend-platform,2022-10-22,false,false,false,false
|
nestedFolders,preview,@grafana/backend-platform,2022-10-22,false,false,false,false
|
||||||
nestedFolderPicker,GA,@grafana/grafana-frontend-platform,2023-07-24,false,false,false,true
|
nestedFolderPicker,GA,@grafana/grafana-frontend-platform,2023-07-24,false,false,false,true
|
||||||
emptyDashboardPage,GA,@grafana/dashboards-squad,2023-03-28,false,false,false,true
|
|
||||||
disablePrometheusExemplarSampling,GA,@grafana/observability-metrics,2022-12-19,false,false,false,false
|
disablePrometheusExemplarSampling,GA,@grafana/observability-metrics,2022-12-19,false,false,false,false
|
||||||
alertingBacktesting,experimental,@grafana/alerting-squad,2022-10-20,false,false,false,false
|
alertingBacktesting,experimental,@grafana/alerting-squad,2022-10-20,false,false,false,false
|
||||||
editPanelCSVDragAndDrop,experimental,@grafana/grafana-bi-squad,2022-12-20,false,false,false,true
|
editPanelCSVDragAndDrop,experimental,@grafana/grafana-bi-squad,2022-12-20,false,false,false,true
|
||||||
|
|
@ -151,10 +151,6 @@ const (
|
|||||||
// Enables the new folder picker to work with nested folders. Requires the nestedFolders feature toggle
|
// Enables the new folder picker to work with nested folders. Requires the nestedFolders feature toggle
|
||||||
FlagNestedFolderPicker = "nestedFolderPicker"
|
FlagNestedFolderPicker = "nestedFolderPicker"
|
||||||
|
|
||||||
// FlagEmptyDashboardPage
|
|
||||||
// Enable the redesigned user interface of a dashboard page that includes no panels
|
|
||||||
FlagEmptyDashboardPage = "emptyDashboardPage"
|
|
||||||
|
|
||||||
// FlagDisablePrometheusExemplarSampling
|
// FlagDisablePrometheusExemplarSampling
|
||||||
// Disable Prometheus exemplar sampling
|
// Disable Prometheus exemplar sampling
|
||||||
FlagDisablePrometheusExemplarSampling = "disablePrometheusExemplarSampling"
|
FlagDisablePrometheusExemplarSampling = "disablePrometheusExemplarSampling"
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { PanelModel } from '../../state';
|
|
||||||
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
|
|
||||||
|
|
||||||
import { AddPanelWidgetUnconnected as AddPanelWidget, Props } from './AddPanelWidget';
|
|
||||||
|
|
||||||
const getTestContext = (propOverrides?: object) => {
|
|
||||||
const props: Props = {
|
|
||||||
dashboard: createDashboardModelFixture(),
|
|
||||||
panel: new PanelModel({}),
|
|
||||||
addPanel: jest.fn() as any,
|
|
||||||
};
|
|
||||||
Object.assign(props, propOverrides);
|
|
||||||
return render(<AddPanelWidget {...props} />);
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('AddPanelWidget', () => {
|
|
||||||
it('should render component without error', () => {
|
|
||||||
expect(() => {
|
|
||||||
getTestContext();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render the add panel actions', () => {
|
|
||||||
getTestContext();
|
|
||||||
expect(screen.getByText(/Add a new panel/i)).toBeInTheDocument();
|
|
||||||
expect(screen.getByText(/Add a new row/i)).toBeInTheDocument();
|
|
||||||
expect(screen.getByText(/Add a panel from the panel library/i)).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,299 +0,0 @@
|
|||||||
import { css, cx, keyframes } from '@emotion/css';
|
|
||||||
import { chain, cloneDeep, defaults, find, sortBy } from 'lodash';
|
|
||||||
import React, { useMemo, useState } from 'react';
|
|
||||||
import { connect, MapDispatchToProps } from 'react-redux';
|
|
||||||
import tinycolor from 'tinycolor2';
|
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
|
||||||
import { locationService, reportInteraction } from '@grafana/runtime';
|
|
||||||
import { Icon, IconButton, useStyles2 } from '@grafana/ui';
|
|
||||||
import { CardButton } from 'app/core/components/CardButton';
|
|
||||||
import config from 'app/core/config';
|
|
||||||
import { LS_PANEL_COPY_KEY } from 'app/core/constants';
|
|
||||||
import store from 'app/core/store';
|
|
||||||
import { addPanel } from 'app/features/dashboard/state/reducers';
|
|
||||||
|
|
||||||
import {
|
|
||||||
LibraryPanelsSearch,
|
|
||||||
LibraryPanelsSearchVariant,
|
|
||||||
} from '../../../library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch';
|
|
||||||
import { LibraryElementDTO } from '../../../library-panels/types';
|
|
||||||
import { DashboardModel, PanelModel } from '../../state';
|
|
||||||
|
|
||||||
export type PanelPluginInfo = { id: number; defaults: { gridPos: { w: number; h: number }; title: string } };
|
|
||||||
|
|
||||||
export interface OwnProps {
|
|
||||||
panel: PanelModel;
|
|
||||||
dashboard: DashboardModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DispatchProps {
|
|
||||||
addPanel: typeof addPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Props = OwnProps & DispatchProps;
|
|
||||||
|
|
||||||
const getCopiedPanelPlugins = () => {
|
|
||||||
const panels = chain(config.panels)
|
|
||||||
.filter({ hideFromList: false })
|
|
||||||
.map((item) => item)
|
|
||||||
.value();
|
|
||||||
const copiedPanels = [];
|
|
||||||
|
|
||||||
const copiedPanelJson = store.get(LS_PANEL_COPY_KEY);
|
|
||||||
if (copiedPanelJson) {
|
|
||||||
const copiedPanel = JSON.parse(copiedPanelJson);
|
|
||||||
const pluginInfo: any = find(panels, { id: copiedPanel.type });
|
|
||||||
if (pluginInfo) {
|
|
||||||
const pluginCopy = cloneDeep(pluginInfo);
|
|
||||||
pluginCopy.name = copiedPanel.title;
|
|
||||||
pluginCopy.sort = -1;
|
|
||||||
pluginCopy.defaults = copiedPanel;
|
|
||||||
copiedPanels.push(pluginCopy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortBy(copiedPanels, 'sort');
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AddPanelWidgetUnconnected = ({ panel, dashboard }: Props) => {
|
|
||||||
const [addPanelView, setAddPanelView] = useState(false);
|
|
||||||
|
|
||||||
const onCancelAddPanel = (evt: React.MouseEvent<HTMLButtonElement>) => {
|
|
||||||
evt.preventDefault();
|
|
||||||
dashboard.removePanel(panel);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onBack = () => {
|
|
||||||
setAddPanelView(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onCreateNewPanel = () => {
|
|
||||||
const { gridPos } = panel;
|
|
||||||
|
|
||||||
const newPanel: Partial<PanelModel> = {
|
|
||||||
type: 'timeseries',
|
|
||||||
title: 'Panel Title',
|
|
||||||
datasource: panel.datasource,
|
|
||||||
gridPos: { x: gridPos.x, y: gridPos.y, w: gridPos.w, h: gridPos.h },
|
|
||||||
isNew: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
dashboard.addPanel(newPanel);
|
|
||||||
dashboard.removePanel(panel);
|
|
||||||
|
|
||||||
locationService.partial({ editPanel: newPanel.id });
|
|
||||||
};
|
|
||||||
|
|
||||||
const onPasteCopiedPanel = (panelPluginInfo: PanelPluginInfo) => {
|
|
||||||
const { gridPos } = panel;
|
|
||||||
|
|
||||||
const newPanel = {
|
|
||||||
type: panelPluginInfo.id,
|
|
||||||
title: 'Panel Title',
|
|
||||||
gridPos: {
|
|
||||||
x: gridPos.x,
|
|
||||||
y: gridPos.y,
|
|
||||||
w: panelPluginInfo.defaults.gridPos.w,
|
|
||||||
h: panelPluginInfo.defaults.gridPos.h,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// apply panel template / defaults
|
|
||||||
if (panelPluginInfo.defaults) {
|
|
||||||
defaults(newPanel, panelPluginInfo.defaults);
|
|
||||||
newPanel.title = panelPluginInfo.defaults.title;
|
|
||||||
store.delete(LS_PANEL_COPY_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
dashboard.addPanel(newPanel);
|
|
||||||
dashboard.removePanel(panel);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onAddLibraryPanel = (panelInfo: LibraryElementDTO) => {
|
|
||||||
const { gridPos } = panel;
|
|
||||||
|
|
||||||
const newPanel = {
|
|
||||||
...panelInfo.model,
|
|
||||||
gridPos,
|
|
||||||
libraryPanel: panelInfo,
|
|
||||||
};
|
|
||||||
|
|
||||||
dashboard.addPanel(newPanel);
|
|
||||||
dashboard.removePanel(panel);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onCreateNewRow = () => {
|
|
||||||
const newRow = {
|
|
||||||
type: 'row',
|
|
||||||
title: 'Row title',
|
|
||||||
gridPos: { x: 0, y: 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
dashboard.addPanel(newRow);
|
|
||||||
dashboard.removePanel(panel);
|
|
||||||
};
|
|
||||||
|
|
||||||
const styles = useStyles2(getStyles);
|
|
||||||
const copiedPanelPlugins = useMemo(() => getCopiedPanelPlugins(), []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.wrapper}>
|
|
||||||
<div className={cx('panel-container', styles.callToAction)}>
|
|
||||||
<AddPanelWidgetHandle onCancel={onCancelAddPanel} onBack={addPanelView ? onBack : undefined} styles={styles}>
|
|
||||||
{addPanelView ? 'Add panel from panel library' : 'Add panel'}
|
|
||||||
</AddPanelWidgetHandle>
|
|
||||||
{addPanelView ? (
|
|
||||||
<LibraryPanelsSearch onClick={onAddLibraryPanel} variant={LibraryPanelsSearchVariant.Tight} showPanelFilter />
|
|
||||||
) : (
|
|
||||||
<div className={styles.actionsWrapper}>
|
|
||||||
<CardButton
|
|
||||||
icon="file-blank"
|
|
||||||
aria-label={selectors.pages.AddDashboard.addNewPanel}
|
|
||||||
onClick={() => {
|
|
||||||
reportInteraction('Create new panel');
|
|
||||||
onCreateNewPanel();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Add a new panel
|
|
||||||
</CardButton>
|
|
||||||
<CardButton
|
|
||||||
icon="wrap-text"
|
|
||||||
aria-label={selectors.pages.AddDashboard.addNewRow}
|
|
||||||
onClick={() => {
|
|
||||||
reportInteraction('Create new row');
|
|
||||||
onCreateNewRow();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Add a new row
|
|
||||||
</CardButton>
|
|
||||||
<CardButton
|
|
||||||
icon="book-open"
|
|
||||||
aria-label={selectors.pages.AddDashboard.addNewPanelLibrary}
|
|
||||||
onClick={() => {
|
|
||||||
reportInteraction('Add a panel from the panel library');
|
|
||||||
setAddPanelView(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Add a panel from the panel library
|
|
||||||
</CardButton>
|
|
||||||
{copiedPanelPlugins.length === 1 && (
|
|
||||||
<CardButton
|
|
||||||
icon="clipboard-alt"
|
|
||||||
aria-label={selectors.pages.AddDashboard.addNewPanelLibrary}
|
|
||||||
onClick={() => {
|
|
||||||
reportInteraction('Paste panel from clipboard');
|
|
||||||
onPasteCopiedPanel(copiedPanelPlugins[0]);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Paste panel from clipboard
|
|
||||||
</CardButton>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = { addPanel };
|
|
||||||
|
|
||||||
export const AddPanelWidget = connect(undefined, mapDispatchToProps)(AddPanelWidgetUnconnected);
|
|
||||||
|
|
||||||
interface AddPanelWidgetHandleProps {
|
|
||||||
onCancel: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
||||||
onBack?: () => void;
|
|
||||||
children?: string;
|
|
||||||
styles: AddPanelStyles;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AddPanelWidgetHandle = ({ children, onBack, onCancel, styles }: AddPanelWidgetHandleProps) => {
|
|
||||||
return (
|
|
||||||
<div className={cx(styles.headerRow, 'grid-drag-handle')}>
|
|
||||||
{onBack && (
|
|
||||||
<div className={styles.backButton}>
|
|
||||||
<IconButton name="arrow-left" onClick={onBack} size="xl" tooltip="Go back" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!onBack && (
|
|
||||||
<div className={styles.backButton}>
|
|
||||||
<Icon name="panel-add" size="xl" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{children && <span>{children}</span>}
|
|
||||||
<div className="flex-grow-1" />
|
|
||||||
<IconButton aria-label="Close 'Add Panel' widget" name="times" onClick={onCancel} tooltip="Close widget" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => {
|
|
||||||
const pulsate = keyframes`
|
|
||||||
0% {box-shadow: 0 0 0 2px ${theme.colors.background.canvas}, 0 0 0px 4px ${theme.colors.primary.main};}
|
|
||||||
50% {box-shadow: 0 0 0 2px ${theme.components.dashboard.background}, 0 0 0px 4px ${tinycolor(
|
|
||||||
theme.colors.primary.main
|
|
||||||
)
|
|
||||||
.darken(20)
|
|
||||||
.toHexString()};}
|
|
||||||
100% {box-shadow: 0 0 0 2px ${theme.components.dashboard.background}, 0 0 0px 4px ${theme.colors.primary.main};}
|
|
||||||
`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
// wrapper is used to make sure box-shadow animation isn't cut off in dashboard page
|
|
||||||
wrapper: css`
|
|
||||||
height: 100%;
|
|
||||||
padding-top: ${theme.spacing(0.5)};
|
|
||||||
`,
|
|
||||||
callToAction: css`
|
|
||||||
overflow: hidden;
|
|
||||||
outline: 2px dotted transparent;
|
|
||||||
outline-offset: 2px;
|
|
||||||
box-shadow:
|
|
||||||
0 0 0 2px black,
|
|
||||||
0 0 0px 4px #1f60c4;
|
|
||||||
animation: ${pulsate} 2s ease infinite;
|
|
||||||
`,
|
|
||||||
actionsWrapper: css`
|
|
||||||
height: 100%;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
column-gap: ${theme.spacing(1)};
|
|
||||||
row-gap: ${theme.spacing(1)};
|
|
||||||
padding: ${theme.spacing(0, 1, 1, 1)};
|
|
||||||
|
|
||||||
// This is to make the last action full width (if by itself)
|
|
||||||
& > div:nth-child(2n-1):nth-last-of-type(1) {
|
|
||||||
grid-column: span 2;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
headerRow: css`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
height: 38px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 100%;
|
|
||||||
font-size: ${theme.typography.fontSize};
|
|
||||||
font-weight: ${theme.typography.fontWeightMedium};
|
|
||||||
padding-left: ${theme.spacing(1)};
|
|
||||||
transition: background-color 0.1s ease-in-out;
|
|
||||||
cursor: move;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: ${theme.colors.background.secondary};
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
backButton: css`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
padding-left: ${theme.spacing(0.5)};
|
|
||||||
width: ${theme.spacing(4)};
|
|
||||||
`,
|
|
||||||
noMargin: css`
|
|
||||||
margin: 0;
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
type AddPanelStyles = ReturnType<typeof getStyles>;
|
|
@ -1,78 +0,0 @@
|
|||||||
.add-panel-widget-container {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-panel-widget {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-panel-widget__header {
|
|
||||||
top: 0;
|
|
||||||
position: absolute;
|
|
||||||
padding: 0 8px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
cursor: move;
|
|
||||||
background: $page-header-bg;
|
|
||||||
box-shadow: $page-header-shadow;
|
|
||||||
border-bottom: 1px solid $page-header-border-color;
|
|
||||||
|
|
||||||
.gicon {
|
|
||||||
font-size: 30px;
|
|
||||||
margin-right: $space-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transition: background-color 0.1s ease-in-out;
|
|
||||||
background-color: $panel-header-hover-bg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-panel-widget__title {
|
|
||||||
font-size: $font-size-md;
|
|
||||||
font-weight: $font-weight-semi-bold;
|
|
||||||
margin-right: $space-xl;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-panel-widget__link {
|
|
||||||
margin: 0 $space-sm;
|
|
||||||
width: 170px;
|
|
||||||
height: 88px !important;
|
|
||||||
flex-direction: column !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-panel-widget__icon {
|
|
||||||
margin-bottom: $space-sm;
|
|
||||||
|
|
||||||
.gicon {
|
|
||||||
color: white;
|
|
||||||
height: 44px;
|
|
||||||
width: 53px;
|
|
||||||
position: relative;
|
|
||||||
left: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-panel-widget__create {
|
|
||||||
display: inherit;
|
|
||||||
margin-bottom: $space-lg;
|
|
||||||
// this is to have the big button appear centered
|
|
||||||
margin-top: 55px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-panel-widget__actions {
|
|
||||||
display: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-panel-widget__action {
|
|
||||||
margin: 0 $space-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-panel-widget__btn-container {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export { AddPanelWidget } from './AddPanelWidget';
|
|
@ -59,7 +59,6 @@ export interface OwnProps {
|
|||||||
hideTimePicker: boolean;
|
hideTimePicker: boolean;
|
||||||
folderTitle?: string;
|
folderTitle?: string;
|
||||||
title: string;
|
title: string;
|
||||||
onAddPanel: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addCustomLeftAction(content: DynamicDashNavButtonModel) {
|
export function addCustomLeftAction(content: DynamicDashNavButtonModel) {
|
||||||
@ -251,7 +250,7 @@ export const DashNav = React.memo<Props>((props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderRightActions = () => {
|
const renderRightActions = () => {
|
||||||
const { dashboard, onAddPanel, isFullscreen, kioskMode, hideTimePicker } = props;
|
const { dashboard, isFullscreen, kioskMode, hideTimePicker } = props;
|
||||||
const { canSave, canEdit, showSettings, canShare } = dashboard.meta;
|
const { canSave, canEdit, showSettings, canShare } = dashboard.meta;
|
||||||
const { snapshot } = dashboard;
|
const { snapshot } = dashboard;
|
||||||
const snapshotUrl = snapshot && snapshot.originalUrl;
|
const snapshotUrl = snapshot && snapshot.originalUrl;
|
||||||
@ -310,25 +309,13 @@ export const DashNav = React.memo<Props>((props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (canEdit && !isFullscreen) {
|
if (canEdit && !isFullscreen) {
|
||||||
if (config.featureToggles.emptyDashboardPage) {
|
buttons.push(
|
||||||
buttons.push(
|
<AddPanelButton
|
||||||
<AddPanelButton
|
dashboard={dashboard}
|
||||||
dashboard={dashboard}
|
onToolbarAddMenuOpen={DashboardInteractions.toolbarAddClick}
|
||||||
onToolbarAddMenuOpen={DashboardInteractions.toolbarAddClick}
|
key="panel-add-dropdown"
|
||||||
key="panel-add-dropdown"
|
/>
|
||||||
/>
|
);
|
||||||
);
|
|
||||||
} else {
|
|
||||||
buttons.push(
|
|
||||||
<ToolbarButton
|
|
||||||
tooltip={t('dashboard.toolbar.add-panel', 'Add panel')}
|
|
||||||
icon="panel-add"
|
|
||||||
iconSize="xl"
|
|
||||||
onClick={onAddPanel}
|
|
||||||
key="button-panel-add"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canShare) {
|
if (canShare) {
|
||||||
|
@ -37,7 +37,6 @@ import { liveTimer } from '../dashgrid/liveTimer';
|
|||||||
import { getTimeSrv } from '../services/TimeSrv';
|
import { getTimeSrv } from '../services/TimeSrv';
|
||||||
import { cleanUpDashboardAndVariables } from '../state/actions';
|
import { cleanUpDashboardAndVariables } from '../state/actions';
|
||||||
import { initDashboard } from '../state/initDashboard';
|
import { initDashboard } from '../state/initDashboard';
|
||||||
import { calculateNewPanelGridPos } from '../utils/panel';
|
|
||||||
|
|
||||||
import { DashboardPageRouteParams, DashboardPageRouteSearchParams } from './types';
|
import { DashboardPageRouteParams, DashboardPageRouteSearchParams } from './types';
|
||||||
|
|
||||||
@ -265,29 +264,6 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
return updateStatePageNavFromProps(props, updatedState);
|
return updateStatePageNavFromProps(props, updatedState);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Todo: Remove this when we remove the emptyDashboardPage toggle
|
|
||||||
onAddPanel = () => {
|
|
||||||
const { dashboard } = this.props;
|
|
||||||
|
|
||||||
if (!dashboard) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return if the "Add panel" exists already
|
|
||||||
if (dashboard.panels.length > 0 && dashboard.panels[0].type === 'add-panel') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dashboard.addPanel({
|
|
||||||
type: 'add-panel',
|
|
||||||
gridPos: calculateNewPanelGridPos(dashboard),
|
|
||||||
title: 'Panel Title',
|
|
||||||
});
|
|
||||||
|
|
||||||
// scroll to top after adding panel
|
|
||||||
this.setState({ updateScrollTop: 0 });
|
|
||||||
};
|
|
||||||
|
|
||||||
setScrollRef = (scrollElement: HTMLDivElement): void => {
|
setScrollRef = (scrollElement: HTMLDivElement): void => {
|
||||||
this.setState({ scrollElement });
|
this.setState({ scrollElement });
|
||||||
};
|
};
|
||||||
@ -355,7 +331,6 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
title={dashboard.title}
|
title={dashboard.title}
|
||||||
folderTitle={dashboard.meta.folderTitle}
|
folderTitle={dashboard.meta.folderTitle}
|
||||||
isFullscreen={!!viewPanel}
|
isFullscreen={!!viewPanel}
|
||||||
onAddPanel={this.onAddPanel}
|
|
||||||
kioskMode={kioskMode}
|
kioskMode={kioskMode}
|
||||||
hideTimePicker={dashboard.timepicker.hidden}
|
hideTimePicker={dashboard.timepicker.hidden}
|
||||||
/>
|
/>
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { config, getDataSourceSrv, locationService } from '@grafana/runtime';
|
import { getDataSourceSrv, locationService } from '@grafana/runtime';
|
||||||
import { Page } from 'app/core/components/Page/Page';
|
import { Page } from 'app/core/components/Page/Page';
|
||||||
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
||||||
import { useDispatch } from 'app/types';
|
import { useDispatch } from 'app/types';
|
||||||
|
|
||||||
import { getNewDashboardModelData, setDashboardToFetchFromLocalStorage } from '../state/initDashboard';
|
|
||||||
import { setInitialDatasource } from '../state/reducers';
|
import { setInitialDatasource } from '../state/reducers';
|
||||||
|
|
||||||
export default function NewDashboardWithDS(props: GrafanaRouteComponentProps<{ datasourceUid: string }>) {
|
export default function NewDashboardWithDS(props: GrafanaRouteComponentProps<{ datasourceUid: string }>) {
|
||||||
@ -20,21 +19,7 @@ export default function NewDashboardWithDS(props: GrafanaRouteComponentProps<{ d
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.featureToggles.emptyDashboardPage) {
|
dispatch(setInitialDatasource(datasourceUid));
|
||||||
const newDashboard = getNewDashboardModelData();
|
|
||||||
const { dashboard } = newDashboard;
|
|
||||||
dashboard.panels[0] = {
|
|
||||||
...dashboard.panels[0],
|
|
||||||
datasource: {
|
|
||||||
uid: ds.uid,
|
|
||||||
type: ds.type,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
setDashboardToFetchFromLocalStorage(newDashboard);
|
|
||||||
} else {
|
|
||||||
dispatch(setInitialDatasource(datasourceUid));
|
|
||||||
}
|
|
||||||
|
|
||||||
locationService.replace('/dashboard/new');
|
locationService.replace('/dashboard/new');
|
||||||
}, [datasourceUid, dispatch]);
|
}, [datasourceUid, dispatch]);
|
||||||
|
@ -14,7 +14,6 @@ import { VariablesChanged } from 'app/features/variables/types';
|
|||||||
import { DashboardPanelsChangedEvent } from 'app/types/events';
|
import { DashboardPanelsChangedEvent } from 'app/types/events';
|
||||||
|
|
||||||
import { AddLibraryPanelWidget } from '../components/AddLibraryPanelWidget';
|
import { AddLibraryPanelWidget } from '../components/AddLibraryPanelWidget';
|
||||||
import { AddPanelWidget } from '../components/AddPanelWidget';
|
|
||||||
import { DashboardRow } from '../components/DashboardRow';
|
import { DashboardRow } from '../components/DashboardRow';
|
||||||
import { DashboardModel, PanelModel } from '../state';
|
import { DashboardModel, PanelModel } from '../state';
|
||||||
import { GridPos } from '../state/PanelModel';
|
import { GridPos } from '../state/PanelModel';
|
||||||
@ -262,11 +261,6 @@ export class DashboardGrid extends PureComponent<Props, State> {
|
|||||||
return <DashboardRow key={panel.key} panel={panel} dashboard={this.props.dashboard} />;
|
return <DashboardRow key={panel.key} panel={panel} dashboard={this.props.dashboard} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Todo: Remove this when we remove the emptyDashboardPage toggle
|
|
||||||
if (panel.type === 'add-panel') {
|
|
||||||
return <AddPanelWidget key={panel.key} panel={panel} dashboard={this.props.dashboard} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (panel.type === 'add-library-panel') {
|
if (panel.type === 'add-library-panel') {
|
||||||
return <AddLibraryPanelWidget key={panel.key} panel={panel} dashboard={this.props.dashboard} />;
|
return <AddLibraryPanelWidget key={panel.key} panel={panel} dashboard={this.props.dashboard} />;
|
||||||
}
|
}
|
||||||
@ -302,7 +296,7 @@ export class DashboardGrid extends PureComponent<Props, State> {
|
|||||||
render() {
|
render() {
|
||||||
const { isEditable, dashboard } = this.props;
|
const { isEditable, dashboard } = this.props;
|
||||||
|
|
||||||
if (config.featureToggles.emptyDashboardPage && dashboard.panels.length === 0) {
|
if (dashboard.panels.length === 0) {
|
||||||
return <DashboardEmpty dashboard={dashboard} canCreate={isEditable} />;
|
return <DashboardEmpty dashboard={dashboard} canCreate={isEditable} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,17 +116,11 @@ describe('DashboardModel', () => {
|
|||||||
expect(keys[1]).toBe('editable');
|
expect(keys[1]).toBe('editable');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove add panel panels', () => {
|
it('should have only 1 panel after adding panel to a new dashboard', () => {
|
||||||
const model = createDashboardModelFixture();
|
const model = createDashboardModelFixture();
|
||||||
model.addPanel({
|
|
||||||
type: 'add-panel',
|
|
||||||
});
|
|
||||||
model.addPanel({
|
model.addPanel({
|
||||||
type: 'graph',
|
type: 'graph',
|
||||||
});
|
});
|
||||||
model.addPanel({
|
|
||||||
type: 'add-panel',
|
|
||||||
});
|
|
||||||
const saveModel = model.getSaveModelClone();
|
const saveModel = model.getSaveModelClone();
|
||||||
const panels = saveModel.panels;
|
const panels = saveModel.panels;
|
||||||
|
|
||||||
|
@ -302,12 +302,8 @@ export class DashboardModel implements TimeModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getPanelSaveModels() {
|
private getPanelSaveModels() {
|
||||||
// Todo: Remove panel.type === 'add-panel' when we remove the emptyDashboardPage toggle
|
|
||||||
return this.panels
|
return this.panels
|
||||||
.filter(
|
.filter((panel) => this.isSnapshotTruthy() || !(panel.repeatPanelId || panel.repeatedByRow))
|
||||||
(panel) =>
|
|
||||||
this.isSnapshotTruthy() || !(panel.type === 'add-panel' || panel.repeatPanelId || panel.repeatedByRow)
|
|
||||||
)
|
|
||||||
.map((panel) => {
|
.map((panel) => {
|
||||||
// Clean libarary panels on save
|
// Clean libarary panels on save
|
||||||
if (panel.libraryPanel) {
|
if (panel.libraryPanel) {
|
||||||
|
@ -91,7 +91,7 @@ function describeInitScenario(description: string, scenarioFn: ScenarioFn) {
|
|||||||
title: 'My cool dashboard',
|
title: 'My cool dashboard',
|
||||||
panels: [
|
panels: [
|
||||||
{
|
{
|
||||||
type: 'add-panel',
|
type: 'stat',
|
||||||
gridPos: { x: 0, y: 0, w: 12, h: 9 },
|
gridPos: { x: 0, y: 0, w: 12, h: 9 },
|
||||||
title: 'Panel Title',
|
title: 'Panel Title',
|
||||||
id: 2,
|
id: 2,
|
||||||
|
@ -130,7 +130,7 @@ async function fetchDashboard(
|
|||||||
if (args.urlFolderUid) {
|
if (args.urlFolderUid) {
|
||||||
await dispatch(getFolderByUid(args.urlFolderUid));
|
await dispatch(getFolderByUid(args.urlFolderUid));
|
||||||
}
|
}
|
||||||
return getNewDashboardModelData(args.urlFolderUid, args.panelType);
|
return getNewDashboardModelData(args.urlFolderUid);
|
||||||
}
|
}
|
||||||
case DashboardRoutes.Path: {
|
case DashboardRoutes.Path: {
|
||||||
const path = args.urlSlug ?? '';
|
const path = args.urlSlug ?? '';
|
||||||
@ -300,20 +300,7 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult<void> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNewDashboardModelData(
|
export function getNewDashboardModelData(urlFolderUid?: string): { dashboard: any; meta: DashboardMeta } {
|
||||||
urlFolderUid?: string,
|
|
||||||
panelType?: string
|
|
||||||
): { dashboard: any; meta: DashboardMeta } {
|
|
||||||
const panels = config.featureToggles.emptyDashboardPage
|
|
||||||
? []
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
type: panelType ?? 'add-panel',
|
|
||||||
gridPos: { x: 0, y: 0, w: 12, h: 9 },
|
|
||||||
title: 'Panel Title',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
meta: {
|
meta: {
|
||||||
canStar: false,
|
canStar: false,
|
||||||
@ -324,7 +311,7 @@ export function getNewDashboardModelData(
|
|||||||
},
|
},
|
||||||
dashboard: {
|
dashboard: {
|
||||||
title: 'New dashboard',
|
title: 'New dashboard',
|
||||||
panels,
|
panels: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -22,15 +22,6 @@ interface AddPanelToDashboardOptions {
|
|||||||
time: Dashboard['time'];
|
time: Dashboard['time'];
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDashboard(): DashboardDTO {
|
|
||||||
const dto = getNewDashboardModelData();
|
|
||||||
|
|
||||||
// getNewDashboardModelData adds by default the "add-panel" panel. We don't want that.
|
|
||||||
dto.dashboard.panels = [];
|
|
||||||
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns transformations for the logs table visualisation in explore.
|
* Returns transformations for the logs table visualisation in explore.
|
||||||
* If the logs table supports a labels column, we need to extract the fields.
|
* If the logs table supports a labels column, we need to extract the fields.
|
||||||
@ -96,7 +87,7 @@ export async function setDashboardInLocalStorage(options: AddPanelToDashboardOpt
|
|||||||
throw AddToDashboardError.FETCH_DASHBOARD;
|
throw AddToDashboardError.FETCH_DASHBOARD;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dto = createDashboard();
|
dto = getNewDashboardModelData();
|
||||||
}
|
}
|
||||||
|
|
||||||
dto.dashboard.panels = [panel, ...(dto.dashboard.panels ?? [])];
|
dto.dashboard.panels = [panel, ...(dto.dashboard.panels ?? [])];
|
||||||
|
@ -31,7 +31,6 @@
|
|||||||
@import 'layout/lists';
|
@import 'layout/lists';
|
||||||
|
|
||||||
// COMPONENTS
|
// COMPONENTS
|
||||||
@import '../app/features/dashboard/components/AddPanelWidget/AddPanelWidget';
|
|
||||||
@import 'components/scrollbar';
|
@import 'components/scrollbar';
|
||||||
@import 'components/buttons';
|
@import 'components/buttons';
|
||||||
@import 'components/navs';
|
@import 'components/navs';
|
||||||
|
Loading…
Reference in New Issue
Block a user