Code Diff
diff --git a/public/app/features/connections/Connections.test.tsx b/public/app/features/connections/Connections.test.tsx
index 1387813ab6894..dd53d89f39e37 100644
--- a/public/app/features/connections/Connections.test.tsx
+++ b/public/app/features/connections/Connections.test.tsx
@@ -40,22 +40,28 @@ describe('Connections', () => {
const mockDatasources = getMockDataSources(3);
beforeEach(() => {
+ config.pluginAdminExternalManageEnabled = true;
(api.getDataSources as jest.Mock) = jest.fn().mockResolvedValue(mockDatasources);
(contextSrv.hasPermission as jest.Mock) = jest.fn().mockReturnValue(true);
});
- test('shows the "Connections Homepage" page by default when edition is Cloud', async () => {
+ test('shows cloud subtitle and cards from nav tree when edition is Cloud', async () => {
config.pluginAdminExternalManageEnabled = true;
renderPage();
- // Add new connection card
+ // Cards are derived from the nav tree (navIndex mock)
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
+ // Metadata enrichment: descriptions come from CardMetadata
+ expect(
+ await screen.findByText('Connect data to Grafana through data sources, integrations and apps')
+ ).toBeVisible();
+
+ // Cloud subtitle
expect(await screen.findByText('Welcome to Connections')).toBeVisible();
expect(
await screen.findByText(
@@ -64,21 +70,49 @@ describe('Connections', () => {
).toBeVisible();
});
- test('shows the OSS "Connections Homepage" page by default when edition is OpenSource', async () => {
+ test('shows OSS subtitle and OSS card descriptions when edition is OpenSource', async () => {
config.pluginAdminExternalManageEnabled = false;
renderPage();
- // Add new connection card
- expect(await screen.findByText('Add new connection')).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();
+
+ // OSS-specific card subtitle for "Add new connection"
+ expect(await screen.findByText('Connect to a new data source')).toBeVisible();
+ // OSS-specific title for "Data sources"
+ expect(await screen.findByText('View configured data sources')).toBeVisible();
+ });
+
+ test('only shows cards for nav items present in the connections nav section', async () => {
+ // Store with a minimal connections nav (e.g. OSS - only core items)
+ const minimalStore = configureStore({
+ navIndex: {
+ ...navIndex,
+ connections: {
+ ...navIndex.connections,
+ children: [
+ {
+ id: 'connections-add-new-connection',
+ text: 'Add new connection',
+ url: '/connections/add-new-connection',
+ },
+ { id: 'connections-datasources', text: 'Data sources', url: '/connections/datasources' },
+ ],
+ },
+ },
+ plugins: getPluginsStateMock([]),
+ });
+ renderPage(ROUTES.Base, minimalStore);
+
+ expect(await screen.findByText('Add new connection')).toBeVisible();
+ expect(await screen.findByText('Data sources')).toBeVisible();
+ expect(screen.queryByText('Collector')).not.toBeInTheDocument();
+ expect(screen.queryByText('Integrations')).not.toBeInTheDocument();
+ expect(screen.queryByText('Private data source connect')).not.toBeInTheDocument();
});
test('renders the correct tab even if accessing it with a "sub-url"', async () => {
diff --git a/public/app/features/connections/components/PageCard/CardData.ts b/public/app/features/connections/components/PageCard/CardData.ts
deleted file mode 100644
index 670072a5ee9db..0000000000000
--- a/public/app/features/connections/components/PageCard/CardData.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-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',
- },
- ];
-}
diff --git a/public/app/features/connections/components/PageCard/CardMetadata.ts b/public/app/features/connections/components/PageCard/CardMetadata.ts
new file mode 100644
index 0000000000000..e2934bab03837
--- /dev/null
+++ b/public/app/features/connections/components/PageCard/CardMetadata.ts
@@ -0,0 +1,76 @@
+import type { IconName } from '@grafana/data';
+import { t } from '@grafana/i18n';
+
+type CardMetadata = {
+ icon: IconName;
+ subTitle: string;
+ text?: string;
+};
+
+// Visual metadata for connections nav items keyed by URL. This map intentionally
+// overrides both plugin standalone pages (which carry no icon/subTitle through
+// the nav tree) and some core nav items (Add new connection, Data sources) so the
+// landing-page cards use the correct icon and copy per edition.
+// isOnPrem mirrors !config.pluginAdminExternalManageEnabled.
+export function getConnectionsCardMetadata(isOnPrem: boolean): Record<string, CardMetadata> {
+ return {
+ '/connections/add-new-connection': {
+ icon: 'plus-circle',
+ subTitle: isOnPrem
+ ? t('connections.oss.connections-home-page.add-new-connection.subtitle', 'Connect to a new data source')
+ : t(
+ 'connections.cloud.connections-home-page.add-new-connection.subtitle',
+ 'Connect data to Grafana through data sources, integrations and apps'
+ ),
+ },
+ '/connections/datasources': {
+ icon: 'database',
+ text: isOnPrem
+ ? t('connections.oss.connections-home-page.data-sources.title', 'View configured data sources')
+ : undefined,
+ subTitle: t(
+ 'connections.cloud.connections-home-page.data-sources.subtitle',
+ 'Manage your existing data source connections'
+ ),
+ },
+ '/a/grafana-collector-app': {
+ icon: 'frontend-observability',
+ subTitle: t(
+ 'connections.cloud.connections-home-page.collector.subtitle',
+ 'Manage the configuration of Grafana Alloy, our distribution of the OpenTelemetry Collector'
+ ),
+ },
+ '/a/grafana-collector-app/alloy': {
+ icon: 'frontend-observability',
+ subTitle: t(
+ 'connections.cloud.connections-home-page.collector-setup.subtitle',
+ 'Configure and manage your telemetry collectors'
+ ),
+ },
+ '/a/grafana-collector-app/instrumentation-hub': {
+ icon: 'sitemap',
+ subTitle: t(
+ 'connections.cloud.connections-home-page.instrumentation-hub.subtitle',
+ 'Instrument all your services with a single click using ongoing instrumentation and Kubernetes monitoring.'
+ ),
+ },
+ '/a/grafana-collector-app/fleet-management': {
+ icon: 'file-alt',
+ subTitle: t(
+ 'connections.cloud.connections-home-page.fleet-management.subtitle',
+ 'Manage your collector inventory and remotely configure your fleet'
+ ),
+ },
+ '/connections/infrastructure': {
+ icon: 'apps',
+ subTitle: t('connections.cloud.connections-home-page.integrations.subtitle', 'Manage your active integrations'),
+ },
+ '/connections/private-data-source-connections': {
+ icon: 'lock',
+ subTitle: t(
+ 'connections.cloud.connections-home-page.private-data-source-connections.subtitle',
+ 'Manage your private network connections for data sources'
+ ),
+ },
+ };
+}
diff --git a/public/app/features/connections/mocks/store.navIndex.mock.ts b/public/app/features/connections/mocks/store.navIndex.mock.ts
index f0ea6c8b06c95..cbb89737e8734 100644
--- a/public/app/features/connections/mocks/store.navIndex.mock.ts
+++ b/public/app/features/connections/mocks/store.navIndex.mock.ts
@@ -247,6 +247,12 @@ export const navIndex: NavIndex = {
subTitle: 'Browse and create new connections',
url: '/connections/add-new-connection',
},
+ {
+ id: 'standalone-plugin-page-/a/grafana-collector-app',
+ text: 'Collector',
+ url: '/a/grafana-collector-app',
+ pluginId: 'grafana-collector-app',
+ },
{
id: 'connections-datasources',
text: 'Data sources',
@@ -255,10 +261,16 @@ export const navIndex: NavIndex = {
},
{
id: 'standalone-plugin-page-/connections/infrastructure',
- text: 'Infrastructure',
+ text: 'Integrations',
url: '/connections/infrastructure',
pluginId: 'grafana-easystart-app',
},
+ {
+ id: 'standalone-plugin-page-/connections/private-data-source-connections',
+ text: 'Private data source connect',
+ url: '/connections/private-data-source-connections',
+ pluginId: 'grafana-pdc-app',
+ },
],
parentItem: {
id: 'home',
@@ -276,7 +288,7 @@ export const navIndex: NavIndex = {
},
'standalone-plugin-page-/connections/infrastructure': {
id: 'standalone-plugin-page-/connections/infrastructure',
- text: 'Infrastructure',
+ text: 'Integrations',
url: '/connections/infrastructure',
pluginId: 'grafana-easystart-app',
},
diff --git a/public/app/features/connections/pages/ConnectionsHomePage.tsx b/public/app/features/connections/pages/ConnectionsHomePage.tsx
index 8a630f35aac92..e696fa0c76764 100644
--- a/public/app/features/connections/pages/ConnectionsHomePage.tsx
+++ b/public/app/features/connections/pages/ConnectionsHomePage.tsx
@@ -1,20 +1,44 @@
import { css } from '@emotion/css';
-import { type GrafanaTheme2 } from '@grafana/data';
+import { type GrafanaTheme2, type IconName, isIconName } from '@grafana/data';
import { Trans } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { useStyles2 } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
+import { useSelector } from 'app/types/store';
-import { getCloudCardData, getOssCardData } from '../components/PageCard/CardData';
+import { getConnectionsCardMetadata } from '../components/PageCard/CardMetadata';
import PageCard from '../components/PageCard/PageCard';
+const FALLBACK_ICON: IconName = 'plug';
+
+function resolveIcon(metaIcon: IconName | undefined, navIcon: string | undefined): IconName {
+ if (metaIcon) {
+ return metaIcon;
+ }
+ if (navIcon && isIconName(navIcon)) {
+ return navIcon;
+ }
+ return FALLBACK_ICON;
+}
+
export default function ConnectionsHomePage() {
const styles = useStyles2(getStyles);
const isOnPrem = !config.pluginAdminExternalManageEnabled;
-
- let cardsData = isOnPrem ? getOssCardData() : getCloudCardData();
+ const cardMetadata = getConnectionsCardMetadata(isOnPrem);
+ const navIndex = useSelector((state) => state.navIndex);
+ const cardsData = (navIndex['connections']?.children ?? [])
+ .filter((item) => item.url)
+ .map((item) => {
+ const meta = cardMetadata[item.url!];
+ return {
+ ...item,
+ text: meta?.text ?? item.text,
+ icon: resolveIcon(meta?.icon, item.icon),
+ subTitle: meta?.subTitle ?? item.subTitle ?? '',
+ };
+ });
return (
<Page
@@ -42,15 +66,15 @@ export default function ConnectionsHomePage() {
</Trans>
)}
</p>
- {cardsData && cardsData.length > 0 && (
+ {cardsData.length > 0 && (
<section className={styles.cardsSection}>
- {cardsData?.map((child, index) => (
+ {cardsData.map((child, index) => (
<PageCard
- key={index}
+ key={child.id ?? index}
title={child.text}
description={child.subTitle}
icon={child.icon}
- url={child.url}
+ url={child.url!}
index={index}
/>
))}
diff --git a/pub
... [truncated]