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:
parent
15d32546ea
commit
9ff3bf4849
@ -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.", "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": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[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 { 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 { 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();
|
||||
@ -25,6 +26,13 @@ export default function NewDashboardPage(props: GrafanaRouteComponentProps) {
|
||||
return showDashboardPage ? (
|
||||
<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 React from 'react';
|
||||
import React, { ComponentProps } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { DataSourcePluginMeta, GrafanaTheme2, PageLayoutType } from '@grafana/data';
|
||||
@ -21,13 +21,14 @@ const topDatasources = [
|
||||
'grafana-azure-monitor-datasource',
|
||||
];
|
||||
|
||||
export function DatasourceOnboarding({
|
||||
onNewDashboard,
|
||||
loading = false,
|
||||
}: {
|
||||
onNewDashboard?: () => void;
|
||||
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 { value: datasources, loading: loadingDatasources } = useAsync(async () => {
|
||||
const datasourceMeta: DataSourcePluginMeta[] = await getBackendSrv().get('/api/plugins', {
|
||||
@ -50,13 +51,9 @@ export function DatasourceOnboarding({
|
||||
}
|
||||
|
||||
return (
|
||||
<Page
|
||||
navId="dashboards/browse"
|
||||
pageNav={{ text: t('dashboard', 'New dashboard'), url: '/dashboard/new' }}
|
||||
layout={PageLayoutType.Canvas}
|
||||
>
|
||||
<Page navId={navId} pageNav={pageNav} layout={PageLayoutType.Canvas}>
|
||||
<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}>
|
||||
<h4 className={styles.explanation}>
|
||||
{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.')}
|
||||
</h4>
|
||||
)}
|
||||
<button onClick={onNewDashboard} className={styles.createNew}>
|
||||
<span>{t('datasource-onboarding.sampleData', 'Or set up a new dashboard with sample data')}</span>
|
||||
<button onClick={onCTAClick} className={styles.ctaButton}>
|
||||
<span>{CTAText}</span>
|
||||
<Icon name="arrow-right" size="lg" />
|
||||
</button>
|
||||
</div>
|
||||
@ -193,7 +190,7 @@ function getStyles(theme: GrafanaTheme2) {
|
||||
textDecoration: 'underline',
|
||||
textUnderlinePosition: 'under',
|
||||
}),
|
||||
createNew: css({
|
||||
ctaButton: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
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 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 { locationService, config } from '@grafana/runtime';
|
||||
@ -27,13 +28,13 @@ jest.mock('app/core/core', () => {
|
||||
jest.mock('react-virtualized-auto-sizer', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
default(props: any) {
|
||||
return <div>{props.children({ width: 1000 })}</div>;
|
||||
default(props: ComponentProps<typeof AutoSizer>) {
|
||||
return <div>{props.children({ width: 1000, height: 1000 })}</div>;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('Wrapper', () => {
|
||||
describe('ExplorePage', () => {
|
||||
afterEach(() => {
|
||||
tearDown();
|
||||
});
|
@ -31,7 +31,7 @@ const styles = {
|
||||
`,
|
||||
};
|
||||
|
||||
function Wrapper(props: GrafanaRouteComponentProps<{}, ExploreQueryParams>) {
|
||||
export function ExplorePage(props: GrafanaRouteComponentProps<{}, ExploreQueryParams>) {
|
||||
useExplorePageTitle();
|
||||
const dispatch = useDispatch();
|
||||
const queryParams = props.queryParams;
|
||||
@ -173,5 +173,3 @@ const useExplorePageTitle = () => {
|
||||
|
||||
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 { ExploreId } from '../../../../types';
|
||||
import { initialUserState } from '../../../profile/state/reducers';
|
||||
import Wrapper from '../../Wrapper';
|
||||
import { ExplorePage } from '../../ExplorePage';
|
||||
|
||||
type DatasourceSetup = { settings: DataSourceInstanceSettings; api: DataSourceApi };
|
||||
|
||||
@ -120,7 +120,7 @@ export function setupExplore(options?: SetupOptions): {
|
||||
locationService.partial(urlParams);
|
||||
}
|
||||
|
||||
const route = { component: Wrapper };
|
||||
const route = { component: ExplorePage };
|
||||
|
||||
const { unmount, container } = render(
|
||||
<Provider store={storeState}>
|
||||
|
@ -216,7 +216,7 @@ export function getAppRoutes(): RouteDescriptor[] {
|
||||
),
|
||||
component: SafeDynamicImport(() =>
|
||||
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')
|
||||
),
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user