mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboards: Remove no-datasource screen (#68574)
* Dashboards: Remove non-ds configured landing page * Remove `datasourceOnboarding` feature toggle
This commit is contained in:
parent
5f84dad7e4
commit
1505a188eb
@ -88,7 +88,6 @@ Alpha features might be changed or removed without prior notice.
|
||||
| `showDashboardValidationWarnings` | Show warnings when dashboards do not validate against the schema |
|
||||
| `mysqlAnsiQuotes` | Use double quotes to escape keyword in a MySQL query |
|
||||
| `showTraceId` | Show trace ids for requests |
|
||||
| `datasourceOnboarding` | Enable data source onboarding page |
|
||||
| `authnService` | Use new auth service to perform authentication |
|
||||
| `alertingBacktesting` | Rule backtesting API for alerting |
|
||||
| `editPanelCSVDragAndDrop` | Enables drag and drop for CSV and Excel files |
|
||||
|
@ -59,7 +59,6 @@ export interface FeatureToggles {
|
||||
nestedFolders?: boolean;
|
||||
accessTokenExpirationCheck?: boolean;
|
||||
showTraceId?: boolean;
|
||||
datasourceOnboarding?: boolean;
|
||||
emptyDashboardPage?: boolean;
|
||||
authnService?: boolean;
|
||||
disablePrometheusExemplarSampling?: boolean;
|
||||
|
@ -277,12 +277,6 @@ var (
|
||||
State: FeatureStateAlpha,
|
||||
Owner: grafanaObservabilityLogsSquad,
|
||||
},
|
||||
{
|
||||
Name: "datasourceOnboarding",
|
||||
Description: "Enable data source onboarding page",
|
||||
State: FeatureStateAlpha,
|
||||
Owner: grafanaDashboardsSquad,
|
||||
},
|
||||
{
|
||||
Name: "emptyDashboardPage",
|
||||
Description: "Enable the redesigned user interface of a dashboard page that includes no panels",
|
||||
|
@ -40,7 +40,6 @@ accessControlOnCall,beta,@grafana/grafana-authnz-team,false,false,false,false
|
||||
nestedFolders,beta,@grafana/backend-platform,false,false,false,false
|
||||
accessTokenExpirationCheck,stable,@grafana/grafana-authnz-team,false,false,false,false
|
||||
showTraceId,alpha,@grafana/observability-logs,false,false,false,false
|
||||
datasourceOnboarding,alpha,@grafana/dashboards-squad,false,false,false,false
|
||||
emptyDashboardPage,stable,@grafana/dashboards-squad,false,false,false,true
|
||||
authnService,alpha,@grafana/grafana-authnz-team,false,false,false,false
|
||||
disablePrometheusExemplarSampling,stable,@grafana/observability-metrics,false,false,false,false
|
||||
|
|
@ -171,10 +171,6 @@ const (
|
||||
// Show trace ids for requests
|
||||
FlagShowTraceId = "showTraceId"
|
||||
|
||||
// FlagDatasourceOnboarding
|
||||
// Enable data source onboarding page
|
||||
FlagDatasourceOnboarding = "datasourceOnboarding"
|
||||
|
||||
// FlagEmptyDashboardPage
|
||||
// Enable the redesigned user interface of a dashboard page that includes no panels
|
||||
FlagEmptyDashboardPage = "emptyDashboardPage"
|
||||
|
@ -1,33 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
||||
import { EmptyStateNoDatasource } from 'app/features/datasources/components/EmptyStateNoDatasource';
|
||||
import { useLoadDataSources } from 'app/features/datasources/state';
|
||||
import { useSelector } from 'app/types';
|
||||
|
||||
import DashboardPage from './DashboardPage';
|
||||
|
||||
export default function NewDashboardPage(props: GrafanaRouteComponentProps) {
|
||||
const { isLoading } = useLoadDataSources();
|
||||
|
||||
const { hasDatasource } = useSelector((state) => ({
|
||||
hasDatasource: state.dataSources.dataSourcesCount > 0,
|
||||
}));
|
||||
const [createDashboard, setCreateDashboard] = useState(false);
|
||||
const showDashboardPage = hasDatasource || createDashboard || !config.featureToggles.datasourceOnboarding;
|
||||
|
||||
return showDashboardPage ? (
|
||||
<DashboardPage {...props} />
|
||||
) : (
|
||||
<EmptyStateNoDatasource
|
||||
onCTAClick={() => setCreateDashboard(true)}
|
||||
loading={isLoading}
|
||||
title={t('datasource-onboarding.welcome', 'Welcome to Grafana dashboards!')}
|
||||
CTAText={t('datasource-onboarding.sampleData', 'Or set up a new dashboard with sample data')}
|
||||
navId="dashboards/browse"
|
||||
pageNav={{ text: t('datasource-onboarding.new-dashboard', 'New dashboard'), url: '/dashboard/new' }}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import React from 'react';
|
||||
import { DeepPartial } from 'react-hook-form';
|
||||
import { TestProvider } from 'test/helpers/TestProvider';
|
||||
|
||||
import { DataSourcePluginMeta } from '@grafana/data';
|
||||
import * as runtime from '@grafana/runtime';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import 'whatwg-fetch';
|
||||
|
||||
import { EmptyStateNoDatasource } from './EmptyStateNoDatasource';
|
||||
|
||||
let reportInteractionSpy: jest.SpyInstance;
|
||||
const server = setupServer();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(contextSrv, 'hasRole').mockReturnValue(true);
|
||||
reportInteractionSpy = jest.spyOn(runtime, 'reportInteraction');
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
server.listen({ onUnhandledRequest: 'bypass' });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
server.close();
|
||||
});
|
||||
|
||||
describe('EmptyStateNoDatasource', () => {
|
||||
it('correctly tracks user interactions', async () => {
|
||||
server.use(
|
||||
rest.get('/api/plugins', (_, res, ctx) => {
|
||||
return res(
|
||||
ctx.json<Array<DeepPartial<DataSourcePluginMeta>>>([
|
||||
{ id: 'prometheus', name: 'Prometheus', info: { logos: { small: 'prometheus.png' } } },
|
||||
{ id: 'mysql', name: 'MySQL', info: { logos: { small: 'mysql.png' } } },
|
||||
{ id: 'elasticsearch', name: 'Elasticsearch', info: { logos: { small: 'elasticsearch.png' } } },
|
||||
{ id: 'influxdb', name: 'InfluxDB', info: { logos: { small: 'influxdb.png' } } },
|
||||
{ id: 'graphite', name: 'Graphite', info: { logos: { small: 'graphite.png' } } },
|
||||
{ id: 'stackdriver', name: 'StackDriver', info: { logos: { small: 'stackdriver.png' } } },
|
||||
{ id: 'cloudwatch', name: 'CloudWatch', info: { logos: { small: 'cloudwatch.png' } } },
|
||||
{
|
||||
id: 'grafana-azure-monitor-datasource',
|
||||
name: 'Azure Monitor',
|
||||
info: { logos: { small: 'grafana-azure-monitor-datasource.png' } },
|
||||
},
|
||||
])
|
||||
);
|
||||
})
|
||||
);
|
||||
render(
|
||||
<TestProvider>
|
||||
<EmptyStateNoDatasource title="A Title" CTAText="CTA" />
|
||||
</TestProvider>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('heading', { name: 'A Title' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Prometheus' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Prometheus' }));
|
||||
expect(reportInteractionSpy).toHaveBeenLastCalledWith('dashboards_connectds_ds_clicked');
|
||||
|
||||
fireEvent.click(screen.getByRole('link', { name: 'View all' }));
|
||||
expect(reportInteractionSpy).toHaveBeenCalledWith('dashboards_connectds_viewall_clicked');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'CTA' }));
|
||||
expect(reportInteractionSpy).toHaveBeenLastCalledWith('dashboards_connectds_sampledata_clicked');
|
||||
});
|
||||
});
|
@ -1,215 +0,0 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { ComponentProps } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { DataSourcePluginMeta, GrafanaTheme2, PageLayoutType } from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { Icon, useStyles2 } from '@grafana/ui';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { useAddDatasource } from 'app/features/datasources/state';
|
||||
|
||||
const topDatasources = [
|
||||
'prometheus',
|
||||
'mysql',
|
||||
'elasticsearch',
|
||||
'influxdb',
|
||||
'graphite',
|
||||
'stackdriver',
|
||||
'cloudwatch',
|
||||
'grafana-azure-monitor-datasource',
|
||||
];
|
||||
|
||||
interface Props extends Pick<ComponentProps<typeof Page>, 'navId' | 'pageNav'> {
|
||||
title: string;
|
||||
CTAText: string;
|
||||
onCTAClick?: () => void;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export function EmptyStateNoDatasource({ onCTAClick, loading = false, title, CTAText, navId, pageNav }: Props) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const { backend } = useGrafana();
|
||||
const { value: datasources, loading: loadingDatasources } = useAsync(async () => {
|
||||
const datasourceMeta: DataSourcePluginMeta[] = await backend.get('/api/plugins', {
|
||||
enabled: 1,
|
||||
type: 'datasource',
|
||||
});
|
||||
|
||||
const byId = datasourceMeta.reduce<Record<string, DataSourcePluginMeta>>((prev, cur) => {
|
||||
prev[cur.id] = cur;
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
return topDatasources.map((d) => byId[d]);
|
||||
}, []);
|
||||
|
||||
const onAddDatasource = useAddDatasource();
|
||||
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Page navId={navId} pageNav={pageNav} layout={PageLayoutType.Canvas}>
|
||||
<div className={styles.wrapper}>
|
||||
<h1 className={styles.title}>{title}</h1>
|
||||
<div className={styles.description}>
|
||||
<h4 className={styles.explanation}>
|
||||
{t('datasource-onboarding.explanation', "To visualize your data, you'll need to connect it first.")}
|
||||
</h4>
|
||||
</div>
|
||||
{contextSrv.hasRole('Admin') ? (
|
||||
<>
|
||||
<h4 className={styles.preferredDataSource}>
|
||||
{t('datasource-onboarding.preferred', 'Connect your preferred data source:')}
|
||||
</h4>
|
||||
{!loadingDatasources && datasources !== undefined && (
|
||||
<ul className={styles.datasources}>
|
||||
{datasources.map((d) => (
|
||||
<li key={d.id}>
|
||||
<button
|
||||
onClick={() => {
|
||||
reportInteraction('dashboards_connectds_ds_clicked');
|
||||
onAddDatasource(d);
|
||||
}}
|
||||
>
|
||||
<img src={d.info.logos.small} alt="" height="16" width="16" className={styles.logo} />
|
||||
<span className={styles.datasourceName}>{d.name}</span>
|
||||
<Icon name="arrow-right" size="lg" className={styles.arrowIcon} />
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<a
|
||||
href="/datasources/new"
|
||||
className={styles.viewAll}
|
||||
onClick={() => reportInteraction('dashboards_connectds_viewall_clicked')}
|
||||
>
|
||||
<span>{t('datasource-onboarding.viewAll', 'View all')}</span>
|
||||
<Icon name="arrow-right" size="lg" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<h4>
|
||||
{t('datasource-onboarding.contact-admin', 'Please contact your administrator to configure data sources.')}
|
||||
</h4>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
reportInteraction('dashboards_connectds_sampledata_clicked');
|
||||
onCTAClick?.();
|
||||
}}
|
||||
className={styles.ctaButton}
|
||||
>
|
||||
<span>{CTAText}</span>
|
||||
<Icon name="arrow-right" size="lg" />
|
||||
</button>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
wrapper: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}),
|
||||
title: css({
|
||||
textAlign: 'center',
|
||||
fontSize: theme.typography.pxToRem(32),
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
}),
|
||||
description: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(1),
|
||||
maxWidth: '50vw',
|
||||
marginBottom: theme.spacing(8),
|
||||
color: theme.colors.text.secondary,
|
||||
}),
|
||||
explanation: css({
|
||||
marginBottom: '0px',
|
||||
textAlign: 'center',
|
||||
fontSize: theme.typography.pxToRem(19),
|
||||
}),
|
||||
preferredDataSource: css({
|
||||
marginBottom: theme.spacing(3),
|
||||
fontSize: theme.typography.pxToRem(19),
|
||||
fontWeight: theme.typography.fontWeightRegular,
|
||||
}),
|
||||
datasources: css({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: `repeat(auto-fit, minmax(${theme.spacing(28)}, 1fr))`,
|
||||
gap: theme.spacing(2),
|
||||
listStyle: 'none',
|
||||
width: '100%',
|
||||
maxWidth: theme.spacing(88),
|
||||
|
||||
'> li': {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
'> button': {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: theme.spacing(7),
|
||||
gap: theme.spacing(2),
|
||||
margin: '0px',
|
||||
padding: `calc(${theme.spacing(2)} - 1px)`,
|
||||
lineHeight: theme.spacing(3),
|
||||
border: `1px solid ${theme.colors.border.weak}`,
|
||||
borderRadius: theme.shape.borderRadius(1),
|
||||
background: theme.colors.background.primary,
|
||||
fontSize: theme.typography.pxToRem(19),
|
||||
color: 'inherit',
|
||||
},
|
||||
},
|
||||
}),
|
||||
logo: css({
|
||||
width: theme.spacing(2),
|
||||
height: theme.spacing(2),
|
||||
objectFit: 'contain',
|
||||
}),
|
||||
datasourceName: css({
|
||||
marginRight: 'auto',
|
||||
}),
|
||||
arrowIcon: css({
|
||||
color: theme.colors.text.link,
|
||||
}),
|
||||
viewAll: css({
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: theme.spacing(2),
|
||||
lineHeight: theme.spacing(3),
|
||||
fontSize: theme.typography.pxToRem(19),
|
||||
color: theme.colors.text.link,
|
||||
textDecoration: 'underline',
|
||||
textUnderlinePosition: 'under',
|
||||
}),
|
||||
ctaButton: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(1),
|
||||
margin: '0px',
|
||||
marginTop: theme.spacing(8),
|
||||
padding: theme.spacing(0),
|
||||
border: 'none',
|
||||
background: 'inherit',
|
||||
fontSize: theme.typography.h6.fontSize,
|
||||
color: theme.colors.text.secondary,
|
||||
}),
|
||||
};
|
||||
}
|
@ -50,7 +50,7 @@ export function getAppRoutes(): RouteDescriptor[] {
|
||||
pageClass: 'page-dashboard',
|
||||
routeName: DashboardRoutes.New,
|
||||
component: SafeDynamicImport(
|
||||
() => import(/* webpackChunkName: "DashboardPage" */ '../features/dashboard/containers/NewDashboardPage')
|
||||
() => import(/* webpackChunkName: "DashboardPage" */ '../features/dashboard/containers/DashboardPage')
|
||||
),
|
||||
},
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user