Dashboards: Remove no-datasource screen (#68574)

* Dashboards: Remove non-ds configured landing page

* Remove `datasourceOnboarding` feature toggle
This commit is contained in:
Ivan Ortega Alba 2023-05-17 14:22:40 +02:00 committed by GitHub
parent 5f84dad7e4
commit 1505a188eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1 additions and 340 deletions

View File

@ -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 |

View File

@ -59,7 +59,6 @@ export interface FeatureToggles {
nestedFolders?: boolean;
accessTokenExpirationCheck?: boolean;
showTraceId?: boolean;
datasourceOnboarding?: boolean;
emptyDashboardPage?: boolean;
authnService?: boolean;
disablePrometheusExemplarSampling?: boolean;

View File

@ -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",

View File

@ -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

1 Name State Owner requiresDevMode RequiresLicense RequiresRestart FrontendOnly
40 nestedFolders beta @grafana/backend-platform false false false false
41 accessTokenExpirationCheck stable @grafana/grafana-authnz-team false false false false
42 showTraceId alpha @grafana/observability-logs false false false false
datasourceOnboarding alpha @grafana/dashboards-squad false false false false
43 emptyDashboardPage stable @grafana/dashboards-squad false false false true
44 authnService alpha @grafana/grafana-authnz-team false false false false
45 disablePrometheusExemplarSampling stable @grafana/observability-metrics false false false false

View File

@ -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"

View File

@ -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' }}
/>
);
}

View File

@ -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');
});
});

View File

@ -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,
}),
};
}

View File

@ -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')
),
},
{