PluginsCatalog: post installation, adding an "add datasource"-button. (#40155)

* added post installation steps.

* changes according to feedback.

* removing return union null type.

* added tests.

* changing the wording of the button to 'create a..'

* updated tests to check for the updated copy.

* changing the back to be a regular back button.

* updated snapshot
This commit is contained in:
Marcus Andersson
2021-10-15 13:18:39 +02:00
committed by GitHub
parent 8e070d6858
commit 79ce09ddee
8 changed files with 119 additions and 16 deletions

View File

@@ -1,7 +1,6 @@
import React, { FC } from 'react';
import { selectors } from '@grafana/e2e-selectors';
import config from 'app/core/config';
import { Button, LinkButton } from '@grafana/ui';
import { AccessControlAction } from 'app/types/';
@@ -22,9 +21,9 @@ const ButtonRow: FC<Props> = ({ isReadOnly, onDelete, onSubmit, onTest, exploreU
return (
<div className="gf-form-button-row">
<LinkButton variant="secondary" fill="solid" href={`${config.appSubUrl}/datasources`}>
<Button variant="secondary" fill="solid" type="button" onClick={() => history.back()}>
Back
</LinkButton>
</Button>
<LinkButton variant="secondary" fill="solid" href={exploreUrl} disabled={!canExploreDataSources}>
Explore
</LinkButton>

View File

@@ -22,7 +22,7 @@ import { getNavModel } from 'app/core/selectors/navModel';
// Types
import { StoreState, AccessControlAction } from 'app/types/';
import { DataSourceSettings, urlUtil } from '@grafana/data';
import { Alert, Button, LinkButton } from '@grafana/ui';
import { Alert, Button } from '@grafana/ui';
import { getDataSourceLoadingNav, buildNavModel, getDataSourceNav } from '../state/navModel';
import { PluginStateInfo } from 'app/features/plugins/PluginStateInfo';
import { dataSourceLoaded, setDataSourceName, setIsDefault } from '../state/reducers';
@@ -190,9 +190,9 @@ export class DataSourceSettingsPage extends PureComponent<Props> {
Delete
</Button>
)}
<LinkButton variant="secondary" href="datasources" fill="outline">
<Button variant="secondary" fill="outline" type="button" onClick={() => history.back()}>
Back
</LinkButton>
</Button>
</div>
</Page.Contents>
</Page>

View File

@@ -4,13 +4,14 @@ exports[`Render should render component 1`] = `
<div
className="gf-form-button-row"
>
<LinkButton
<Button
fill="solid"
href="/datasources"
onClick={[Function]}
type="button"
variant="secondary"
>
Back
</LinkButton>
</Button>
<LinkButton
disabled={false}
fill="solid"
@@ -42,13 +43,14 @@ exports[`Render should render with buttons enabled 1`] = `
<div
className="gf-form-button-row"
>
<LinkButton
<Button
fill="solid"
href="/datasources"
onClick={[Function]}
type="button"
variant="secondary"
>
Back
</LinkButton>
</Button>
<LinkButton
disabled={false}
fill="solid"

View File

@@ -0,0 +1,28 @@
import { DataSourcePluginMeta } from '@grafana/data';
import { Button } from '@grafana/ui';
import { addDataSource } from 'app/features/datasources/state/actions';
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { CatalogPlugin } from '../../types';
type Props = {
plugin: CatalogPlugin;
};
export function GetStartedWithDataSource({ plugin }: Props): React.ReactElement {
const dispatch = useDispatch();
const onAddDataSource = useCallback(() => {
const meta = {
name: plugin.name,
id: plugin.id,
} as DataSourcePluginMeta;
dispatch(addDataSource(meta));
}, [dispatch, plugin]);
return (
<Button variant="primary" onClick={onAddDataSource}>
Create a {plugin.name} data source
</Button>
);
}

View File

@@ -0,0 +1,21 @@
import React, { ReactElement } from 'react';
import { PluginType } from '@grafana/data';
import { CatalogPlugin } from '../../types';
import { GetStartedWithDataSource } from './GetStartedWithDataSource';
type Props = {
plugin: CatalogPlugin;
};
export function GetStartedWithPlugin({ plugin }: Props): ReactElement | null {
if (!plugin.isInstalled || plugin.isDisabled) {
return null;
}
switch (plugin.type) {
case PluginType.datasource:
return <GetStartedWithDataSource plugin={plugin} />;
default:
return null;
}
}

View File

@@ -0,0 +1 @@
export { GetStartedWithPlugin } from './GetStartedWithPlugin';

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { css, cx } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2, Icon } from '@grafana/ui';
import { useStyles2, Icon, HorizontalGroup } from '@grafana/ui';
import { InstallControls } from './InstallControls';
import { PluginDetailsHeaderSignature } from './PluginDetailsHeaderSignature';
@@ -9,6 +9,7 @@ import { PluginDetailsHeaderDependencies } from './PluginDetailsHeaderDependenci
import { PluginLogo } from './PluginLogo';
import { CatalogPlugin } from '../types';
import { PluginDisabledBadge } from './Badges';
import { GetStartedWithPlugin } from './GetStartedWithPlugin';
type Props = {
currentUrl: string;
@@ -84,7 +85,10 @@ export function PluginDetailsHeader({ plugin, currentUrl, parentUrl }: Props): R
<p>{plugin.description}</p>
<InstallControls plugin={plugin} />
<HorizontalGroup height="auto">
<InstallControls plugin={plugin} />
<GetStartedWithPlugin plugin={plugin} />
</HorizontalGroup>
</div>
</div>
);

View File

@@ -11,7 +11,7 @@ import { CatalogPlugin, PluginTabIds, RequestStatus, ReducerState } from '../typ
import * as api from '../api';
import { fetchRemotePlugins } from '../state/actions';
import { mockPluginApis, getCatalogPluginMock, getPluginsStateMock } from '../__mocks__';
import { PluginErrorCode, PluginSignatureStatus } from '@grafana/data';
import { PluginErrorCode, PluginSignatureStatus, PluginType } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
jest.mock('@grafana/runtime', () => {
@@ -119,7 +119,6 @@ describe('Plugin details page', () => {
it('should display the number of downloads in the header', async () => {
const downloads = 24324;
const { queryByText } = renderPluginDetails({ id, downloads });
await waitFor(() => expect(queryByText(new Intl.NumberFormat().format(downloads))).toBeInTheDocument());
});
@@ -380,4 +379,53 @@ describe('Plugin details page', () => {
const message = 'The install controls have been disabled because the Grafana server cannot access grafana.com.';
expect(rendered.getByText(message)).toBeInTheDocument();
});
it('should display post installation step for installed data source plugins', async () => {
const name = 'Akumuli';
const { queryByText } = renderPluginDetails({
name,
isInstalled: true,
type: PluginType.datasource,
});
await waitFor(() => queryByText('Uninstall'));
expect(queryByText(`Create a ${name} data source`)).toBeInTheDocument();
});
it('should not display post installation step for disabled data source plugins', async () => {
const name = 'Akumuli';
const { queryByText } = renderPluginDetails({
name,
isInstalled: true,
isDisabled: true,
type: PluginType.datasource,
});
await waitFor(() => queryByText('Uninstall'));
expect(queryByText(`Create a ${name} data source`)).toBeNull();
});
it('should not display post installation step for panel plugins', async () => {
const name = 'Akumuli';
const { queryByText } = renderPluginDetails({
name,
isInstalled: true,
type: PluginType.panel,
});
await waitFor(() => queryByText('Uninstall'));
expect(queryByText(`Create a ${name} data source`)).toBeNull();
});
it('should not display post installation step for app plugins', async () => {
const name = 'Akumuli';
const { queryByText } = renderPluginDetails({
name,
isInstalled: true,
type: PluginType.app,
});
await waitFor(() => queryByText('Uninstall'));
expect(queryByText(`Create a ${name} data source`)).toBeNull();
});
});