Datasource Onboarding: Create initial data source onboarding page (#58795)

This commit is contained in:
kay delaney 2022-12-07 23:29:38 +00:00 committed by GitHub
parent 74167b4d44
commit 955bf55c6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 250 additions and 1 deletions

View File

@ -82,6 +82,7 @@ export interface FeatureToggles {
nestedFolders?: boolean;
accessTokenExpirationCheck?: boolean;
elasticsearchBackendMigration?: boolean;
datasourceOnboarding?: boolean;
secureSocksDatasourceProxy?: boolean;
authnService?: boolean;
sessionRemoteCache?: boolean;

View File

@ -373,6 +373,11 @@ var (
Description: "Use Elasticsearch as backend data source",
State: FeatureStateAlpha,
},
{
Name: "datasourceOnboarding",
Description: "Enable data source onboarding page",
State: FeatureStateAlpha,
},
{
Name: "secureSocksDatasourceProxy",
Description: "Enable secure socks tunneling for supported core datasources",

View File

@ -271,6 +271,10 @@ const (
// Use Elasticsearch as backend data source
FlagElasticsearchBackendMigration = "elasticsearchBackendMigration"
// FlagDatasourceOnboarding
// Enable data source onboarding page
FlagDatasourceOnboarding = "datasourceOnboarding"
// FlagSecureSocksDatasourceProxy
// Enable secure socks tunneling for supported core datasources
FlagSecureSocksDatasourceProxy = "secureSocksDatasourceProxy"

View File

@ -0,0 +1,209 @@
import { css } from '@emotion/css';
import React from 'react';
import { useAsync } from 'react-use';
import { DataSourcePluginMeta, GrafanaTheme2, PageLayoutType } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime';
import { Icon, useStyles2 } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
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',
];
export function DatasourceOnboarding({
onNewDashboard,
loading = false,
}: {
onNewDashboard?: () => void;
loading?: boolean;
}) {
const styles = useStyles2(getStyles);
const { value: datasources, loading: loadingDatasources } = useAsync(async () => {
const datasourceMeta: DataSourcePluginMeta[] = await getBackendSrv().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="dashboards/browse"
pageNav={{ text: t('dashboard', 'New dashboard'), url: '/dashboard/new' }}
layout={PageLayoutType.Canvas}
>
<div className={styles.wrapper}>
<h1 className={styles.title}>{t('datasource-onboarding.welcome', 'Welcome to Grafana dashboards!')}</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={() => onAddDatasource(d)}>
<img
src={d.info.logos.small}
alt={t('datasource-onboarding.logo', 'Logo for {{datasourceName}} data source', {
datasourceName: d.name,
})}
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}>
<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={onNewDashboard} className={styles.createNew}>
<span>{t('datasource-onboarding.sampleData', 'Or set up a new dashboard with sample data')}</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',
}),
createNew: 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

@ -0,0 +1,30 @@
import React, { useState } from 'react';
import { useEffectOnce } from 'react-use';
import { config } from '@grafana/runtime';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { loadDataSources } from 'app/features/datasources/state';
import { useDispatch, useSelector } from 'app/types';
import DashboardPage from './DashboardPage';
import { DatasourceOnboarding } from './DatasourceOnboarding';
export default function NewDashboardPage(props: GrafanaRouteComponentProps) {
const dispatch = useDispatch();
useEffectOnce(() => {
dispatch(loadDataSources());
});
const { hasDatasource, loading } = useSelector((state) => ({
hasDatasource: state.dataSources.dataSourcesCount > 0,
loading: !state.dataSources.hasFetched,
}));
const [createDashboard, setCreateDashboard] = useState(false);
const showDashboardPage = hasDatasource || createDashboard || !config.featureToggles.datasourceOnboarding;
return showDashboardPage ? (
<DashboardPage {...props} />
) : (
<DatasourceOnboarding onNewDashboard={() => setCreateDashboard(true)} loading={loading} />
);
}

View File

@ -88,7 +88,7 @@ export function getAppRoutes(): RouteDescriptor[] {
pageClass: 'page-dashboard',
routeName: DashboardRoutes.New,
component: SafeDynamicImport(
() => import(/* webpackChunkName: "DashboardPage" */ '../features/dashboard/containers/DashboardPage')
() => import(/* webpackChunkName: "DashboardPage" */ '../features/dashboard/containers/NewDashboardPage')
),
},
{