Plugin Details Page: Fix tabs not loading on hard refresh (#112915)

* don't show nav children while plugin config is loading

* add tests
This commit is contained in:
Erik Sundell
2025-10-28 07:32:35 +01:00
committed by GitHub
parent ce246936c4
commit 3dd4493d50
2 changed files with 127 additions and 2 deletions
@@ -0,0 +1,124 @@
import { renderHook } from '@testing-library/react';
import { PluginSignatureStatus, PluginSignatureType, PluginType } from '@grafana/data';
import { config } from '@grafana/runtime';
import { CatalogPlugin } from '../types';
import { usePluginDetailsTabs } from './usePluginDetailsTabs';
jest.mock('react-router-dom-v5-compat', () => ({
useLocation: () => ({ pathname: '/plugins/test-plugin' }),
}));
jest.mock('./usePluginConfig');
jest.mock('app/core/core', () => ({
contextSrv: {
hasPermissionInMetadata: jest.fn(() => true),
},
}));
const mockUsePluginConfig = jest.requireMock('./usePluginConfig').usePluginConfig;
const mockPlugin: CatalogPlugin = {
id: 'test-plugin',
name: 'Test Plugin',
description: 'Test plugin description',
type: PluginType.app,
isPublished: true,
isInstalled: true,
isCore: false,
isDev: false,
isDisabled: false,
isDeprecated: false,
isEnterprise: false,
isFullyInstalled: true,
isManaged: false,
isPreinstalled: { found: false, withVersion: false },
hasUpdate: false,
info: {
logos: { small: '', large: '' },
keywords: [],
},
orgName: 'Test',
signature: PluginSignatureStatus.valid,
signatureType: PluginSignatureType.grafana,
signatureOrg: 'Grafana',
popularity: 1,
downloads: 100,
updatedAt: '2023-01-01',
publishedAt: '2023-01-01',
angularDetected: false,
accessControl: {},
};
const mockPluginConfig = {
meta: {
id: 'test-plugin',
name: 'Test Plugin',
type: PluginType.app,
module: 'test',
baseUrl: '',
info: {
author: { name: 'Test' },
description: 'Test plugin',
logos: { small: '', large: '' },
links: [],
screenshots: [],
updated: '2023-01-01',
version: '1.0.0',
keywords: [],
},
},
configPages: [
{
id: 'config-page-1',
title: 'Configuration',
icon: 'cog',
body: () => null,
},
],
};
describe('usePluginDetailsTabs', () => {
beforeEach(() => {
jest.clearAllMocks();
config.featureToggles.externalServiceAccounts = false;
});
it('should not include config-specific tabs while plugin config is loading', () => {
// simulate the race condition: plugin is loaded but config is still loading
mockUsePluginConfig.mockReturnValue({
loading: true,
error: undefined,
value: null,
});
const { result } = renderHook(() => usePluginDetailsTabs(mockPlugin, undefined, false));
// should NOT include config page tabs while loading
const configTab = result.current.navModel.children?.find((tab) => tab.id === 'config-page-1');
expect(configTab).toBeUndefined();
// should include basic tabs
const overviewTab = result.current.navModel.children?.find((tab) => tab.id === 'overview');
expect(overviewTab).toBeDefined();
});
it('should include config-specific tabs when plugin config is loaded', () => {
// simulate config loaded successfully
mockUsePluginConfig.mockReturnValue({
loading: false,
error: undefined,
value: mockPluginConfig,
});
const { result } = renderHook(() => usePluginDetailsTabs(mockPlugin, undefined, false));
// should NOW include config page tabs
const configTab = result.current.navModel.children?.find((tab) => tab.id === 'config-page-1');
expect(configTab).toBeDefined();
expect(configTab?.text).toBe('Configuration');
});
});
@@ -85,7 +85,8 @@ export const usePluginDetailsTabs = (
}
// Not extending the tabs with the config pages if the plugin is not installed
if (!pluginConfig) {
// also wait if the plugin config is still loading to avoid showing default tabs prematurely
if (!pluginConfig || loading) {
return navModelChildren;
}
@@ -151,7 +152,7 @@ export const usePluginDetailsTabs = (
}
return navModelChildren;
}, [plugin, pluginConfig, pathname, isPublished, currentPageId, isNarrowScreen]);
}, [plugin, pluginConfig, pathname, isPublished, currentPageId, isNarrowScreen, loading]);
const navModel: NavModelItem = {
text: plugin?.name ?? '',