From 955bf55c6a6a710a2fd7ebdbdac2d9a826858eaf Mon Sep 17 00:00:00 2001 From: kay delaney <45561153+kaydelaney@users.noreply.github.com> Date: Wed, 7 Dec 2022 23:29:38 +0000 Subject: [PATCH] Datasource Onboarding: Create initial data source onboarding page (#58795) --- .../src/types/featureToggles.gen.ts | 1 + pkg/services/featuremgmt/registry.go | 5 + pkg/services/featuremgmt/toggles_gen.go | 4 + .../containers/DatasourceOnboarding.tsx | 209 ++++++++++++++++++ .../dashboard/containers/NewDashboardPage.tsx | 30 +++ public/app/routes/routes.tsx | 2 +- 6 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 public/app/features/dashboard/containers/DatasourceOnboarding.tsx create mode 100644 public/app/features/dashboard/containers/NewDashboardPage.tsx diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 8664082f5a0..eedcf76298e 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -82,6 +82,7 @@ export interface FeatureToggles { nestedFolders?: boolean; accessTokenExpirationCheck?: boolean; elasticsearchBackendMigration?: boolean; + datasourceOnboarding?: boolean; secureSocksDatasourceProxy?: boolean; authnService?: boolean; sessionRemoteCache?: boolean; diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index f0d32770ee5..d430d06fb3e 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -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", diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 708787cd608..d27beaaca21 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -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" diff --git a/public/app/features/dashboard/containers/DatasourceOnboarding.tsx b/public/app/features/dashboard/containers/DatasourceOnboarding.tsx new file mode 100644 index 00000000000..36abb698e6c --- /dev/null +++ b/public/app/features/dashboard/containers/DatasourceOnboarding.tsx @@ -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>((prev, cur) => { + prev[cur.id] = cur; + return prev; + }, {}); + + return topDatasources.map((d) => byId[d]); + }, []); + + const onAddDatasource = useAddDatasource(); + + if (loading) { + return null; + } + + return ( + +
+

{t('datasource-onboarding.welcome', 'Welcome to Grafana dashboards!')}

+
+

+ {t('datasource-onboarding.explanation', "To visualize your data, you'll need to connect it first.")} +

+
+ {contextSrv.hasRole('Admin') ? ( + <> +

+ {t('datasource-onboarding.preferred', 'Connect your preferred data source:')} +

+ {!loadingDatasources && datasources !== undefined && ( + + )} + + ) : ( +

+ {t('datasource-onboarding.contact-admin', 'Please contact your administrator to configure data sources.')} +

+ )} + +
+
+ ); +} + +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, + }), + }; +} diff --git a/public/app/features/dashboard/containers/NewDashboardPage.tsx b/public/app/features/dashboard/containers/NewDashboardPage.tsx new file mode 100644 index 00000000000..2e1575d8e3b --- /dev/null +++ b/public/app/features/dashboard/containers/NewDashboardPage.tsx @@ -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 ? ( + + ) : ( + setCreateDashboard(true)} loading={loading} /> + ); +} diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index 0e5e8a4b2a8..a75042077fe 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -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') ), }, {