Plugins: Add apps to connections page to be consistent with cloud (#109600)

* Add apps to connections page

* fix the tests

* fix connections tests
This commit is contained in:
Yulia Shanyrova 2025-08-19 11:13:01 +02:00 committed by GitHub
parent 0687017595
commit 1a87679dc7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 74 additions and 13 deletions

View File

@ -95,8 +95,6 @@ describe('Connections', () => {
test('renders the core "Add new connection" page in case there is no standalone plugin page override for it', async () => { test('renders the core "Add new connection" page in case there is no standalone plugin page override for it', async () => {
renderPage(ROUTES.AddNewConnection); renderPage(ROUTES.AddNewConnection);
// We expect to see no results and "Data sources" as a header (we only have data sources in OSS Grafana at this point)
expect(await screen.findByText('Data sources')).toBeVisible();
expect(await screen.findByText('No results matching your query were found')).toBeVisible(); expect(await screen.findByText('No results matching your query were found')).toBeVisible();
}); });

View File

@ -33,6 +33,12 @@ const mockCatalogDataSourcePlugin = getCatalogPluginMock({
id: 'sample-data-source', id: 'sample-data-source',
}); });
const mockCatalogAppPlugin = getCatalogPluginMock({
type: PluginType.app,
name: 'Sample app',
id: 'sample-app',
});
describe('Badges', () => { describe('Badges', () => {
test('shows enterprise and deprecated badges for plugins', async () => { test('shows enterprise and deprecated badges for plugins', async () => {
renderPage([ renderPage([
@ -64,8 +70,8 @@ describe('Add new connection', () => {
expect(screen.queryByText('No results matching your query were found')).toBeInTheDocument(); expect(screen.queryByText('No results matching your query were found')).toBeInTheDocument();
}); });
test('renders no results if there is no data source plugin in the list', async () => { test('renders no results if there are no datasource or app plugins in the list', async () => {
renderPage([getCatalogPluginMock()]); renderPage([getCatalogPluginMock({ type: PluginType.panel })]);
expect(screen.queryByText('No results matching your query were found')).toBeInTheDocument(); expect(screen.queryByText('No results matching your query were found')).toBeInTheDocument();
}); });
@ -75,6 +81,20 @@ describe('Add new connection', () => {
expect(await screen.findByText('Sample data source')).toBeVisible(); expect(await screen.findByText('Sample data source')).toBeVisible();
}); });
test('renders app plugins when list is populated', async () => {
renderPage([getCatalogPluginMock(), mockCatalogAppPlugin]);
expect(await screen.findByText('Sample app')).toBeVisible();
});
test('renders app plugin and datasource plugin when list is populated', async () => {
renderPage([getCatalogPluginMock(), mockCatalogAppPlugin, mockCatalogDataSourcePlugin]);
expect(await screen.findByText('Sample app')).toBeVisible();
expect(await screen.findByText('Sample data source')).toBeVisible();
});
test('should list plugins with update when filtering by update', async () => { test('should list plugins with update when filtering by update', async () => {
const { queryByText } = renderPage( const { queryByText } = renderPage(
[ [

View File

@ -61,7 +61,6 @@ export function AddNewConnection() {
const { error, plugins, isLoading } = useGetAll( const { error, plugins, isLoading } = useGetAll(
{ {
keyword: searchTerm, keyword: searchTerm,
type: PluginType.datasource,
isInstalled: filterBy === 'installed' ? true : undefined, isInstalled: filterBy === 'installed' ? true : undefined,
hasUpdate: filterBy === 'has-update' ? true : undefined, hasUpdate: filterBy === 'has-update' ? true : undefined,
}, },
@ -100,14 +99,34 @@ export function AddNewConnection() {
setFocusedItem(null); setFocusedItem(null);
}; };
const cardGridItems = useMemo( const getPluginsByType = useMemo(() => {
return {
[PluginType.datasource]: plugins.filter((plugin) => plugin.type === PluginType.datasource),
[PluginType.app]: plugins.filter((plugin) => plugin.type === PluginType.app),
};
}, [plugins]);
const dataSourcesPlugins = getPluginsByType[PluginType.datasource];
const appsPlugins = getPluginsByType[PluginType.app];
const datasourceCardGridItems = useMemo(
() => () =>
plugins.map((plugin) => ({ dataSourcesPlugins.map((plugin) => ({
...plugin, ...plugin,
logo: plugin.info.logos.small, logo: plugin.info.logos.small,
url: ROUTES.DataSourcesDetails.replace(':id', plugin.id), url: ROUTES.DataSourcesDetails.replace(':id', plugin.id),
})), })),
[plugins] [dataSourcesPlugins]
);
const appsCardGridItems = useMemo(
() =>
appsPlugins.map((plugin) => ({
...plugin,
logo: plugin.info.logos.small,
url: `/plugins/${plugin.id}`,
})),
[appsPlugins]
); );
const onSortByChange = (value: SelectableValue<string>) => { const onSortByChange = (value: SelectableValue<string>) => {
@ -118,8 +137,10 @@ export function AddNewConnection() {
history.push({ query: { filterBy: value } }); history.push({ query: { filterBy: value } });
}; };
const showNoResults = useMemo(() => !isLoading && !error && plugins.length < 1, [isLoading, error, plugins]); const showNoResults = useMemo(
const categoryHeaderLabel = t('connections.connect-data.category-header-label', 'Data sources'); () => !isLoading && !error && dataSourcesPlugins.length < 1 && appsPlugins.length < 1,
[isLoading, error, dataSourcesPlugins, appsPlugins]
);
return ( return (
<> <>
@ -179,7 +200,7 @@ export function AddNewConnection() {
</Field> </Field>
</HorizontalGroup> </HorizontalGroup>
</HorizontalGroup> </HorizontalGroup>
<CategoryHeader iconName="database" label={categoryHeaderLabel} />
{isLoading ? ( {isLoading ? (
<LoadingPlaceholder text={t('common.loading', 'Loading...')} /> <LoadingPlaceholder text={t('common.loading', 'Loading...')} />
) : !!error ? ( ) : !!error ? (
@ -187,8 +208,29 @@ export function AddNewConnection() {
Error message: "{{ error: error.message }}" Error message: "{{ error: error.message }}"
</Trans> </Trans>
) : ( ) : (
<CardGrid items={cardGridItems} onClickItem={onClickCardGridItem} /> <>
{/* Data Sources Section */}
{dataSourcesPlugins.length > 0 && (
<>
<CategoryHeader
iconName="database"
label={t('connections.connect-data.datasources-header', 'Data Sources')}
/>
<CardGrid items={datasourceCardGridItems} onClickItem={onClickCardGridItem} />
</>
)}
{/* Apps Section */}
{appsPlugins.length > 0 && (
<>
<div className={styles.spacer} />
<CategoryHeader iconName="apps" label={t('connections.connect-data.apps-header', 'Apps')} />
<CardGrid items={appsCardGridItems} onClickItem={onClickCardGridItem} />
</>
)}
</>
)} )}
{showNoResults && ( {showNoResults && (
<EmptyState <EmptyState
variant="not-found" variant="not-found"

View File

@ -4177,7 +4177,8 @@
} }
}, },
"connect-data": { "connect-data": {
"category-header-label": "Data sources", "apps-header": "Apps",
"datasources-header": "Data Sources",
"empty-message": "No results matching your query were found", "empty-message": "No results matching your query were found",
"request-data-source": "Request a new data source", "request-data-source": "Request a new data source",
"roadmap": "View roadmap" "roadmap": "View roadmap"