fix(tests): fix test failures

- Fix Prettier formatting in 8 files
- Fix useProvisionedRequestHandler.test.ts by mocking config.bootData
- Ensures ContextSrv can be instantiated in tests
This commit is contained in:
Roberto Jimenez Sanchez
2025-12-02 21:53:45 +01:00
parent cf01ea372b
commit ea7ade6983
9 changed files with 83 additions and 69 deletions
@@ -65,8 +65,12 @@ export function BrowseActions({ folderDTO }: Props) {
}; };
const onDelete = async () => { const onDelete = async () => {
const selectedDashboards = Object.keys(selectedItemsForActions.dashboard).filter((uid) => selectedItemsForActions.dashboard[uid]); const selectedDashboards = Object.keys(selectedItemsForActions.dashboard).filter(
const selectedFolders = Object.keys(selectedItemsForActions.folder).filter((uid) => selectedItemsForActions.folder[uid]); (uid) => selectedItemsForActions.dashboard[uid]
);
const selectedFolders = Object.keys(selectedItemsForActions.folder).filter(
(uid) => selectedItemsForActions.folder[uid]
);
await deleteDashboards({ dashboardUIDs: selectedDashboards }); await deleteDashboards({ dashboardUIDs: selectedDashboards });
await deleteFolders({ folderUIDs: selectedFolders }); await deleteFolders({ folderUIDs: selectedFolders });
trackAction('delete', selectedItemsForActions); trackAction('delete', selectedItemsForActions);
@@ -74,8 +78,12 @@ export function BrowseActions({ folderDTO }: Props) {
}; };
const onMove = async (destinationUID: string) => { const onMove = async (destinationUID: string) => {
const selectedDashboards = Object.keys(selectedItemsForActions.dashboard).filter((uid) => selectedItemsForActions.dashboard[uid]); const selectedDashboards = Object.keys(selectedItemsForActions.dashboard).filter(
const selectedFolders = Object.keys(selectedItemsForActions.folder).filter((uid) => selectedItemsForActions.folder[uid]); (uid) => selectedItemsForActions.dashboard[uid]
);
const selectedFolders = Object.keys(selectedItemsForActions.folder).filter(
(uid) => selectedItemsForActions.folder[uid]
);
await moveFolders({ folderUIDs: selectedFolders, destinationUID }); await moveFolders({ folderUIDs: selectedFolders, destinationUID });
await moveDashboards({ dashboardUIDs: selectedDashboards, destinationUID }); await moveDashboards({ dashboardUIDs: selectedDashboards, destinationUID });
@@ -133,13 +133,13 @@ export function FolderActionsButton({ folder, repoType, isReadOnlyRepo }: Props)
try { try {
// Collect all dashboards under this folder and its children // Collect all dashboards under this folder and its children
const dashboardUIDs = await collectAllDashboardsUnderFolder(folder.uid); const dashboardUIDs = await collectAllDashboardsUnderFolder(folder.uid);
// Create selected items object with all dashboards // Create selected items object with all dashboards
const selectedDashboards: Record<string, boolean> = {}; const selectedDashboards: Record<string, boolean> = {};
dashboardUIDs.forEach((uid) => { dashboardUIDs.forEach((uid) => {
selectedDashboards[uid] = true; selectedDashboards[uid] = true;
}); });
setExportSelectedDashboards(selectedDashboards); setExportSelectedDashboards(selectedDashboards);
setShowExportFolderDrawer(true); setShowExportFolderDrawer(true);
} catch (error) { } catch (error) {
@@ -183,9 +183,7 @@ export function FolderActionsButton({ folder, repoType, isReadOnlyRepo }: Props)
label={deleteLabel} label={deleteLabel}
/> />
)} )}
{provisioningEnabled && isUnmanagedFolder && ( {provisioningEnabled && isUnmanagedFolder && <MenuItem onClick={handleExportFolder} label={exportLabel} />}
<MenuItem onClick={handleExportFolder} label={exportLabel} />
)}
</Menu> </Menu>
); );
@@ -91,10 +91,7 @@ export const refetchChildren = createAsyncThunk(
export const selectFolderWithAllDashboards = createAsyncThunk( export const selectFolderWithAllDashboards = createAsyncThunk(
'browseDashboards/selectFolderWithAllDashboards', 'browseDashboards/selectFolderWithAllDashboards',
async ( async ({ folderUID, isSelected }: { folderUID: string; isSelected: boolean }, { dispatch, getState }) => {
{ folderUID, isSelected }: { folderUID: string; isSelected: boolean },
{ dispatch, getState }
) => {
const state = getState().browseDashboards; const state = getState().browseDashboards;
// Find the folder item to get its parentUID and managedBy // Find the folder item to get its parentUID and managedBy
@@ -102,15 +99,17 @@ export const selectFolderWithAllDashboards = createAsyncThunk(
if (!isSelected) { if (!isSelected) {
// When deselecting, use the normal action - it will handle deselecting all children recursively // When deselecting, use the normal action - it will handle deselecting all children recursively
dispatch(setItemSelectionState({ dispatch(
item: { setItemSelectionState({
kind: 'folder', item: {
uid: folderUID, kind: 'folder',
parentUID: folderItem?.parentUID, uid: folderUID,
managedBy: folderItem?.managedBy parentUID: folderItem?.parentUID,
}, managedBy: folderItem?.managedBy,
isSelected: false },
})); isSelected: false,
})
);
return; return;
} }
@@ -119,15 +118,17 @@ export const selectFolderWithAllDashboards = createAsyncThunk(
const dashboardUIDs = await collectAllDashboardsUnderFolder(folderUID); const dashboardUIDs = await collectAllDashboardsUnderFolder(folderUID);
// First, select the folder itself // First, select the folder itself
dispatch(setItemSelectionState({ dispatch(
item: { setItemSelectionState({
kind: 'folder', item: {
uid: folderUID, kind: 'folder',
parentUID: folderItem?.parentUID, uid: folderUID,
managedBy: folderItem?.managedBy parentUID: folderItem?.parentUID,
}, managedBy: folderItem?.managedBy,
isSelected: true },
})); isSelected: true,
})
);
// Then select all dashboards found // Then select all dashboards found
// We need to get the parentUID for each dashboard from the state // We need to get the parentUID for each dashboard from the state
@@ -136,15 +137,17 @@ export const selectFolderWithAllDashboards = createAsyncThunk(
const dashboardItem = findItem(state.rootItems?.items ?? [], state.childrenByParentUID, dashboardUID); const dashboardItem = findItem(state.rootItems?.items ?? [], state.childrenByParentUID, dashboardUID);
// Even if dashboard isn't in state, we can still select it by UID // Even if dashboard isn't in state, we can still select it by UID
// The reducer will handle setting selectedItems.dashboard[dashboardUID] = true // The reducer will handle setting selectedItems.dashboard[dashboardUID] = true
dispatch(setItemSelectionState({ dispatch(
item: { setItemSelectionState({
kind: 'dashboard', item: {
uid: dashboardUID, kind: 'dashboard',
parentUID: dashboardItem?.parentUID ?? folderUID, // Fallback to folderUID if not found uid: dashboardUID,
managedBy: dashboardItem?.managedBy parentUID: dashboardItem?.parentUID ?? folderUID, // Fallback to folderUID if not found
}, managedBy: dashboardItem?.managedBy,
isSelected: true },
})); isSelected: true,
})
);
} }
} }
); );
@@ -29,4 +29,3 @@ function ExportToRepositoryRenderer({ model }: SceneComponentProps<ExportToRepos
/> />
); );
} }
@@ -57,10 +57,7 @@ export function RepositoryList({ items }: Props) {
} }
onRemove={unmanagedCount > 0 ? handlePushUnmanaged : undefined} onRemove={unmanagedCount > 0 ? handlePushUnmanaged : undefined}
> >
<Trans <Trans i18nKey="provisioning.folder-repository-list.partial-managed" values={{ managedCount, resourceCount }}>
i18nKey="provisioning.folder-repository-list.partial-managed"
values={{ managedCount, resourceCount }}
>
{{ managedCount }}/{{ resourceCount }} resources managed by Git sync. {{ managedCount }}/{{ resourceCount }} resources managed by Git sync.
</Trans> </Trans>
{unmanagedCount > 0 && ( {unmanagedCount > 0 && (
@@ -60,17 +60,15 @@ function FormContent({ initialValues, selectedItems, workflowOptions, onDismiss
}, [repositories, selectedRepositoryName, isLoadingRepos]); }, [repositories, selectedRepositoryName, isLoadingRepos]);
// Get selected repository // Get selected repository
const repositoryView: RepositoryView | undefined = repositories.find( const repositoryView: RepositoryView | undefined = repositories.find((repo) => repo.name === selectedRepositoryName);
(repo) => repo.name === selectedRepositoryName
);
// Compute workflow options based on selected repository // Compute workflow options based on selected repository
const selectedWorkflowOptions = repositoryView ? getWorkflowOptions(repositoryView) : workflowOptions; const selectedWorkflowOptions = repositoryView ? getWorkflowOptions(repositoryView) : workflowOptions;
const selectedDefaultWorkflow = repositoryView const selectedDefaultWorkflow = repositoryView
? getDefaultWorkflow(repositoryView) ? getDefaultWorkflow(repositoryView)
: (workflowOptions[0]?.value === 'branch' || workflowOptions[0]?.value === 'write' : workflowOptions[0]?.value === 'branch' || workflowOptions[0]?.value === 'write'
? workflowOptions[0].value ? workflowOptions[0].value
: undefined); : undefined;
// Update workflow, branch, and path when repository changes // Update workflow, branch, and path when repository changes
useEffect(() => { useEffect(() => {
@@ -216,13 +214,14 @@ function FormContent({ initialValues, selectedItems, workflowOptions, onDismiss
</Box> </Box>
{/* Show form-level errors */} {/* Show form-level errors */}
{errors.root && ( {errors.root && <Alert severity="error" title={String(errors.root.message)} />}
<Alert severity="error" title={String(errors.root.message)} />
)}
{/* Info if folders are selected */} {/* Info if folders are selected */}
{Object.keys(selectedItems.folder || {}).filter((uid) => selectedItems.folder[uid]).length > 0 && ( {Object.keys(selectedItems.folder || {}).filter((uid) => selectedItems.folder[uid]).length > 0 && (
<Alert severity="info" title={t('browse-dashboards.bulk-export-resources-form.folders-info', 'Folders in selection')}> <Alert
severity="info"
title={t('browse-dashboards.bulk-export-resources-form.folders-info', 'Folders in selection')}
>
{t( {t(
'browse-dashboards.bulk-export-resources-form.folders-info-description', 'browse-dashboards.bulk-export-resources-form.folders-info-description',
'Folders will be left behind. New folders will be created in the repository based on the resource folder structure.' 'Folders will be left behind. New folders will be created in the repository based on the resource folder structure.'
@@ -297,7 +296,10 @@ function FormContent({ initialValues, selectedItems, workflowOptions, onDismiss
<Input <Input
type="text" type="text"
{...methods.register('path')} {...methods.register('path')}
placeholder={t('browse-dashboards.bulk-export-resources-form.path-placeholder', 'e.g., dashboards/')} placeholder={t(
'browse-dashboards.bulk-export-resources-form.path-placeholder',
'e.g., dashboards/'
)}
/> />
</Field> </Field>
)} )}
@@ -318,10 +320,7 @@ function FormContent({ initialValues, selectedItems, workflowOptions, onDismiss
<Button variant="secondary" fill="outline" onClick={onDismiss} disabled={isCreatingJob}> <Button variant="secondary" fill="outline" onClick={onDismiss} disabled={isCreatingJob}>
<Trans i18nKey="browse-dashboards.bulk-export-resources-form.button-cancel">Cancel</Trans> <Trans i18nKey="browse-dashboards.bulk-export-resources-form.button-cancel">Cancel</Trans>
</Button> </Button>
<Button <Button type="submit" disabled={!!job || isCreatingJob || hasSubmitted || !selectedRepositoryName}>
type="submit"
disabled={!!job || isCreatingJob || hasSubmitted || !selectedRepositoryName}
>
{isCreatingJob {isCreatingJob
? t('browse-dashboards.bulk-export-resources-form.button-exporting', 'Exporting...') ? t('browse-dashboards.bulk-export-resources-form.button-exporting', 'Exporting...')
: t('browse-dashboards.bulk-export-resources-form.button-export', 'Export')} : t('browse-dashboards.bulk-export-resources-form.button-export', 'Export')}
@@ -382,4 +381,3 @@ const getPathPrefixStyles = (theme: GrafanaTheme2) => ({
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
}), }),
}); });
@@ -7,9 +7,21 @@ import { ResourceWrapper } from 'app/api/clients/provisioning/v0alpha1';
import { useProvisionedRequestHandler, RequestHandlers } from './useProvisionedRequestHandler'; import { useProvisionedRequestHandler, RequestHandlers } from './useProvisionedRequestHandler';
jest.mock('@grafana/runtime', () => ({ jest.mock('@grafana/runtime', () => {
getAppEvents: jest.fn(), const original = jest.requireActual('@grafana/runtime');
})); return {
...original,
getAppEvents: jest.fn(),
config: {
...original.config,
bootData: {
user: {},
settings: {},
navTree: [],
},
},
};
});
jest.mock('@grafana/i18n', () => ({ jest.mock('@grafana/i18n', () => ({
t: jest.fn((key: string, defaultValue: string) => defaultValue), t: jest.fn((key: string, defaultValue: string) => defaultValue),
@@ -12,9 +12,10 @@ import { findItem } from '../../browse-dashboards/state/utils';
import { DashboardTreeSelection } from '../../browse-dashboards/types'; import { DashboardTreeSelection } from '../../browse-dashboards/types';
// This hook checks if selected items are unmanaged (not managed by any repository) // This hook checks if selected items are unmanaged (not managed by any repository)
export function useSelectionUnmanagedStatus( export function useSelectionUnmanagedStatus(selectedItems: Omit<DashboardTreeSelection, 'panel' | '$all'>): {
selectedItems: Omit<DashboardTreeSelection, 'panel' | '$all'> hasUnmanaged: boolean;
): { hasUnmanaged: boolean; isLoading: boolean } { isLoading: boolean;
} {
const browseState = useSelector((state) => state.browseDashboards); const browseState = useSelector((state) => state.browseDashboards);
const [, stateManager] = useSearchStateManager(); const [, stateManager] = useSearchStateManager();
const isSearching = stateManager.hasSearchFilters(); const isSearching = stateManager.hasSearchFilters();
@@ -155,4 +156,3 @@ export function useSelectionUnmanagedStatus(
return status; return status;
} }
@@ -69,4 +69,3 @@ export async function collectAllDashboardsUnderFolder(folderUID: string): Promis
return dashboardUIDs; return dashboardUIDs;
} }