mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: Use Datasource Onboarding page when visiting without any datasource set up (#60399)
* allow DatasourceOnboarding title and cta text customization * Explore: use Datasource Onboarding page when visiting without any datasource set up * move & rename DatasourceOnboarding * Rename component
This commit is contained in:
@@ -3771,9 +3771,6 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Do not use any type assertions.", "7"],
|
[0, 0, 0, "Do not use any type assertions.", "7"],
|
||||||
[0, 0, 0, "Do not use any type assertions.", "8"]
|
[0, 0, 0, "Do not use any type assertions.", "8"]
|
||||||
],
|
],
|
||||||
"public/app/features/explore/Wrapper.test.tsx:5381": [
|
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
|
||||||
],
|
|
||||||
"public/app/features/explore/spec/helper/setup.tsx:5381": [
|
"public/app/features/explore/spec/helper/setup.tsx:5381": [
|
||||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ import React, { useState } from 'react';
|
|||||||
import { useEffectOnce } from 'react-use';
|
import { useEffectOnce } from 'react-use';
|
||||||
|
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
|
import { t } from 'app/core/internationalization';
|
||||||
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
||||||
|
import { EmptyStateNoDatasource } from 'app/features/datasources/components/EmptyStateNoDatasource';
|
||||||
import { loadDataSources } from 'app/features/datasources/state';
|
import { loadDataSources } from 'app/features/datasources/state';
|
||||||
import { useDispatch, useSelector } from 'app/types';
|
import { useDispatch, useSelector } from 'app/types';
|
||||||
|
|
||||||
import DashboardPage from './DashboardPage';
|
import DashboardPage from './DashboardPage';
|
||||||
import { DatasourceOnboarding } from './DatasourceOnboarding';
|
|
||||||
|
|
||||||
export default function NewDashboardPage(props: GrafanaRouteComponentProps) {
|
export default function NewDashboardPage(props: GrafanaRouteComponentProps) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -25,6 +26,13 @@ export default function NewDashboardPage(props: GrafanaRouteComponentProps) {
|
|||||||
return showDashboardPage ? (
|
return showDashboardPage ? (
|
||||||
<DashboardPage {...props} />
|
<DashboardPage {...props} />
|
||||||
) : (
|
) : (
|
||||||
<DatasourceOnboarding onNewDashboard={() => setCreateDashboard(true)} loading={loading} />
|
<EmptyStateNoDatasource
|
||||||
|
onCTAClick={() => setCreateDashboard(true)}
|
||||||
|
loading={loading}
|
||||||
|
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('dashboard', 'New dashboard'), url: '/dashboard/new' }}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React from 'react';
|
import React, { ComponentProps } from 'react';
|
||||||
import { useAsync } from 'react-use';
|
import { useAsync } from 'react-use';
|
||||||
|
|
||||||
import { DataSourcePluginMeta, GrafanaTheme2, PageLayoutType } from '@grafana/data';
|
import { DataSourcePluginMeta, GrafanaTheme2, PageLayoutType } from '@grafana/data';
|
||||||
@@ -21,13 +21,14 @@ const topDatasources = [
|
|||||||
'grafana-azure-monitor-datasource',
|
'grafana-azure-monitor-datasource',
|
||||||
];
|
];
|
||||||
|
|
||||||
export function DatasourceOnboarding({
|
interface Props extends Pick<ComponentProps<typeof Page>, 'navId' | 'pageNav'> {
|
||||||
onNewDashboard,
|
title: string;
|
||||||
loading = false,
|
CTAText: string;
|
||||||
}: {
|
onCTAClick?: () => void;
|
||||||
onNewDashboard?: () => void;
|
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
}) {
|
}
|
||||||
|
|
||||||
|
export function EmptyStateNoDatasource({ onCTAClick, loading = false, title, CTAText, navId, pageNav }: Props) {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const { value: datasources, loading: loadingDatasources } = useAsync(async () => {
|
const { value: datasources, loading: loadingDatasources } = useAsync(async () => {
|
||||||
const datasourceMeta: DataSourcePluginMeta[] = await getBackendSrv().get('/api/plugins', {
|
const datasourceMeta: DataSourcePluginMeta[] = await getBackendSrv().get('/api/plugins', {
|
||||||
@@ -50,13 +51,9 @@ export function DatasourceOnboarding({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page navId={navId} pageNav={pageNav} layout={PageLayoutType.Canvas}>
|
||||||
navId="dashboards/browse"
|
|
||||||
pageNav={{ text: t('dashboard', 'New dashboard'), url: '/dashboard/new' }}
|
|
||||||
layout={PageLayoutType.Canvas}
|
|
||||||
>
|
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<h1 className={styles.title}>{t('datasource-onboarding.welcome', 'Welcome to Grafana dashboards!')}</h1>
|
<h1 className={styles.title}>{title}</h1>
|
||||||
<div className={styles.description}>
|
<div className={styles.description}>
|
||||||
<h4 className={styles.explanation}>
|
<h4 className={styles.explanation}>
|
||||||
{t('datasource-onboarding.explanation', "To visualize your data, you'll need to connect it first.")}
|
{t('datasource-onboarding.explanation', "To visualize your data, you'll need to connect it first.")}
|
||||||
@@ -100,8 +97,8 @@ export function DatasourceOnboarding({
|
|||||||
{t('datasource-onboarding.contact-admin', 'Please contact your administrator to configure data sources.')}
|
{t('datasource-onboarding.contact-admin', 'Please contact your administrator to configure data sources.')}
|
||||||
</h4>
|
</h4>
|
||||||
)}
|
)}
|
||||||
<button onClick={onNewDashboard} className={styles.createNew}>
|
<button onClick={onCTAClick} className={styles.ctaButton}>
|
||||||
<span>{t('datasource-onboarding.sampleData', 'Or set up a new dashboard with sample data')}</span>
|
<span>{CTAText}</span>
|
||||||
<Icon name="arrow-right" size="lg" />
|
<Icon name="arrow-right" size="lg" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -193,7 +190,7 @@ function getStyles(theme: GrafanaTheme2) {
|
|||||||
textDecoration: 'underline',
|
textDecoration: 'underline',
|
||||||
textUnderlinePosition: 'under',
|
textUnderlinePosition: 'under',
|
||||||
}),
|
}),
|
||||||
createNew: css({
|
ctaButton: css({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
39
public/app/features/explore/EmptyStateWrapper.tsx
Normal file
39
public/app/features/explore/EmptyStateWrapper.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useEffectOnce } from 'react-use';
|
||||||
|
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
||||||
|
import { EmptyStateNoDatasource } from 'app/features/datasources/components/EmptyStateNoDatasource';
|
||||||
|
import { ExploreQueryParams, useDispatch, useSelector } from 'app/types';
|
||||||
|
|
||||||
|
import { loadDataSources } from '../datasources/state';
|
||||||
|
|
||||||
|
import { ExplorePage } from './ExplorePage';
|
||||||
|
|
||||||
|
export default function EmptyStateWrapper(props: GrafanaRouteComponentProps<{}, ExploreQueryParams>) {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
useEffectOnce(() => {
|
||||||
|
if (config.featureToggles.datasourceOnboarding) {
|
||||||
|
dispatch(loadDataSources());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { hasDatasource, loading } = useSelector((state) => ({
|
||||||
|
hasDatasource: state.dataSources.dataSourcesCount > 0,
|
||||||
|
loading: !state.dataSources.hasFetched,
|
||||||
|
}));
|
||||||
|
const [showOnboarding, setShowOnboarding] = useState(config.featureToggles.datasourceOnboarding);
|
||||||
|
const showExplorePage = hasDatasource || !showOnboarding;
|
||||||
|
|
||||||
|
return showExplorePage ? (
|
||||||
|
<ExplorePage {...props} />
|
||||||
|
) : (
|
||||||
|
<EmptyStateNoDatasource
|
||||||
|
onCTAClick={() => setShowOnboarding(false)}
|
||||||
|
loading={loading}
|
||||||
|
title="Welcome to Grafana Explore!"
|
||||||
|
CTAText="Or explore sample data"
|
||||||
|
navId="explore"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import React from 'react';
|
import React, { ComponentProps } from 'react';
|
||||||
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
|
|
||||||
import { serializeStateToUrlParam } from '@grafana/data';
|
import { serializeStateToUrlParam } from '@grafana/data';
|
||||||
import { locationService, config } from '@grafana/runtime';
|
import { locationService, config } from '@grafana/runtime';
|
||||||
@@ -27,13 +28,13 @@ jest.mock('app/core/core', () => {
|
|||||||
jest.mock('react-virtualized-auto-sizer', () => {
|
jest.mock('react-virtualized-auto-sizer', () => {
|
||||||
return {
|
return {
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
default(props: any) {
|
default(props: ComponentProps<typeof AutoSizer>) {
|
||||||
return <div>{props.children({ width: 1000 })}</div>;
|
return <div>{props.children({ width: 1000, height: 1000 })}</div>;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Wrapper', () => {
|
describe('ExplorePage', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
tearDown();
|
tearDown();
|
||||||
});
|
});
|
||||||
@@ -31,7 +31,7 @@ const styles = {
|
|||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
function Wrapper(props: GrafanaRouteComponentProps<{}, ExploreQueryParams>) {
|
export function ExplorePage(props: GrafanaRouteComponentProps<{}, ExploreQueryParams>) {
|
||||||
useExplorePageTitle();
|
useExplorePageTitle();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const queryParams = props.queryParams;
|
const queryParams = props.queryParams;
|
||||||
@@ -173,5 +173,3 @@ const useExplorePageTitle = () => {
|
|||||||
|
|
||||||
document.title = `${navModel.main.text} - ${datasources.join(' | ')} - ${Branding.AppTitle}`;
|
document.title = `${navModel.main.text} - ${datasources.join(' | ')} - ${Branding.AppTitle}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Wrapper;
|
|
||||||
@@ -29,7 +29,7 @@ import { LokiDatasource } from '../../../../plugins/datasource/loki/datasource';
|
|||||||
import { LokiQuery } from '../../../../plugins/datasource/loki/types';
|
import { LokiQuery } from '../../../../plugins/datasource/loki/types';
|
||||||
import { ExploreId } from '../../../../types';
|
import { ExploreId } from '../../../../types';
|
||||||
import { initialUserState } from '../../../profile/state/reducers';
|
import { initialUserState } from '../../../profile/state/reducers';
|
||||||
import Wrapper from '../../Wrapper';
|
import { ExplorePage } from '../../ExplorePage';
|
||||||
|
|
||||||
type DatasourceSetup = { settings: DataSourceInstanceSettings; api: DataSourceApi };
|
type DatasourceSetup = { settings: DataSourceInstanceSettings; api: DataSourceApi };
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ export function setupExplore(options?: SetupOptions): {
|
|||||||
locationService.partial(urlParams);
|
locationService.partial(urlParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
const route = { component: Wrapper };
|
const route = { component: ExplorePage };
|
||||||
|
|
||||||
const { unmount, container } = render(
|
const { unmount, container } = render(
|
||||||
<Provider store={storeState}>
|
<Provider store={storeState}>
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ export function getAppRoutes(): RouteDescriptor[] {
|
|||||||
),
|
),
|
||||||
component: SafeDynamicImport(() =>
|
component: SafeDynamicImport(() =>
|
||||||
config.exploreEnabled
|
config.exploreEnabled
|
||||||
? import(/* webpackChunkName: "explore" */ 'app/features/explore/Wrapper')
|
? import(/* webpackChunkName: "explore" */ 'app/features/explore/EmptyStateWrapper')
|
||||||
: import(/* webpackChunkName: "explore-feature-toggle-page" */ 'app/features/explore/FeatureTogglePage')
|
: import(/* webpackChunkName: "explore-feature-toggle-page" */ 'app/features/explore/FeatureTogglePage')
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user