Wire up compatibility check to CommunityDashboardSection
This commit is contained in:
+131
-5
@@ -2,10 +2,12 @@ import { screen, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { render } from 'test/test-utils';
|
||||
|
||||
import { DashboardJson } from 'app/features/manage-dashboards/types';
|
||||
|
||||
import { CommunityDashboardSection } from './CommunityDashboardSection';
|
||||
import { fetchCommunityDashboards } from './api/dashboardLibraryApi';
|
||||
import { GnetDashboard } from './types';
|
||||
import { onUseCommunityDashboard } from './utils/communityDashboardHelpers';
|
||||
import { onUseCommunityDashboard, interpolateDashboardForCompatibilityCheck } from './utils/communityDashboardHelpers';
|
||||
|
||||
jest.mock('./api/dashboardLibraryApi', () => ({
|
||||
fetchCommunityDashboards: jest.fn(),
|
||||
@@ -14,21 +16,32 @@ jest.mock('./api/dashboardLibraryApi', () => ({
|
||||
jest.mock('./utils/communityDashboardHelpers', () => ({
|
||||
...jest.requireActual('./utils/communityDashboardHelpers'),
|
||||
onUseCommunityDashboard: jest.fn(),
|
||||
interpolateDashboardForCompatibilityCheck: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('./CompatibilityModal', () => ({
|
||||
CompatibilityModal: jest.fn(() => <div>Compatibility Modal</div>),
|
||||
}));
|
||||
|
||||
// Track the datasource type for mocking
|
||||
let mockDatasourceType = 'prometheus';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getDataSourceSrv: () => ({
|
||||
getInstanceSettings: jest.fn((uid: string) => ({
|
||||
uid,
|
||||
name: `DataSource ${uid}`,
|
||||
type: 'test',
|
||||
type: mockDatasourceType,
|
||||
})),
|
||||
}),
|
||||
}));
|
||||
|
||||
const mockFetchCommunityDashboards = fetchCommunityDashboards as jest.MockedFunction<typeof fetchCommunityDashboards>;
|
||||
const mockOnUseCommunityDashboard = onUseCommunityDashboard as jest.MockedFunction<typeof onUseCommunityDashboard>;
|
||||
const mockInterpolateDashboard = interpolateDashboardForCompatibilityCheck as jest.MockedFunction<
|
||||
typeof interpolateDashboardForCompatibilityCheck
|
||||
>;
|
||||
|
||||
const createMockGnetDashboard = (overrides: Partial<GnetDashboard> = {}): GnetDashboard => ({
|
||||
id: 1,
|
||||
@@ -42,13 +55,14 @@ const createMockGnetDashboard = (overrides: Partial<GnetDashboard> = {}): GnetDa
|
||||
|
||||
const setup = async (
|
||||
props: Partial<React.ComponentProps<typeof CommunityDashboardSection>> = {},
|
||||
successScenario = true
|
||||
successScenario = true,
|
||||
datasourceUid = 'test-datasource-uid'
|
||||
) => {
|
||||
const renderResult = render(
|
||||
<CommunityDashboardSection onShowMapping={jest.fn()} datasourceType="test" {...props} />,
|
||||
<CommunityDashboardSection onShowMapping={jest.fn()} datasourceType={mockDatasourceType} {...props} />,
|
||||
{
|
||||
historyOptions: {
|
||||
initialEntries: ['/test?dashboardLibraryDatasourceUid=test-datasource-uid'],
|
||||
initialEntries: [`/test?dashboardLibraryDatasourceUid=${datasourceUid}`],
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -65,6 +79,7 @@ const setup = async (
|
||||
describe('CommunityDashboardSection', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockDatasourceType = 'prometheus';
|
||||
});
|
||||
|
||||
it('should render', async () => {
|
||||
@@ -122,4 +137,115 @@ describe('CommunityDashboardSection', () => {
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith('Error loading community dashboards', expect.any(Error));
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
|
||||
describe('Compatibility Check Feature', () => {
|
||||
it('should show "Check Compatibility" button when datasource type is prometheus', async () => {
|
||||
mockDatasourceType = 'prometheus';
|
||||
mockFetchCommunityDashboards.mockResolvedValue({
|
||||
page: 1,
|
||||
pages: 5,
|
||||
items: [createMockGnetDashboard()],
|
||||
});
|
||||
|
||||
await setup();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('button', { name: 'Check compatibility' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should hide "Check Compatibility" button when datasource type is not prometheus', async () => {
|
||||
mockDatasourceType = 'influxdb';
|
||||
mockFetchCommunityDashboards.mockResolvedValue({
|
||||
page: 1,
|
||||
pages: 5,
|
||||
items: [createMockGnetDashboard()],
|
||||
});
|
||||
|
||||
await setup();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Dashboard')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.queryByRole('button', { name: 'Check compatibility' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should hide "Check Compatibility" button when no datasourceUid in URL', async () => {
|
||||
mockDatasourceType = 'prometheus';
|
||||
mockFetchCommunityDashboards.mockResolvedValue({
|
||||
page: 1,
|
||||
pages: 5,
|
||||
items: [createMockGnetDashboard()],
|
||||
});
|
||||
|
||||
// Render without datasourceUid in URL
|
||||
render(<CommunityDashboardSection onShowMapping={jest.fn()} datasourceType="prometheus" />, {
|
||||
historyOptions: {
|
||||
initialEntries: ['/test'],
|
||||
},
|
||||
});
|
||||
|
||||
// Wait for component to finish initial rendering
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('button', { name: 'Check compatibility' })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should call interpolation function and open modal when "Check Compatibility" is clicked', async () => {
|
||||
mockDatasourceType = 'prometheus';
|
||||
mockFetchCommunityDashboards.mockResolvedValue({
|
||||
page: 1,
|
||||
pages: 5,
|
||||
items: [createMockGnetDashboard()],
|
||||
});
|
||||
|
||||
const mockInterpolatedDashboard: DashboardJson = { title: 'Interpolated Dashboard' } as DashboardJson;
|
||||
mockInterpolateDashboard.mockResolvedValue(mockInterpolatedDashboard);
|
||||
|
||||
const { user } = await setup();
|
||||
|
||||
const checkCompatibilityButton = screen.getByRole('button', { name: 'Check compatibility' });
|
||||
await user.click(checkCompatibilityButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockInterpolateDashboard).toHaveBeenCalledWith(1, 'test-datasource-uid');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Compatibility Modal')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should display error alert when interpolation fails', async () => {
|
||||
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
||||
mockDatasourceType = 'prometheus';
|
||||
mockFetchCommunityDashboards.mockResolvedValue({
|
||||
page: 1,
|
||||
pages: 5,
|
||||
items: [createMockGnetDashboard()],
|
||||
});
|
||||
|
||||
mockInterpolateDashboard.mockRejectedValue(
|
||||
new Error('Unable to automatically map all datasource inputs for this dashboard')
|
||||
);
|
||||
|
||||
const { user } = await setup();
|
||||
|
||||
const checkCompatibilityButton = screen.getByRole('button', { name: 'Check compatibility' });
|
||||
await user.click(checkCompatibilityButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Error loading dashboard')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText('Unable to automatically map all datasource inputs for this dashboard')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+61
-1
@@ -7,7 +7,10 @@ import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { Button, useStyles2, Stack, Grid, EmptyState, Alert, FilterInput, Box } from '@grafana/ui';
|
||||
import { DashboardJson } from 'app/features/manage-dashboards/types';
|
||||
import { PluginDashboard } from 'app/types/plugins';
|
||||
|
||||
import { CompatibilityModal } from './CompatibilityModal';
|
||||
import { DashboardCard } from './DashboardCard';
|
||||
import { MappingContext } from './SuggestedDashboardsModal';
|
||||
import { fetchCommunityDashboards } from './api/dashboardLibraryApi';
|
||||
@@ -18,12 +21,13 @@ import {
|
||||
EVENT_LOCATIONS,
|
||||
SOURCE_ENTRY_POINTS,
|
||||
} from './interactions';
|
||||
import { GnetDashboard } from './types';
|
||||
import { GnetDashboard, isGnetDashboard } from './types';
|
||||
import {
|
||||
getThumbnailUrl,
|
||||
getLogoUrl,
|
||||
buildDashboardDetails,
|
||||
onUseCommunityDashboard,
|
||||
interpolateDashboardForCompatibilityCheck,
|
||||
COMMUNITY_PAGE_SIZE_QUERY,
|
||||
COMMUNITY_RESULT_SIZE,
|
||||
} from './utils/communityDashboardHelpers';
|
||||
@@ -44,6 +48,8 @@ export const CommunityDashboardSection = ({ onShowMapping, datasourceType }: Pro
|
||||
const datasourceUid = searchParams.get('dashboardLibraryDatasourceUid');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const hasTrackedLoaded = useRef(false);
|
||||
const [selectedDashboardJson, setSelectedDashboardJson] = useState<DashboardJson | null>(null);
|
||||
const [isCompatibilityModalOpen, setIsCompatibilityModalOpen] = useState(false);
|
||||
|
||||
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState('');
|
||||
useDebounce(
|
||||
@@ -151,6 +157,31 @@ export const CommunityDashboardSection = ({ onShowMapping, datasourceType }: Pro
|
||||
[response, datasourceUid, debouncedSearchQuery, onShowMapping]
|
||||
);
|
||||
|
||||
const [{ error: fetchError }, handleCheckCompatibility] = useAsyncFn(
|
||||
async (dashboard: PluginDashboard | GnetDashboard): Promise<void> => {
|
||||
// Type guard: Only GnetDashboards (community dashboards) are supported
|
||||
if (!isGnetDashboard(dashboard)) {
|
||||
console.warn('Compatibility check is only supported for community dashboards (GnetDashboard)');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!datasourceUid || !response?.datasourceType) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const interpolatedDashboard = await interpolateDashboardForCompatibilityCheck(dashboard.id, datasourceUid);
|
||||
|
||||
setSelectedDashboardJson(interpolatedDashboard);
|
||||
setIsCompatibilityModalOpen(true);
|
||||
} catch (err) {
|
||||
console.error('Error preparing dashboard for compatibility check:', err);
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
[datasourceUid, response]
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack direction="column" gap={2} height="100%">
|
||||
{isPreviewDashboardError && (
|
||||
@@ -163,6 +194,20 @@ export const CommunityDashboardSection = ({ onShowMapping, datasourceType }: Pro
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
{fetchError && (
|
||||
<div>
|
||||
<Alert
|
||||
title={t('dashboard-library.compatibility-check-error-title', 'Error loading dashboard')}
|
||||
severity="error"
|
||||
>
|
||||
{fetchError.message ||
|
||||
t(
|
||||
'dashboard-library.compatibility-check-error-description',
|
||||
'Failed to load dashboard for compatibility check. Please try again.'
|
||||
)}
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
<FilterInput
|
||||
placeholder={
|
||||
datasourceType
|
||||
@@ -263,6 +308,8 @@ export const CommunityDashboardSection = ({ onShowMapping, datasourceType }: Pro
|
||||
isLogo={isLogo}
|
||||
details={details}
|
||||
kind="suggested_dashboard"
|
||||
showCompatibilityButton={!!datasourceUid && response?.datasourceType === 'prometheus'}
|
||||
onCheckCompatibility={handleCheckCompatibility}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -278,6 +325,19 @@ export const CommunityDashboardSection = ({ onShowMapping, datasourceType }: Pro
|
||||
</Stack>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Compatibility Modal - conditionally rendered */}
|
||||
{isCompatibilityModalOpen && selectedDashboardJson && datasourceUid && (
|
||||
<CompatibilityModal
|
||||
isOpen={isCompatibilityModalOpen}
|
||||
onDismiss={() => {
|
||||
setIsCompatibilityModalOpen(false);
|
||||
setSelectedDashboardJson(null);
|
||||
}}
|
||||
dashboardJson={selectedDashboardJson}
|
||||
datasourceUid={datasourceUid}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Badge, Box, Button, Card, IconButton, Text, TextLink, Tooltip, useStyle
|
||||
import { attachSkeleton, SkeletonComponent } from '@grafana/ui/unstable';
|
||||
import { PluginDashboard } from 'app/types/plugins';
|
||||
|
||||
import { GnetDashboard } from './types';
|
||||
import { GnetDashboard, isGnetDashboard } from './types';
|
||||
|
||||
interface Details {
|
||||
id: string;
|
||||
@@ -28,7 +28,7 @@ interface Props {
|
||||
showDatasourceProvidedBadge?: boolean;
|
||||
dimThumbnail?: boolean; // Apply 50% opacity to thumbnail when badge is shown
|
||||
kind: 'template_dashboard' | 'suggested_dashboard';
|
||||
onCheckCompatibility?: (dashboard: PluginDashboard | GnetDashboard) => void;
|
||||
onCheckCompatibility?: (dashboard: PluginDashboard | GnetDashboard) => void | Promise<void>;
|
||||
showCompatibilityButton?: boolean;
|
||||
}
|
||||
|
||||
@@ -116,7 +116,12 @@ function DashboardCardComponent({
|
||||
icon="check-circle"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onCheckCompatibility(dashboard);
|
||||
// Only call compatibility check for GnetDashboards (community dashboards)
|
||||
if (isGnetDashboard(dashboard)) {
|
||||
onCheckCompatibility(dashboard);
|
||||
} else {
|
||||
console.warn('Compatibility check is only supported for community dashboards (GnetDashboard)');
|
||||
}
|
||||
}}
|
||||
aria-label={t('dashboard-library.card.check-compatibility-button', 'Check compatibility')}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { DashboardJson } from 'app/features/manage-dashboards/types';
|
||||
import { PluginDashboard } from 'app/types/plugins';
|
||||
|
||||
export interface Link {
|
||||
rel: string;
|
||||
@@ -47,3 +48,11 @@ export interface GnetDashboardsResponse {
|
||||
pages: number;
|
||||
items: GnetDashboard[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if a dashboard is a GnetDashboard (community dashboard).
|
||||
* PluginDashboard has fields like importedRevision, importedUri, path that GnetDashboard doesn't have.
|
||||
*/
|
||||
export function isGnetDashboard(dashboard: PluginDashboard | GnetDashboard): dashboard is GnetDashboard {
|
||||
return !('importedRevision' in dashboard || 'importedUri' in dashboard || 'path' in dashboard);
|
||||
}
|
||||
|
||||
+145
-1
@@ -1,4 +1,4 @@
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { BackendSrv, getBackendSrv, locationService } from '@grafana/runtime';
|
||||
import { InputType, DataSourceInput, DashboardInput } from 'app/features/manage-dashboards/state/reducers';
|
||||
import { DashboardJson } from 'app/features/manage-dashboards/types';
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
getLogoUrl,
|
||||
navigateToTemplate,
|
||||
onUseCommunityDashboard,
|
||||
interpolateDashboardForCompatibilityCheck,
|
||||
} from './communityDashboardHelpers';
|
||||
|
||||
jest.mock('../api/dashboardLibraryApi', () => ({
|
||||
@@ -34,12 +35,34 @@ jest.mock('../interactions', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: jest.fn(),
|
||||
locationService: {
|
||||
push: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock function references
|
||||
const mockFetchCommunityDashboard = fetchCommunityDashboard as jest.MockedFunction<typeof fetchCommunityDashboard>;
|
||||
const mockTryAutoMapDatasources = tryAutoMapDatasources as jest.MockedFunction<typeof tryAutoMapDatasources>;
|
||||
const mockParseConstantInputs = parseConstantInputs as jest.MockedFunction<typeof parseConstantInputs>;
|
||||
const mockGetBackendSrv = getBackendSrv as jest.MockedFunction<typeof getBackendSrv>;
|
||||
|
||||
// Helper functions for creating mock objects
|
||||
const createMockBackendSrv = (overrides: Partial<BackendSrv> = {}): BackendSrv =>
|
||||
({
|
||||
post: jest.fn(),
|
||||
get: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
patch: jest.fn(),
|
||||
put: jest.fn(),
|
||||
request: jest.fn(),
|
||||
datasourceRequest: jest.fn(),
|
||||
resolveCancelerIfExists: jest.fn(),
|
||||
...overrides,
|
||||
}) as BackendSrv;
|
||||
|
||||
const createMockGnetDashboard = (overrides: Partial<GnetDashboard> = {}): GnetDashboard => ({
|
||||
id: 123,
|
||||
name: 'Test Dashboard',
|
||||
@@ -610,4 +633,125 @@ describe('communityDashboardHelpers', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('interpolateDashboardForCompatibilityCheck', () => {
|
||||
let mockPost: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mockPost = jest.fn();
|
||||
mockGetBackendSrv.mockReturnValue(createMockBackendSrv({ post: mockPost }));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should successfully interpolate dashboard when auto-mapping succeeds', async () => {
|
||||
const dashboardJson = createMockDashboardJson({
|
||||
__inputs: [
|
||||
{
|
||||
name: 'DS_PROMETHEUS',
|
||||
type: InputType.DataSource,
|
||||
label: 'Prometheus',
|
||||
value: '',
|
||||
description: '',
|
||||
pluginId: 'prometheus',
|
||||
info: '',
|
||||
} as DataSourceInput & { description: string },
|
||||
],
|
||||
});
|
||||
|
||||
const interpolatedDashboard = createMockDashboardJson({ title: 'Interpolated Dashboard' });
|
||||
|
||||
mockFetchCommunityDashboard.mockResolvedValue({ json: dashboardJson });
|
||||
mockTryAutoMapDatasources.mockReturnValue({
|
||||
allMapped: true,
|
||||
mappings: [{ name: 'DS_PROMETHEUS', type: 'datasource', value: 'prom-uid', pluginId: 'prometheus' }],
|
||||
unmappedDsInputs: [],
|
||||
});
|
||||
mockPost.mockResolvedValue(interpolatedDashboard);
|
||||
|
||||
const result = await interpolateDashboardForCompatibilityCheck(123, 'prom-uid');
|
||||
|
||||
expect(result).toEqual(interpolatedDashboard);
|
||||
expect(mockFetchCommunityDashboard).toHaveBeenCalledWith(123);
|
||||
expect(mockTryAutoMapDatasources).toHaveBeenCalled();
|
||||
expect(mockPost).toHaveBeenCalledWith('/api/dashboards/interpolate', {
|
||||
dashboard: dashboardJson,
|
||||
overwrite: true,
|
||||
inputs: [{ name: 'DS_PROMETHEUS', type: 'datasource', value: 'prom-uid', pluginId: 'prometheus' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error when auto-mapping fails', async () => {
|
||||
const dashboardJson = createMockDashboardJson({
|
||||
__inputs: [
|
||||
{
|
||||
name: 'DS_PROMETHEUS',
|
||||
type: InputType.DataSource,
|
||||
label: 'Prometheus',
|
||||
value: '',
|
||||
description: '',
|
||||
pluginId: 'prometheus',
|
||||
info: '',
|
||||
} as DataSourceInput & { description: string },
|
||||
],
|
||||
});
|
||||
|
||||
mockFetchCommunityDashboard.mockResolvedValue({ json: dashboardJson });
|
||||
mockTryAutoMapDatasources.mockReturnValue({
|
||||
allMapped: false,
|
||||
mappings: [],
|
||||
unmappedDsInputs: [
|
||||
{
|
||||
name: 'DS_PROMETHEUS',
|
||||
pluginId: 'prometheus',
|
||||
type: InputType.DataSource,
|
||||
value: '',
|
||||
label: 'Prometheus',
|
||||
description: '',
|
||||
info: '',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await expect(interpolateDashboardForCompatibilityCheck(123, 'prom-uid')).rejects.toThrow(
|
||||
'Unable to automatically map all datasource inputs for this dashboard'
|
||||
);
|
||||
|
||||
expect(mockPost).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should throw error when interpolation API fails', async () => {
|
||||
const dashboardJson = createMockDashboardJson();
|
||||
|
||||
mockFetchCommunityDashboard.mockResolvedValue({ json: dashboardJson });
|
||||
mockTryAutoMapDatasources.mockReturnValue({
|
||||
allMapped: true,
|
||||
mappings: [],
|
||||
unmappedDsInputs: [],
|
||||
});
|
||||
mockPost.mockRejectedValue(new Error('API failed'));
|
||||
|
||||
await expect(interpolateDashboardForCompatibilityCheck(123, 'prom-uid')).rejects.toThrow('API failed');
|
||||
});
|
||||
|
||||
it('should handle dashboard with no __inputs', async () => {
|
||||
const dashboardJson = createMockDashboardJson({ __inputs: undefined });
|
||||
const interpolatedDashboard = createMockDashboardJson({ title: 'Interpolated Dashboard' });
|
||||
|
||||
mockFetchCommunityDashboard.mockResolvedValue({ json: dashboardJson });
|
||||
mockTryAutoMapDatasources.mockReturnValue({
|
||||
allMapped: true,
|
||||
mappings: [],
|
||||
unmappedDsInputs: [],
|
||||
});
|
||||
mockPost.mockResolvedValue(interpolatedDashboard);
|
||||
|
||||
const result = await interpolateDashboardForCompatibilityCheck(123, 'prom-uid');
|
||||
|
||||
expect(result).toEqual(interpolatedDashboard);
|
||||
expect(mockTryAutoMapDatasources).toHaveBeenCalledWith([], 'prom-uid');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+50
-1
@@ -1,6 +1,6 @@
|
||||
import { PanelModel } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { getBackendSrv, locationService } from '@grafana/runtime';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||
import { DataSourceInput } from 'app/features/manage-dashboards/state/reducers';
|
||||
@@ -311,3 +311,52 @@ export async function onUseCommunityDashboard({
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolate a community dashboard for compatibility checking.
|
||||
*
|
||||
* This function fetches the dashboard from Grafana.com, auto-maps datasource inputs,
|
||||
* and returns the interpolated dashboard with template variables resolved.
|
||||
*
|
||||
* @throws Error if auto-mapping fails - compatibility check requires all datasource inputs to be resolved
|
||||
* @param dashboardId - The Grafana.com dashboard ID
|
||||
* @param datasourceUid - The UID of the datasource to map to
|
||||
* @returns Promise<DashboardJson> - The interpolated dashboard with resolved template variables
|
||||
*/
|
||||
export async function interpolateDashboardForCompatibilityCheck(
|
||||
dashboardId: number,
|
||||
datasourceUid: string
|
||||
): Promise<DashboardJson> {
|
||||
// 1. Fetch full dashboard JSON from Grafana.com
|
||||
const gnetResponse = await fetchCommunityDashboard(dashboardId);
|
||||
const dashboardJson = gnetResponse.json;
|
||||
|
||||
// 2. Extract datasource inputs from dashboard's __inputs array
|
||||
const dsInputs: DataSourceInput[] = dashboardJson.__inputs?.filter(isDataSourceInput) || [];
|
||||
|
||||
// 3. Auto-map datasources using existing utility
|
||||
const mappingResult = tryAutoMapDatasources(dsInputs, datasourceUid);
|
||||
|
||||
// 4. Check if auto-mapping was successful
|
||||
// Compatibility check requires all datasource variables to be resolved
|
||||
if (!mappingResult.allMapped) {
|
||||
throw new Error(
|
||||
t(
|
||||
'dashboard-library.compatibility-auto-map-failed',
|
||||
'Unable to automatically map all datasource inputs for this dashboard. Compatibility check requires all datasource variables to be resolved.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// 5. Prepare inputs array for interpolation API
|
||||
const inputs: InputMapping[] = mappingResult.mappings;
|
||||
|
||||
// 6. Call interpolation endpoint to replace template variables
|
||||
const interpolatedDashboard = await getBackendSrv().post<DashboardJson>('/api/dashboards/interpolate', {
|
||||
dashboard: dashboardJson,
|
||||
overwrite: true,
|
||||
inputs: inputs,
|
||||
});
|
||||
|
||||
return interpolatedDashboard;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user