Plugins: Add Connections homepage (#108316)
This commit is contained in:
@@ -2,6 +2,8 @@ import { RenderResult, screen } from '@testing-library/react';
|
||||
import { Route, Routes } from 'react-router-dom-v5-compat';
|
||||
import { render } from 'test/test-utils';
|
||||
|
||||
import { GrafanaEdition } from '@grafana/data/internal';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import * as api from 'app/features/datasources/api';
|
||||
import { getMockDataSources } from 'app/features/datasources/mocks/dataSourcesMocks';
|
||||
@@ -43,15 +45,41 @@ describe('Connections', () => {
|
||||
(contextSrv.hasPermission as jest.Mock) = jest.fn().mockReturnValue(true);
|
||||
});
|
||||
|
||||
test('shows the "Add new connection" page by default', async () => {
|
||||
test('shows the "Connections Homepage" page by default when edition is Cloud', async () => {
|
||||
config.buildInfo.edition = GrafanaEdition.Enterprise;
|
||||
renderPage();
|
||||
|
||||
// Data sources group
|
||||
// Add new connection card
|
||||
expect(await screen.findByText('Add new connection')).toBeVisible();
|
||||
expect(await screen.findByText('Collector:')).toBeVisible();
|
||||
expect(await screen.findByText('Data sources')).toBeVisible();
|
||||
expect(await screen.findByText('Integrations')).toBeVisible();
|
||||
expect(await screen.findByText('Private data source connect')).toBeVisible();
|
||||
|
||||
// Heading
|
||||
expect(await screen.findByText('Welcome to Connections')).toBeVisible();
|
||||
expect(
|
||||
await screen.findByText(
|
||||
'Connect your infrastructure to Grafana Cloud using data sources, integrations and apps. Use this page to add to manage everything from data ingestion to private connections and telemetry pipelines.'
|
||||
)
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('shows the OSS "Connections Homepage" page by default when edition is OpenSource', async () => {
|
||||
config.buildInfo.edition = GrafanaEdition.OpenSource;
|
||||
renderPage();
|
||||
|
||||
// Add new connection card
|
||||
expect(await screen.findByText('Add new connection')).toBeVisible();
|
||||
expect(await screen.findByText('Browse and create new connections')).toBeVisible();
|
||||
expect(await screen.findByText('View configured data sources')).toBeVisible();
|
||||
|
||||
// Heading
|
||||
expect(await screen.findByText('Welcome to Connections')).toBeVisible();
|
||||
expect(
|
||||
await screen.findByText(
|
||||
'Manage your data source connections in one place. Use this page to add a new data source or manage your existing connections.'
|
||||
)
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('renders the correct tab even if accessing it with a "sub-url"', async () => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { StoreState, useSelector } from 'app/types/store';
|
||||
|
||||
import { ROUTES } from './constants';
|
||||
import { AddNewConnectionPage } from './pages/AddNewConnectionPage';
|
||||
import ConnectionsHomePage from './pages/ConnectionsHomePage';
|
||||
import { DataSourceDashboardsPage } from './pages/DataSourceDashboardsPage';
|
||||
import { DataSourceDetailsPage } from './pages/DataSourceDetailsPage';
|
||||
import { DataSourcesListPage } from './pages/DataSourcesListPage';
|
||||
@@ -30,7 +31,7 @@ export default function Connections() {
|
||||
return (
|
||||
<Routes>
|
||||
{/* Redirect to "Add new connection" by default */}
|
||||
<Route caseSensitive path={'/'} element={<Navigate replace to={ROUTES.AddNewConnection} />} />
|
||||
<Route caseSensitive path={'/'} element={<ConnectionsHomePage />} />
|
||||
{/* The route paths need to be relative to the parent path (ROUTES.Base), so we need to remove that part */}
|
||||
<Route caseSensitive path={ROUTES.DataSources.replace(ROUTES.Base, '')} element={<DataSourcesListPage />} />
|
||||
<Route caseSensitive path={ROUTES.DataSourcesNew.replace(ROUTES.Base, '')} element={<NewDataSourcePage />} />
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import type { IconName } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
|
||||
type CardData = {
|
||||
text: string;
|
||||
subTitle: string;
|
||||
url: string;
|
||||
icon: IconName;
|
||||
};
|
||||
|
||||
export function getCloudCardData(): CardData[] {
|
||||
return [
|
||||
{
|
||||
text: t('connections.cloud.connections-home-page.add-new-connection.title', 'Add new connection'),
|
||||
subTitle: t(
|
||||
'connections.cloud.connections-home-page.add-new-connection.subtitle',
|
||||
'Connect data to Grafana through data sources, integrations and apps'
|
||||
),
|
||||
url: '/connections/add-new-connection',
|
||||
icon: 'plus-circle',
|
||||
},
|
||||
{
|
||||
text: t('connections.cloud.connections-home-page.collector.title', 'Collector:'),
|
||||
subTitle: t(
|
||||
'connections.cloud.connections-home-page.collector.subtitle',
|
||||
'Manage the configuration of Grafana Alloy, our distribution of the OpenTelemetry Collector'
|
||||
),
|
||||
url: '/a/grafana-collector-app',
|
||||
icon: 'frontend-observability',
|
||||
},
|
||||
{
|
||||
text: t('connections.cloud.connections-home-page.data-sources.title', 'Data sources'),
|
||||
subTitle: t(
|
||||
'connections.cloud.connections-home-page.data-sources.subtitle',
|
||||
'Manage your existing data source connections'
|
||||
),
|
||||
url: '/connections/datasources',
|
||||
icon: 'database',
|
||||
},
|
||||
{
|
||||
text: t('connections.cloud.connections-home-page.integrations.title', 'Integrations'),
|
||||
subTitle: t('connections.cloud.connections-home-page.integrations.subtitle', 'Manage your active integrations'),
|
||||
url: '/connections/infrastructure',
|
||||
icon: 'apps',
|
||||
},
|
||||
{
|
||||
text: t(
|
||||
'connections.cloud.connections-home-page.private-data-source-connections.title',
|
||||
'Private data source connect'
|
||||
),
|
||||
subTitle: t(
|
||||
'connections.cloud.connections-home-page.private-data-source-connections.subtitle',
|
||||
'Manage your private network connections for data sources'
|
||||
),
|
||||
url: '/connections/private-data-source-connections',
|
||||
icon: 'sitemap',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function getOssCardData(): CardData[] {
|
||||
return [
|
||||
{
|
||||
text: t('connections.oss.connections-home-page.add-new-connection.title', 'Add new connection'),
|
||||
subTitle: t('connections.oss.connections-home-page.add-new-connection.subtitle', 'Connect to a new data source'),
|
||||
url: '/connections/add-new-connection',
|
||||
icon: 'plus-circle',
|
||||
},
|
||||
{
|
||||
text: t('connections.oss.connections-home-page.data-sources.title', 'View configured data sources'),
|
||||
subTitle: t(
|
||||
'connections.oss.connections-home-page.data-sources.subtitle',
|
||||
'Manage your existing data source connections'
|
||||
),
|
||||
url: '/connections/datasources',
|
||||
icon: 'database',
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { GrafanaTheme2, IconName } from '@grafana/data';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { Icon, useStyles2 } from '@grafana/ui';
|
||||
|
||||
type PageCardProps = {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: IconName;
|
||||
url: string;
|
||||
index: number;
|
||||
};
|
||||
|
||||
export default function PageCard({ title, description, icon, url, index }: PageCardProps) {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
locationService.push(url);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.card}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => locationService.push(url)}
|
||||
onKeyDown={onKeyDown}
|
||||
>
|
||||
<Icon name={icon} className={`${styles.logo} ${index % 2 === 0 ? styles.evenLogo : styles.oddLogo}`} />
|
||||
<div className={styles.contentColumn}>
|
||||
<h3 className={styles.title}>{title}</h3>
|
||||
<p className={styles.description}>{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
card: css({
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
gap: theme.spacing(2),
|
||||
paddingTop: theme.spacing(2),
|
||||
backgroundColor: theme.colors.background.secondary,
|
||||
borderRadius: theme.shape.radius.default,
|
||||
padding: `${theme.spacing(3)} ${theme.spacing(4)} ${theme.spacing(2.25)} ${theme.spacing(4)}`,
|
||||
minHeight: theme.spacing(19.4),
|
||||
'&:hover': {
|
||||
cursor: 'pointer',
|
||||
backgroundColor: theme.colors.emphasize(theme.colors.background.secondary, 0.03),
|
||||
},
|
||||
width: theme.spacing(48),
|
||||
}),
|
||||
contentColumn: css({
|
||||
flex: 1,
|
||||
}),
|
||||
title: css({
|
||||
marginBottom: theme.spacing(1),
|
||||
fontSize: theme.typography.h4.fontSize,
|
||||
fontWeight: theme.typography.h4.fontWeight,
|
||||
color: theme.colors.text.primary,
|
||||
}),
|
||||
description: css({
|
||||
WebkitLineClamp: 3,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
display: '-webkit-box',
|
||||
overflow: 'hidden',
|
||||
margin: '0',
|
||||
color: theme.colors.text.secondary,
|
||||
}),
|
||||
logo: css({
|
||||
objectFit: 'contain',
|
||||
width: '47px',
|
||||
height: '47px',
|
||||
padding: theme.spacing(1.2),
|
||||
borderRadius: theme.spacing(1),
|
||||
}),
|
||||
evenLogo: css({
|
||||
color: '#4ADE80',
|
||||
backgroundColor: 'rgba(34, 197, 94, 0.10)',
|
||||
}),
|
||||
oddLogo: css({
|
||||
color: '#FB923C',
|
||||
backgroundColor: 'rgba(249, 115, 22, 0.10)',
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,103 @@
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { GrafanaEdition } from '@grafana/data/internal';
|
||||
import { Trans } from '@grafana/i18n';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
|
||||
import { getCloudCardData, getOssCardData } from '../components/PageCard/CardData';
|
||||
import PageCard from '../components/PageCard/PageCard';
|
||||
|
||||
export default function ConnectionsHomePage() {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const isOSS = config.buildInfo.edition === GrafanaEdition.OpenSource;
|
||||
|
||||
let cardsData = isOSS ? getOssCardData() : getCloudCardData();
|
||||
|
||||
return (
|
||||
<Page
|
||||
navId="connections"
|
||||
pageNav={{
|
||||
text: '',
|
||||
active: true,
|
||||
}}
|
||||
>
|
||||
<Page.Contents>
|
||||
<div className={styles.centeredContainer}>
|
||||
<h1 className={styles.mainTitle}>
|
||||
<Trans i18nKey="connections.connections-home-page.welcome-to-connections">Welcome to Connections</Trans>
|
||||
</h1>
|
||||
<p className={styles.subTitle}>
|
||||
{isOSS ? (
|
||||
<Trans i18nKey="connections.oss.connections-home-page.subtitle">
|
||||
Manage your data source connections in one place. Use this page to add a new data source or manage your
|
||||
existing connections.
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans i18nKey="connections.cloud.connections-home-page.subtitle">
|
||||
Connect your infrastructure to Grafana Cloud using data sources, integrations and apps. Use this page to
|
||||
add to manage everything from data ingestion to private connections and telemetry pipelines.
|
||||
</Trans>
|
||||
)}
|
||||
</p>
|
||||
{cardsData && cardsData.length > 0 && (
|
||||
<section className={styles.cardsSection}>
|
||||
{cardsData?.map((child, index) => (
|
||||
<PageCard
|
||||
key={index}
|
||||
title={child.text}
|
||||
description={child.subTitle}
|
||||
icon={child.icon}
|
||||
url={child.url}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
mainTitle: css({
|
||||
textAlign: 'left',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
}),
|
||||
subTitle: css({
|
||||
color: theme.colors.text.secondary,
|
||||
textAlign: 'left',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
textAlign: 'center',
|
||||
maxWidth: theme.spacing(119),
|
||||
},
|
||||
}),
|
||||
cardsSection: css({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'flex-start',
|
||||
gap: theme.spacing(4),
|
||||
paddingTop: theme.spacing(2),
|
||||
paddingBottom: theme.spacing(2),
|
||||
width: '100%',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
justifyContent: 'center',
|
||||
paddingTop: theme.spacing(6),
|
||||
paddingBottom: theme.spacing(6),
|
||||
},
|
||||
}),
|
||||
centeredContainer: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -4134,12 +4134,40 @@
|
||||
"body": "Try the new Advisor to uncover potential issues with your data sources and plugins.",
|
||||
"go-to-advisor": "Go to Advisor"
|
||||
},
|
||||
"cloud": {
|
||||
"connections-home-page": {
|
||||
"add-new-connection": {
|
||||
"subtitle": "Connect data to Grafana through data sources, integrations and apps",
|
||||
"title": "Add new connection"
|
||||
},
|
||||
"collector": {
|
||||
"subtitle": "Manage the configuration of Grafana Alloy, our distribution of the OpenTelemetry Collector",
|
||||
"title": "Collector:"
|
||||
},
|
||||
"data-sources": {
|
||||
"subtitle": "Manage your existing data source connections",
|
||||
"title": "Data sources"
|
||||
},
|
||||
"integrations": {
|
||||
"subtitle": "Manage your active integrations",
|
||||
"title": "Integrations"
|
||||
},
|
||||
"private-data-source-connections": {
|
||||
"subtitle": "Manage your private network connections for data sources",
|
||||
"title": "Private data source connect"
|
||||
},
|
||||
"subtitle": "Connect your infrastructure to Grafana Cloud using data sources, integrations and apps. Use this page to add to manage everything from data ingestion to private connections and telemetry pipelines."
|
||||
}
|
||||
},
|
||||
"connect-data": {
|
||||
"category-header-label": "Data sources",
|
||||
"empty-message": "No results matching your query were found",
|
||||
"request-data-source": "Request a new data source",
|
||||
"roadmap": "View roadmap"
|
||||
},
|
||||
"connections-home-page": {
|
||||
"welcome-to-connections": "Welcome to Connections"
|
||||
},
|
||||
"connections-redirect-notice": {
|
||||
"aria-label-link-to-connections": "Link to Connections",
|
||||
"body": "Data sources have a new home! You can discover new data sources or manage existing ones in the Connections page, accessible from the main menu.",
|
||||
@@ -4170,6 +4198,19 @@
|
||||
"not-found-datasource": {
|
||||
"body": "Maybe you mistyped the URL or the plugin with the id <1></1> is unavailable.<3></3>To see a list of available datasources please <5>click here</5>."
|
||||
},
|
||||
"oss": {
|
||||
"connections-home-page": {
|
||||
"add-new-connection": {
|
||||
"subtitle": "Connect to a new data source",
|
||||
"title": "Add new connection"
|
||||
},
|
||||
"data-sources": {
|
||||
"subtitle": "Manage your existing data source connections",
|
||||
"title": "View configured data sources"
|
||||
},
|
||||
"subtitle": "Manage your data source connections in one place. Use this page to add a new data source or manage your existing connections."
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"aria-label-search-all": "Search all",
|
||||
"placeholder": "Search all"
|
||||
|
||||
Reference in New Issue
Block a user