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 () => {
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();
});

View File

@ -33,6 +33,12 @@ const mockCatalogDataSourcePlugin = getCatalogPluginMock({
id: 'sample-data-source',
});
const mockCatalogAppPlugin = getCatalogPluginMock({
type: PluginType.app,
name: 'Sample app',
id: 'sample-app',
});
describe('Badges', () => {
test('shows enterprise and deprecated badges for plugins', async () => {
renderPage([
@ -64,8 +70,8 @@ describe('Add new connection', () => {
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 () => {
renderPage([getCatalogPluginMock()]);
test('renders no results if there are no datasource or app plugins in the list', async () => {
renderPage([getCatalogPluginMock({ type: PluginType.panel })]);
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();
});
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 () => {
const { queryByText } = renderPage(
[

View File

@ -61,7 +61,6 @@ export function AddNewConnection() {
const { error, plugins, isLoading } = useGetAll(
{
keyword: searchTerm,
type: PluginType.datasource,
isInstalled: filterBy === 'installed' ? true : undefined,
hasUpdate: filterBy === 'has-update' ? true : undefined,
},
@ -100,14 +99,34 @@ export function AddNewConnection() {
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,
logo: plugin.info.logos.small,
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>) => {
@ -118,8 +137,10 @@ export function AddNewConnection() {
history.push({ query: { filterBy: value } });
};
const showNoResults = useMemo(() => !isLoading && !error && plugins.length < 1, [isLoading, error, plugins]);
const categoryHeaderLabel = t('connections.connect-data.category-header-label', 'Data sources');
const showNoResults = useMemo(
() => !isLoading && !error && dataSourcesPlugins.length < 1 && appsPlugins.length < 1,
[isLoading, error, dataSourcesPlugins, appsPlugins]
);
return (
<>
@ -179,7 +200,7 @@ export function AddNewConnection() {
</Field>
</HorizontalGroup>
</HorizontalGroup>
<CategoryHeader iconName="database" label={categoryHeaderLabel} />
{isLoading ? (
<LoadingPlaceholder text={t('common.loading', 'Loading...')} />
) : !!error ? (
@ -187,8 +208,29 @@ export function AddNewConnection() {
Error message: "{{ error: error.message }}"
</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 && (
<EmptyState
variant="not-found"

View File

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