useProvisionedRequestHandler: Refactor to support more resource type (#108945)
* useProvisionedRequestHandler: refactor to support more resource type
This commit is contained in:
+41
-3
@@ -49,6 +49,26 @@ jest.mock('app/features/dashboard-scene/components/Provisioned/ResourceEditFormS
|
||||
ResourceEditFormSharedFields: () => <div data-testid="shared-fields" />,
|
||||
}));
|
||||
|
||||
const MOCK_DATA = {
|
||||
repository: {
|
||||
name: 'test-repo',
|
||||
namespace: 'default',
|
||||
title: 'Test Repository',
|
||||
type: 'git',
|
||||
},
|
||||
resource: {
|
||||
type: {
|
||||
kind: 'Folder',
|
||||
},
|
||||
upsert: {
|
||||
apiVersion: 'v1',
|
||||
kind: 'Folder',
|
||||
metadata: { name: 'test-folder', uid: 'test-folder-uid' },
|
||||
spec: { title: 'Test Folder' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockUseDeleteRepositoryFilesMutation = useDeleteRepositoryFilesWithPathMutation as jest.MockedFunction<
|
||||
typeof useDeleteRepositoryFilesWithPathMutation
|
||||
>;
|
||||
@@ -267,7 +287,13 @@ describe('DeleteProvisionedFolderForm', () => {
|
||||
|
||||
describe('success handling', () => {
|
||||
it('should navigate to parent folder on successful write workflow', async () => {
|
||||
const successState = { isLoading: false, isSuccess: true, isError: false, error: null };
|
||||
const successState = {
|
||||
isLoading: false,
|
||||
isSuccess: true,
|
||||
isError: false,
|
||||
error: null,
|
||||
data: MOCK_DATA,
|
||||
};
|
||||
setup({}, defaultHookData, successState);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -277,7 +303,13 @@ describe('DeleteProvisionedFolderForm', () => {
|
||||
|
||||
it('should navigate to dashboards root when parent folder has no parentUid', async () => {
|
||||
const folderWithoutParent = { ...mockParentFolder, parentUid: undefined };
|
||||
const successState = { isLoading: false, isSuccess: true, isError: false, error: null };
|
||||
const successState = {
|
||||
isLoading: false,
|
||||
isSuccess: true,
|
||||
isError: false,
|
||||
error: null,
|
||||
data: MOCK_DATA,
|
||||
};
|
||||
setup({ parentFolder: folderWithoutParent }, defaultHookData, successState);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -292,13 +324,19 @@ describe('DeleteProvisionedFolderForm', () => {
|
||||
isSuccess: true,
|
||||
isError: false,
|
||||
error: null,
|
||||
data: { urls: { newPullRequestURL: 'https://github.com/test/repo/pull/new' } },
|
||||
data: {
|
||||
...MOCK_DATA,
|
||||
ref: 'feature-branch',
|
||||
path: 'folders/test-folder.json',
|
||||
urls: { newPullRequestURL: 'https://github.com/test/repo/pull/new' },
|
||||
},
|
||||
};
|
||||
const { mockNavigate } = setup({}, { ...defaultHookData, initialValues: branchFormData }, successState);
|
||||
|
||||
await waitFor(() => {
|
||||
const expectedParams = new URLSearchParams();
|
||||
expectedParams.set('new_pull_request_url', 'https://github.com/test/repo/pull/new');
|
||||
expectedParams.set('repo_type', 'git');
|
||||
const expectedUrl = `/dashboards?${expectedParams.toString()}`;
|
||||
|
||||
expect(mockNavigate).toHaveBeenCalledWith(expectedUrl);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom-v5-compat';
|
||||
|
||||
@@ -12,6 +11,10 @@ import { AnnoKeySourcePath } from 'app/features/apiserver/types';
|
||||
import { ResourceEditFormSharedFields } from 'app/features/dashboard-scene/components/Provisioned/ResourceEditFormSharedFields';
|
||||
import { BaseProvisionedFormData } from 'app/features/dashboard-scene/saving/shared';
|
||||
import { buildResourceBranchRedirectUrl } from 'app/features/dashboard-scene/settings/utils';
|
||||
import {
|
||||
useProvisionedRequestHandler,
|
||||
ProvisionedOperationInfo,
|
||||
} from 'app/features/dashboard-scene/utils/useProvisionedRequestHandler';
|
||||
import { FolderDTO } from 'app/types/folders';
|
||||
|
||||
import { useProvisionedFolderFormData } from '../hooks/useProvisionedFolderFormData';
|
||||
@@ -57,50 +60,51 @@ function FormContent({ initialValues, parentFolder, repository, workflowOptions,
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: move to a hook if this useEffect shared mostly the same logic as in NewProvisionedFolderForm
|
||||
useEffect(() => {
|
||||
if (request.isSuccess && repository) {
|
||||
const prUrl = request.data?.urls?.newPullRequestURL;
|
||||
if (workflow === 'branch' && prUrl) {
|
||||
const url = buildResourceBranchRedirectUrl({
|
||||
paramName: 'new_pull_request_url',
|
||||
paramValue: prUrl,
|
||||
repoType: request.data?.repository?.type,
|
||||
});
|
||||
navigate(url);
|
||||
return;
|
||||
}
|
||||
|
||||
if (workflow === 'write') {
|
||||
getAppEvents().publish({
|
||||
type: AppEvents.alertSuccess.name,
|
||||
payload: [
|
||||
t(
|
||||
'browse-dashboards.delete-provisioned-folder-form.alert-folder-deleted-successfully',
|
||||
'Folder deleted successfully'
|
||||
),
|
||||
],
|
||||
});
|
||||
// Navigate back to parent folder if it exists, otherwise go to dashboards root
|
||||
if (parentFolder?.parentUid) {
|
||||
window.location.href = getFolderURL(parentFolder.parentUid);
|
||||
} else {
|
||||
window.location.href = '/dashboards';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (request.isError) {
|
||||
getAppEvents().publish({
|
||||
type: AppEvents.alertError.name,
|
||||
payload: [
|
||||
t('browse-dashboards.delete-provisioned-folder-form.api-error', 'Failed to delete folder'),
|
||||
request.error,
|
||||
],
|
||||
const onBranchSuccess = ({ urls }: { urls?: Record<string, string> }, info: ProvisionedOperationInfo) => {
|
||||
const prUrl = urls?.newPullRequestURL;
|
||||
if (prUrl) {
|
||||
const url = buildResourceBranchRedirectUrl({
|
||||
paramName: 'new_pull_request_url',
|
||||
paramValue: prUrl,
|
||||
repoType: info.repoType,
|
||||
});
|
||||
return;
|
||||
navigate(url);
|
||||
}
|
||||
}, [request, repository, workflow, parentFolder, navigate]);
|
||||
};
|
||||
|
||||
const onWriteSuccess = () => {
|
||||
// Navigate back to parent folder if it exists, otherwise go to dashboards root
|
||||
if (parentFolder?.parentUid) {
|
||||
window.location.href = getFolderURL(parentFolder.parentUid);
|
||||
} else {
|
||||
window.location.href = '/dashboards';
|
||||
}
|
||||
};
|
||||
|
||||
const onError = (error: unknown) => {
|
||||
getAppEvents().publish({
|
||||
type: AppEvents.alertError.name,
|
||||
payload: [t('browse-dashboards.delete-provisioned-folder-form.api-error', 'Failed to delete folder'), error],
|
||||
});
|
||||
};
|
||||
|
||||
// Use the repository-type and resource-type aware provisioned request handler
|
||||
useProvisionedRequestHandler({
|
||||
request,
|
||||
workflow,
|
||||
successMessage: t(
|
||||
'browse-dashboards.delete-provisioned-folder-form.success-message',
|
||||
'Folder deleted successfully'
|
||||
),
|
||||
resourceType: 'folder',
|
||||
repository,
|
||||
handlers: {
|
||||
onDismiss,
|
||||
onBranchSuccess,
|
||||
onWriteSuccess,
|
||||
onError,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom-v5-compat';
|
||||
|
||||
@@ -13,6 +12,10 @@ import { AnnoKeySourcePath, Resource } from 'app/features/apiserver/types';
|
||||
import { ResourceEditFormSharedFields } from 'app/features/dashboard-scene/components/Provisioned/ResourceEditFormSharedFields';
|
||||
import { BaseProvisionedFormData } from 'app/features/dashboard-scene/saving/shared';
|
||||
import { buildResourceBranchRedirectUrl } from 'app/features/dashboard-scene/settings/utils';
|
||||
import {
|
||||
useProvisionedRequestHandler,
|
||||
ProvisionedOperationInfo,
|
||||
} from 'app/features/dashboard-scene/utils/useProvisionedRequestHandler';
|
||||
import { PROVISIONING_URL } from 'app/features/provisioning/constants';
|
||||
import { usePullRequestParam } from 'app/features/provisioning/hooks/usePullRequestParam';
|
||||
import { FolderDTO } from 'app/types/folders';
|
||||
@@ -44,58 +47,60 @@ function FormContent({ initialValues, repository, workflowOptions, folder, onDis
|
||||
});
|
||||
const { handleSubmit, watch, register, formState } = methods;
|
||||
|
||||
const [workflow, ref, title] = watch(['workflow', 'ref', 'title']);
|
||||
const [workflow, title] = watch(['workflow', 'title']);
|
||||
|
||||
// TODO: replace with useProvisionedRequestHandler hook
|
||||
useEffect(() => {
|
||||
const appEvents = getAppEvents();
|
||||
if (request.isSuccess && repository) {
|
||||
onDismiss?.();
|
||||
|
||||
appEvents.publish({
|
||||
type: AppEvents.alertSuccess.name,
|
||||
payload: [
|
||||
t(
|
||||
'browse-dashboards.new-provisioned-folder-form.alert-folder-created-successfully',
|
||||
'Folder created successfully'
|
||||
),
|
||||
],
|
||||
const onBranchSuccess = ({ urls }: { urls?: Record<string, string> }, info: ProvisionedOperationInfo) => {
|
||||
const prUrl = urls?.newPullRequestURL;
|
||||
if (prUrl) {
|
||||
const url = buildResourceBranchRedirectUrl({
|
||||
paramName: 'new_pull_request_url',
|
||||
paramValue: prUrl,
|
||||
repoType: info.repoType,
|
||||
});
|
||||
navigate(url);
|
||||
}
|
||||
};
|
||||
|
||||
const prUrl = request.data?.urls?.newPullRequestURL;
|
||||
if (workflow === 'branch' && prUrl) {
|
||||
const url = buildResourceBranchRedirectUrl({
|
||||
paramName: 'new_pull_request_url',
|
||||
paramValue: prUrl,
|
||||
repoType: request.data?.repository?.type,
|
||||
});
|
||||
navigate(url);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Update when the upsert type is fixed
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const folder = request.data.resource?.upsert as Resource;
|
||||
if (folder?.metadata?.name) {
|
||||
navigate(`/dashboards/f/${folder?.metadata?.name}/`);
|
||||
return;
|
||||
}
|
||||
const onWriteSuccess = (resource: Resource<FolderDTO>) => {
|
||||
// Navigation for new folders (resource-specific concern)
|
||||
if (resource?.metadata?.name) {
|
||||
navigate(`/dashboards/f/${resource.metadata.name}/`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback to provisioning URL
|
||||
if (repository?.name && request.data?.path) {
|
||||
let url = `${PROVISIONING_URL}/${repository.name}/file/${request.data.path}`;
|
||||
if (request.data.ref?.length) {
|
||||
url += '?ref=' + request.data.ref;
|
||||
}
|
||||
navigate(url);
|
||||
} else if (request.isError) {
|
||||
appEvents.publish({
|
||||
type: AppEvents.alertError.name,
|
||||
payload: [
|
||||
t('browse-dashboards.new-provisioned-folder-form.alert-error-creating-folder', 'Error creating folder'),
|
||||
request.error,
|
||||
],
|
||||
});
|
||||
}
|
||||
}, [request.isSuccess, request.isError, request.error, ref, request.data, workflow, navigate, repository, onDismiss]);
|
||||
};
|
||||
|
||||
const onError = (error: unknown) => {
|
||||
getAppEvents().publish({
|
||||
type: AppEvents.alertError.name,
|
||||
payload: [
|
||||
t('browse-dashboards.new-provisioned-folder-form.alert-error-creating-folder', 'Error creating folder'),
|
||||
error,
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
// Use the repository-type and resource-type aware provisioned request handler
|
||||
useProvisionedRequestHandler<FolderDTO>({
|
||||
request,
|
||||
workflow,
|
||||
repository,
|
||||
resourceType: 'folder',
|
||||
handlers: {
|
||||
onDismiss,
|
||||
onBranchSuccess,
|
||||
onWriteSuccess: (_, resource) => onWriteSuccess(resource),
|
||||
onError,
|
||||
},
|
||||
});
|
||||
|
||||
const doSave = async ({ ref, title, workflow, comment }: BaseProvisionedFormData) => {
|
||||
const repoName = repository?.name;
|
||||
|
||||
+35
-29
@@ -18,7 +18,7 @@ import { useCreateOrUpdateRepositoryFile } from 'app/features/provisioning/hooks
|
||||
import { ResourceEditFormSharedFields } from '../../components/Provisioned/ResourceEditFormSharedFields';
|
||||
import { buildResourceBranchRedirectUrl } from '../../settings/utils';
|
||||
import { getDashboardUrl } from '../../utils/getDashboardUrl';
|
||||
import { useProvisionedRequestHandler } from '../../utils/useProvisionedRequestHandler';
|
||||
import { ProvisionedOperationInfo, useProvisionedRequestHandler } from '../../utils/useProvisionedRequestHandler';
|
||||
import { SaveDashboardFormCommonOptions } from '../SaveDashboardForm';
|
||||
import { ProvisionedDashboardFormData } from '../shared';
|
||||
|
||||
@@ -60,25 +60,15 @@ export function SaveProvisionedDashboardForm({
|
||||
reset(defaultValues);
|
||||
}, [defaultValues, reset]);
|
||||
|
||||
const onRequestError = (error: unknown) => {
|
||||
const onRequestError = (error: unknown, info: ProvisionedOperationInfo) => {
|
||||
appEvents.publish({
|
||||
type: AppEvents.alertError.name,
|
||||
payload: [t('dashboard-scene.save-provisioned-dashboard-form.api-error', 'Error saving dashboard'), error],
|
||||
});
|
||||
};
|
||||
|
||||
const onWriteSuccess = () => {
|
||||
panelEditor?.onDiscard();
|
||||
drawer.onClose();
|
||||
locationService.partial({
|
||||
viewPanel: null,
|
||||
editPanel: null,
|
||||
});
|
||||
};
|
||||
|
||||
const onNewDashboardSuccess = (upsert: Resource<Dashboard>) => {
|
||||
panelEditor?.onDiscard();
|
||||
drawer.onClose();
|
||||
const handleNewDashboard = (upsert: Resource<Dashboard>) => {
|
||||
// Navigation for new dashboards
|
||||
const url = locationUtil.assureBaseUrl(
|
||||
getDashboardUrl({
|
||||
uid: upsert.metadata.name,
|
||||
@@ -86,34 +76,50 @@ export function SaveProvisionedDashboardForm({
|
||||
currentQueryParams: window.location.search,
|
||||
})
|
||||
);
|
||||
|
||||
navigate(url);
|
||||
};
|
||||
|
||||
const onBranchSuccess = (ref: string, path: string) => {
|
||||
const onWriteSuccess = (_: ProvisionedOperationInfo, upsert: Resource<Dashboard>) => {
|
||||
if (isNew && upsert?.metadata.name) {
|
||||
handleNewDashboard(upsert);
|
||||
} else {
|
||||
locationService.partial({
|
||||
viewPanel: null,
|
||||
editPanel: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onBranchSuccess = (ref: string, path: string, info: ProvisionedOperationInfo, upsert: Resource<Dashboard>) => {
|
||||
if (isNew && upsert?.metadata?.name) {
|
||||
handleNewDashboard(upsert);
|
||||
} else {
|
||||
const url = buildResourceBranchRedirectUrl({
|
||||
baseUrl: `${PROVISIONING_URL}/${defaultValues.repo}/dashboard/preview/${path}`,
|
||||
paramName: 'ref',
|
||||
paramValue: ref,
|
||||
repoType: info.repoType,
|
||||
});
|
||||
navigate(url);
|
||||
}
|
||||
};
|
||||
|
||||
const onDismiss = () => {
|
||||
dashboard.setState({ isDirty: false });
|
||||
panelEditor?.onDiscard();
|
||||
drawer.onClose();
|
||||
|
||||
const url = buildResourceBranchRedirectUrl({
|
||||
baseUrl: `${PROVISIONING_URL}/${defaultValues.repo}/dashboard/preview/${path}`,
|
||||
paramName: 'ref',
|
||||
paramValue: ref,
|
||||
repoType: request.data?.repository?.type,
|
||||
});
|
||||
navigate(url);
|
||||
};
|
||||
|
||||
useProvisionedRequestHandler({
|
||||
dashboard,
|
||||
useProvisionedRequestHandler<Dashboard>({
|
||||
request,
|
||||
workflow,
|
||||
resourceType: 'dashboard',
|
||||
handlers: {
|
||||
onBranchSuccess: ({ ref, path }) => onBranchSuccess(ref, path),
|
||||
onBranchSuccess: ({ ref, path }, info, resource) => onBranchSuccess(ref, path, info, resource),
|
||||
onWriteSuccess,
|
||||
onNewDashboardSuccess,
|
||||
onError: onRequestError,
|
||||
onDismiss,
|
||||
},
|
||||
isNew,
|
||||
});
|
||||
|
||||
// Submit handler for saving the form data
|
||||
|
||||
@@ -11,7 +11,7 @@ import { PROVISIONING_URL } from 'app/features/provisioning/constants';
|
||||
import { ResourceEditFormSharedFields } from '../components/Provisioned/ResourceEditFormSharedFields';
|
||||
import { ProvisionedDashboardFormData } from '../saving/shared';
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { useProvisionedRequestHandler } from '../utils/useProvisionedRequestHandler';
|
||||
import { useProvisionedRequestHandler, ProvisionedOperationInfo } from '../utils/useProvisionedRequestHandler';
|
||||
|
||||
import { buildResourceBranchRedirectUrl } from './utils';
|
||||
|
||||
@@ -67,7 +67,7 @@ export function DeleteProvisionedDashboardForm({
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const onRequestError = (error: unknown) => {
|
||||
const onError = (error: unknown) => {
|
||||
getAppEvents().publish({
|
||||
type: AppEvents.alertError.name,
|
||||
payload: [t('dashboard-scene.delete-provisioned-dashboard-form.api-error', 'Failed to delete dashboard'), error],
|
||||
@@ -75,32 +75,36 @@ export function DeleteProvisionedDashboardForm({
|
||||
};
|
||||
|
||||
const onWriteSuccess = () => {
|
||||
dashboard.setState({ isDirty: false });
|
||||
panelEditor?.onDiscard();
|
||||
onDismiss();
|
||||
// TODO reset search state instead
|
||||
window.location.href = '/dashboards';
|
||||
};
|
||||
|
||||
const onBranchSuccess = (path: string, urls?: Record<string, string>) => {
|
||||
const onBranchSuccess = (path: string, info: ProvisionedOperationInfo, urls?: Record<string, string>) => {
|
||||
panelEditor?.onDiscard();
|
||||
onDismiss();
|
||||
const url = buildResourceBranchRedirectUrl({
|
||||
baseUrl: `${PROVISIONING_URL}/${defaultValues.repo}/dashboard/preview/${path}`,
|
||||
paramName: 'pull_request_url',
|
||||
paramValue: urls?.newPullRequestURL,
|
||||
repoType: request.data?.repository?.type,
|
||||
repoType: info.repoType,
|
||||
});
|
||||
navigate(url);
|
||||
};
|
||||
|
||||
useProvisionedRequestHandler({
|
||||
dashboard,
|
||||
request,
|
||||
workflow,
|
||||
resourceType: 'dashboard',
|
||||
successMessage: t(
|
||||
'dashboard-scene.delete-provisioned-dashboard-form.success-message',
|
||||
'Dashboard deleted successfully'
|
||||
),
|
||||
handlers: {
|
||||
onBranchSuccess: ({ path, urls }) => onBranchSuccess(path, urls),
|
||||
onDismiss,
|
||||
onBranchSuccess: ({ path, urls }, info) => onBranchSuccess(path, info, urls),
|
||||
onWriteSuccess,
|
||||
onError: onRequestError,
|
||||
onError,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { getTargetFolderPathInRepo } from 'app/features/browse-dashboards/compon
|
||||
import { ResourceEditFormSharedFields } from '../components/Provisioned/ResourceEditFormSharedFields';
|
||||
import { ProvisionedDashboardFormData } from '../saving/shared';
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { useProvisionedRequestHandler } from '../utils/useProvisionedRequestHandler';
|
||||
import { useProvisionedRequestHandler, ProvisionedOperationInfo } from '../utils/useProvisionedRequestHandler';
|
||||
|
||||
import { buildResourceBranchRedirectUrl } from './utils';
|
||||
|
||||
@@ -118,6 +118,7 @@ export function MoveProvisionedDashboardForm({
|
||||
};
|
||||
|
||||
const onWriteSuccess = () => {
|
||||
dashboard.setState({ isDirty: false });
|
||||
panelEditor?.onDiscard();
|
||||
if (targetFolderUID && targetFolderTitle) {
|
||||
onSuccess(targetFolderUID, targetFolderTitle);
|
||||
@@ -125,23 +126,40 @@ export function MoveProvisionedDashboardForm({
|
||||
navigate('/dashboards');
|
||||
};
|
||||
|
||||
const onBranchSuccess = () => {
|
||||
const onBranchSuccess = (info: ProvisionedOperationInfo) => {
|
||||
dashboard.setState({ isDirty: false });
|
||||
panelEditor?.onDiscard();
|
||||
const url = buildResourceBranchRedirectUrl({
|
||||
paramName: 'new_pull_request_url',
|
||||
paramValue: moveRequest?.data?.urls?.newPullRequestURL,
|
||||
repoType: moveRequest?.data?.repository?.type,
|
||||
repoType: info.repoType,
|
||||
});
|
||||
navigate(url);
|
||||
};
|
||||
|
||||
const onError = (error: unknown) => {
|
||||
getAppEvents().publish({
|
||||
type: AppEvents.alertError.name,
|
||||
payload: [
|
||||
t('dashboard-scene.move-provisioned-dashboard-form.alert-error-moving-dashboard', 'Error moving dashboard'),
|
||||
error,
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
useProvisionedRequestHandler({
|
||||
dashboard,
|
||||
request: moveRequest,
|
||||
workflow,
|
||||
successMessage: t(
|
||||
'dashboard-scene.move-provisioned-dashboard-form.success-message',
|
||||
'Dashboard moved successfully'
|
||||
),
|
||||
resourceType: 'dashboard',
|
||||
handlers: {
|
||||
onBranchSuccess,
|
||||
onBranchSuccess: (_, info) => onBranchSuccess(info),
|
||||
onWriteSuccess,
|
||||
onDismiss,
|
||||
onError,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -3,18 +3,10 @@ import { renderHook } from '@testing-library/react';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { getAppEvents } from '@grafana/runtime';
|
||||
import { Dashboard } from '@grafana/schema';
|
||||
import {
|
||||
DeleteRepositoryFilesWithPathApiResponse,
|
||||
GetRepositoryFilesWithPathApiResponse,
|
||||
ResourceWrapper,
|
||||
} from 'app/api/clients/provisioning/v0alpha1';
|
||||
import { Resource } from 'app/features/apiserver/types';
|
||||
import { ResourceWrapper } from 'app/api/clients/provisioning/v0alpha1';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { useProvisionedRequestHandler, RequestHandlers } from './useProvisionedRequestHandler';
|
||||
|
||||
import { useProvisionedRequestHandler } from './useProvisionedRequestHandler';
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
getAppEvents: jest.fn(),
|
||||
}));
|
||||
@@ -30,9 +22,9 @@ describe('useProvisionedRequestHandler', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('when request has an error', () => {
|
||||
it('should call onError handler', () => {
|
||||
const { request, handlers, dashboard } = setup({
|
||||
describe('error handling', () => {
|
||||
it('should call onError handler with correct parameters', () => {
|
||||
const { request, handlers } = setup({
|
||||
requestOverrides: {
|
||||
isError: true,
|
||||
isSuccess: false,
|
||||
@@ -42,264 +34,127 @@ describe('useProvisionedRequestHandler', () => {
|
||||
|
||||
renderHook(() =>
|
||||
useProvisionedRequestHandler({
|
||||
dashboard,
|
||||
request,
|
||||
repository: {
|
||||
type: 'github',
|
||||
name: 'test-repo',
|
||||
target: 'folder',
|
||||
title: 'Test Repository',
|
||||
workflows: [],
|
||||
},
|
||||
resourceType: 'dashboard',
|
||||
handlers,
|
||||
})
|
||||
);
|
||||
|
||||
expect(handlers.onError).toHaveBeenCalledWith(new Error('Test error'));
|
||||
expect(handlers.onError).toHaveBeenCalledWith(
|
||||
new Error('Test error'),
|
||||
expect.objectContaining({
|
||||
resourceType: 'dashboard',
|
||||
repoType: 'github',
|
||||
})
|
||||
);
|
||||
expect(handlers.onBranchSuccess).not.toHaveBeenCalled();
|
||||
expect(handlers.onWriteSuccess).not.toHaveBeenCalled();
|
||||
expect(handlers.onNewDashboardSuccess).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when request is successful', () => {
|
||||
it('should set dashboard isDirty to false', () => {
|
||||
const { request, handlers, dashboard } = setup({
|
||||
describe('success handling', () => {
|
||||
it('should publish success event and call onDismiss', () => {
|
||||
const { request, handlers, mockPublish } = setup({
|
||||
requestOverrides: {
|
||||
isError: false,
|
||||
isSuccess: true,
|
||||
data: {
|
||||
ref: 'main',
|
||||
path: '/path/to/dashboard',
|
||||
},
|
||||
},
|
||||
workflowOverride: 'branch',
|
||||
});
|
||||
|
||||
renderHook(() =>
|
||||
useProvisionedRequestHandler({
|
||||
dashboard,
|
||||
request,
|
||||
workflow: 'branch',
|
||||
handlers,
|
||||
})
|
||||
);
|
||||
|
||||
expect(dashboard.setState).toHaveBeenCalledWith({ isDirty: false });
|
||||
});
|
||||
|
||||
it('should publish success event', () => {
|
||||
const { request, handlers, dashboard, mockPublish } = setup({
|
||||
requestOverrides: {
|
||||
isError: false,
|
||||
isSuccess: true,
|
||||
data: {},
|
||||
data: createMockResourceWrapper(),
|
||||
},
|
||||
});
|
||||
|
||||
renderHook(() =>
|
||||
useProvisionedRequestHandler({
|
||||
dashboard,
|
||||
request,
|
||||
resourceType: 'dashboard',
|
||||
handlers,
|
||||
})
|
||||
);
|
||||
|
||||
expect(mockPublish).toHaveBeenCalledWith({
|
||||
type: AppEvents.alertSuccess.name,
|
||||
payload: ['Dashboard changes saved successfully'],
|
||||
payload: ['Dashboard saved successfully'],
|
||||
});
|
||||
expect(handlers.onDismiss).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('branch workflow', () => {
|
||||
it('should call onBranchSuccess when workflow is branch and data has ref and path', () => {
|
||||
const { request, handlers, dashboard } = setup({
|
||||
requestOverrides: {
|
||||
isError: false,
|
||||
isSuccess: true,
|
||||
data: {
|
||||
ref: 'feature-branch',
|
||||
path: '/path/to/dashboard.json',
|
||||
urls: { compareURL: 'http://example.com/edit' },
|
||||
},
|
||||
},
|
||||
workflowOverride: 'branch',
|
||||
});
|
||||
it('should call onBranchSuccess for branch workflow', () => {
|
||||
const { request, handlers } = setup({
|
||||
requestOverrides: {
|
||||
isError: false,
|
||||
isSuccess: true,
|
||||
data: createMockResourceWrapper({
|
||||
ref: 'feature-branch',
|
||||
path: '/path/to/dashboard.json',
|
||||
urls: { compareURL: 'http://example.com/edit' },
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
renderHook(() =>
|
||||
useProvisionedRequestHandler({
|
||||
dashboard,
|
||||
request,
|
||||
workflow: 'branch',
|
||||
handlers,
|
||||
})
|
||||
);
|
||||
renderHook(() =>
|
||||
useProvisionedRequestHandler({
|
||||
request,
|
||||
workflow: 'branch',
|
||||
resourceType: 'dashboard',
|
||||
handlers,
|
||||
})
|
||||
);
|
||||
|
||||
expect(handlers.onBranchSuccess).toHaveBeenCalledWith({
|
||||
expect(handlers.onBranchSuccess).toHaveBeenCalledWith(
|
||||
{
|
||||
ref: 'feature-branch',
|
||||
path: '/path/to/dashboard.json',
|
||||
urls: { compareURL: 'http://example.com/edit' },
|
||||
});
|
||||
expect(handlers.onWriteSuccess).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call onBranchSuccess when ref is missing', () => {
|
||||
const { request, handlers, dashboard } = setup({
|
||||
requestOverrides: {
|
||||
isError: false,
|
||||
isSuccess: true,
|
||||
data: {
|
||||
path: '/path/to/dashboard.json',
|
||||
},
|
||||
},
|
||||
workflowOverride: 'branch',
|
||||
});
|
||||
|
||||
renderHook(() =>
|
||||
useProvisionedRequestHandler({
|
||||
dashboard,
|
||||
request,
|
||||
workflow: 'branch',
|
||||
handlers,
|
||||
})
|
||||
);
|
||||
|
||||
expect(handlers.onBranchSuccess).not.toHaveBeenCalled();
|
||||
expect(handlers.onWriteSuccess).toHaveBeenCalled();
|
||||
});
|
||||
},
|
||||
expect.objectContaining({
|
||||
resourceType: 'dashboard',
|
||||
repoType: 'git',
|
||||
workflow: 'branch',
|
||||
}),
|
||||
expect.any(Object)
|
||||
);
|
||||
expect(handlers.onWriteSuccess).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('new dashboard flow', () => {
|
||||
it('should call onNewDashboardSuccess when isNew is true and resource.upsert exists', () => {
|
||||
const mockUpsertResource = {
|
||||
metadata: {
|
||||
name: 'test-dashboard',
|
||||
uid: 'test-uid',
|
||||
resourceVersion: '1',
|
||||
creationTimestamp: new Date().toISOString(),
|
||||
},
|
||||
spec: { title: 'Test Dashboard' } as Dashboard,
|
||||
apiVersion: 'v1',
|
||||
kind: 'Dashboard',
|
||||
};
|
||||
|
||||
const mockResource = {
|
||||
metadata: {
|
||||
name: 'test-dashboard',
|
||||
uid: 'test-uid',
|
||||
resourceVersion: '1',
|
||||
creationTimestamp: new Date().toISOString(),
|
||||
},
|
||||
spec: { title: 'Test Dashboard' } as Dashboard,
|
||||
apiVersion: 'v1',
|
||||
kind: 'Dashboard',
|
||||
upsert: mockUpsertResource,
|
||||
} as Resource<Dashboard> & { upsert: Resource<Dashboard> };
|
||||
|
||||
const { request, handlers, dashboard } = setup({
|
||||
requestOverrides: {
|
||||
isError: false,
|
||||
isSuccess: true,
|
||||
data: {
|
||||
repository: 'test-repo',
|
||||
resource: mockResource,
|
||||
} as unknown as ProvisionedRequestData,
|
||||
},
|
||||
});
|
||||
|
||||
renderHook(() =>
|
||||
useProvisionedRequestHandler({
|
||||
dashboard,
|
||||
request,
|
||||
handlers,
|
||||
isNew: true,
|
||||
})
|
||||
);
|
||||
|
||||
expect(handlers.onNewDashboardSuccess).toHaveBeenCalledWith(mockResource.upsert);
|
||||
expect(handlers.onWriteSuccess).not.toHaveBeenCalled();
|
||||
it('should call onWriteSuccess for write workflow', () => {
|
||||
const { request, handlers } = setup({
|
||||
requestOverrides: {
|
||||
isError: false,
|
||||
isSuccess: true,
|
||||
data: createMockResourceWrapper(),
|
||||
},
|
||||
});
|
||||
|
||||
it('should not call onNewDashboardSuccess when isNew is false', () => {
|
||||
const { request, handlers, dashboard } = setup({
|
||||
requestOverrides: {
|
||||
isError: false,
|
||||
isSuccess: true,
|
||||
data: {
|
||||
repository: 'test-repo',
|
||||
resource: {
|
||||
upsert: {
|
||||
apiVersion: 'v1',
|
||||
kind: 'Dashboard',
|
||||
metadata: { name: 'test-dashboard' },
|
||||
spec: { title: 'Test Dashboard' } as Dashboard,
|
||||
},
|
||||
metadata: { name: 'test-dashboard' },
|
||||
spec: { title: 'Test Dashboard' } as Dashboard,
|
||||
apiVersion: 'v1',
|
||||
kind: 'Dashboard',
|
||||
} as unknown as Resource<Dashboard>,
|
||||
} as unknown as ProvisionedRequestData,
|
||||
},
|
||||
});
|
||||
renderHook(() =>
|
||||
useProvisionedRequestHandler({
|
||||
request,
|
||||
workflow: 'write',
|
||||
resourceType: 'dashboard',
|
||||
handlers,
|
||||
})
|
||||
);
|
||||
|
||||
renderHook(() =>
|
||||
useProvisionedRequestHandler({
|
||||
dashboard,
|
||||
request,
|
||||
handlers,
|
||||
isNew: false,
|
||||
})
|
||||
);
|
||||
|
||||
expect(handlers.onNewDashboardSuccess).not.toHaveBeenCalled();
|
||||
expect(handlers.onWriteSuccess).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call onNewDashboardSuccess when resource.upsert is missing', () => {
|
||||
const { request, handlers, dashboard } = setup({
|
||||
requestOverrides: {
|
||||
isError: false,
|
||||
isSuccess: true,
|
||||
data: {
|
||||
resource: {},
|
||||
} as ResourceWrapper,
|
||||
},
|
||||
});
|
||||
|
||||
renderHook(() =>
|
||||
useProvisionedRequestHandler({
|
||||
dashboard,
|
||||
request,
|
||||
handlers,
|
||||
isNew: true,
|
||||
})
|
||||
);
|
||||
|
||||
expect(handlers.onNewDashboardSuccess).not.toHaveBeenCalled();
|
||||
expect(handlers.onWriteSuccess).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('write workflow', () => {
|
||||
it('should call onWriteSuccess as fallback', () => {
|
||||
const { request, handlers, dashboard } = setup({
|
||||
requestOverrides: {
|
||||
isError: false,
|
||||
isSuccess: true,
|
||||
data: {} as GetRepositoryFilesWithPathApiResponse,
|
||||
},
|
||||
});
|
||||
|
||||
renderHook(() =>
|
||||
useProvisionedRequestHandler({
|
||||
dashboard,
|
||||
request,
|
||||
handlers,
|
||||
})
|
||||
);
|
||||
|
||||
expect(handlers.onWriteSuccess).toHaveBeenCalled();
|
||||
});
|
||||
expect(handlers.onWriteSuccess).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
resourceType: 'dashboard',
|
||||
repoType: 'git',
|
||||
workflow: 'write',
|
||||
}),
|
||||
expect.any(Object)
|
||||
);
|
||||
expect(handlers.onDismiss).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when request is neither error nor success', () => {
|
||||
it('should not call any handlers', () => {
|
||||
const { request, handlers, dashboard, mockPublish } = setup({
|
||||
describe('edge cases', () => {
|
||||
it('should not call any handlers when request is loading', () => {
|
||||
const { request, handlers, mockPublish } = setup({
|
||||
requestOverrides: {
|
||||
isError: false,
|
||||
isSuccess: false,
|
||||
@@ -309,7 +164,6 @@ describe('useProvisionedRequestHandler', () => {
|
||||
|
||||
renderHook(() =>
|
||||
useProvisionedRequestHandler({
|
||||
dashboard,
|
||||
request,
|
||||
handlers,
|
||||
})
|
||||
@@ -318,15 +172,11 @@ describe('useProvisionedRequestHandler', () => {
|
||||
expect(handlers.onError).not.toHaveBeenCalled();
|
||||
expect(handlers.onBranchSuccess).not.toHaveBeenCalled();
|
||||
expect(handlers.onWriteSuccess).not.toHaveBeenCalled();
|
||||
expect(handlers.onNewDashboardSuccess).not.toHaveBeenCalled();
|
||||
expect(dashboard.setState).not.toHaveBeenCalled();
|
||||
expect(mockPublish).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when request success but no data', () => {
|
||||
it('should not call any handlers when data is undefined', () => {
|
||||
const { request, handlers, dashboard, mockPublish } = setup({
|
||||
it('should not call handlers when success but no data', () => {
|
||||
const { request, handlers, mockPublish } = setup({
|
||||
requestOverrides: {
|
||||
isError: false,
|
||||
isSuccess: true,
|
||||
@@ -336,7 +186,6 @@ describe('useProvisionedRequestHandler', () => {
|
||||
|
||||
renderHook(() =>
|
||||
useProvisionedRequestHandler({
|
||||
dashboard,
|
||||
request,
|
||||
handlers,
|
||||
})
|
||||
@@ -344,25 +193,18 @@ describe('useProvisionedRequestHandler', () => {
|
||||
|
||||
expect(handlers.onWriteSuccess).not.toHaveBeenCalled();
|
||||
expect(handlers.onBranchSuccess).not.toHaveBeenCalled();
|
||||
expect(dashboard.setState).not.toHaveBeenCalled();
|
||||
expect(mockPublish).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('optional handlers', () => {
|
||||
it('should not throw when optional handlers are not provided', () => {
|
||||
const { request, dashboard } = setup({
|
||||
requestOverrides: {
|
||||
isError: false,
|
||||
isSuccess: true,
|
||||
},
|
||||
const { request } = setup({
|
||||
requestOverrides: { isError: false, isSuccess: true },
|
||||
handlersOverrides: {},
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
renderHook(() =>
|
||||
useProvisionedRequestHandler({
|
||||
dashboard,
|
||||
request,
|
||||
handlers: {},
|
||||
})
|
||||
@@ -372,62 +214,69 @@ describe('useProvisionedRequestHandler', () => {
|
||||
});
|
||||
});
|
||||
|
||||
type ProvisionedRequestData = DeleteRepositoryFilesWithPathApiResponse | GetRepositoryFilesWithPathApiResponse;
|
||||
// Helper function to create a properly structured mock ResourceWrapper
|
||||
function createMockResourceWrapper(overrides: Partial<ResourceWrapper> = {}): ResourceWrapper {
|
||||
return {
|
||||
repository: {
|
||||
name: 'test-repo',
|
||||
namespace: 'default',
|
||||
title: 'Test Repository',
|
||||
type: 'git',
|
||||
},
|
||||
resource: {
|
||||
type: {
|
||||
kind: 'Dashboard',
|
||||
},
|
||||
upsert: {
|
||||
apiVersion: 'v1',
|
||||
kind: 'Dashboard',
|
||||
metadata: { name: 'test-dashboard', uid: 'test-uid' },
|
||||
spec: { title: 'Test Dashboard' },
|
||||
},
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function setup({
|
||||
requestOverrides = {},
|
||||
handlersOverrides = {},
|
||||
workflowOverride,
|
||||
}: {
|
||||
requestOverrides?: Partial<{
|
||||
isError: boolean;
|
||||
isSuccess: boolean;
|
||||
isLoading?: boolean;
|
||||
error?: unknown;
|
||||
data?: Partial<ProvisionedRequestData>;
|
||||
data?: ResourceWrapper;
|
||||
}>;
|
||||
handlersOverrides?: Partial<{
|
||||
onBranchSuccess?: jest.Mock;
|
||||
onWriteSuccess?: jest.Mock;
|
||||
onNewDashboardSuccess?: jest.Mock;
|
||||
onError?: jest.Mock;
|
||||
}>;
|
||||
workflowOverride?: string;
|
||||
handlersOverrides?: Partial<RequestHandlers<Dashboard>>;
|
||||
} = {}) {
|
||||
const mockPublish = jest.fn();
|
||||
const mockSetState = jest.fn();
|
||||
|
||||
mockGetAppEvents.mockReturnValue({
|
||||
publish: mockPublish,
|
||||
} as unknown as ReturnType<typeof getAppEvents>);
|
||||
|
||||
const dashboard = {
|
||||
setState: mockSetState,
|
||||
} as unknown as DashboardScene;
|
||||
|
||||
const request = {
|
||||
isError: false,
|
||||
isSuccess: false,
|
||||
isLoading: false,
|
||||
error: undefined,
|
||||
data: undefined,
|
||||
...(requestOverrides as ResourceWrapper),
|
||||
...requestOverrides,
|
||||
};
|
||||
|
||||
const handlers = {
|
||||
const handlers: RequestHandlers<Dashboard> = {
|
||||
onError: jest.fn(),
|
||||
onBranchSuccess: jest.fn(),
|
||||
onWriteSuccess: jest.fn(),
|
||||
onNewDashboardSuccess: jest.fn(),
|
||||
onDismiss: jest.fn(),
|
||||
...handlersOverrides,
|
||||
};
|
||||
|
||||
return {
|
||||
dashboard,
|
||||
request,
|
||||
handlers,
|
||||
mockPublish,
|
||||
mockSetState,
|
||||
workflow: workflowOverride,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,20 +3,32 @@ import { useEffect } from 'react';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { getAppEvents } from '@grafana/runtime';
|
||||
import { Dashboard } from '@grafana/schema';
|
||||
import {
|
||||
DeleteRepositoryFilesWithPathApiResponse,
|
||||
GetRepositoryFilesWithPathApiResponse,
|
||||
RepositoryView,
|
||||
} from 'app/api/clients/provisioning/v0alpha1';
|
||||
import { Resource } from 'app/features/apiserver/types';
|
||||
import { RepoType } from 'app/features/provisioning/Wizard/types';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
type ResourceType = 'dashboard' | 'folder'; // Add more as needed, e.g., 'alert', etc.
|
||||
|
||||
interface RequestHandlers {
|
||||
onBranchSuccess?: (data: { ref: string; path: string; urls?: Record<string, string> }) => void;
|
||||
onWriteSuccess?: () => void;
|
||||
onNewDashboardSuccess?: (resource: Resource<Dashboard>) => void;
|
||||
onError?: (error: unknown) => void;
|
||||
// Information object that gets passed to all handlers
|
||||
interface ProvisionedOperationInfo {
|
||||
repoType: RepoType;
|
||||
resourceType?: ResourceType;
|
||||
workflow?: string;
|
||||
}
|
||||
|
||||
interface RequestHandlers<T> {
|
||||
onBranchSuccess?: (
|
||||
data: { ref: string; path: string; urls?: Record<string, string> },
|
||||
info: ProvisionedOperationInfo,
|
||||
resource: Resource<T>
|
||||
) => void;
|
||||
onWriteSuccess?: (info: ProvisionedOperationInfo, resource: Resource<T>) => void;
|
||||
onError?: (error: unknown, info: ProvisionedOperationInfo) => void;
|
||||
onDismiss?: () => void;
|
||||
}
|
||||
|
||||
interface ProvisionedRequest {
|
||||
@@ -27,51 +39,85 @@ interface ProvisionedRequest {
|
||||
data?: DeleteRepositoryFilesWithPathApiResponse | GetRepositoryFilesWithPathApiResponse;
|
||||
}
|
||||
|
||||
// This hook handles save new dashboard, edit existing dashboard, and delete dashboard response logic for provisioned dashboards.
|
||||
export function useProvisionedRequestHandler({
|
||||
dashboard,
|
||||
// Resource-specific configuration for different resource types
|
||||
interface ResourceConfig {
|
||||
defaultSuccessMessage: string;
|
||||
supportedWorkflows: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic hook for handling provisioned resource operations across any resource type and repository provider.
|
||||
*
|
||||
* This hook is intentionally decoupled from specific components (like DashboardScene) to promote reusability.
|
||||
* Components are responsible for their own state management through specific workflow handlers.
|
||||
*/
|
||||
export function useProvisionedRequestHandler<T>({
|
||||
request,
|
||||
workflow,
|
||||
handlers,
|
||||
isNew,
|
||||
successMessage,
|
||||
repository,
|
||||
resourceType,
|
||||
}: {
|
||||
dashboard: DashboardScene;
|
||||
request: ProvisionedRequest;
|
||||
workflow?: string;
|
||||
handlers: RequestHandlers;
|
||||
isNew?: boolean;
|
||||
handlers: RequestHandlers<T>;
|
||||
successMessage?: string;
|
||||
repository?: RepositoryView;
|
||||
resourceType?: ResourceType;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
const repoType = repository?.type || 'git';
|
||||
const info: ProvisionedOperationInfo = {
|
||||
repoType,
|
||||
resourceType,
|
||||
workflow,
|
||||
};
|
||||
|
||||
if (request.isError) {
|
||||
handlers.onError?.(request.error);
|
||||
handlers.onError?.(request.error, info);
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.isSuccess && request.data) {
|
||||
dashboard.setState({ isDirty: false });
|
||||
const { ref, path, urls, resource } = request.data;
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const resourceData = resource.upsert as Resource<T>;
|
||||
|
||||
// Branch workflow
|
||||
if (workflow === 'branch' && ref && path) {
|
||||
handlers.onBranchSuccess?.({ ref, path, urls });
|
||||
return;
|
||||
}
|
||||
|
||||
// Success message (could be configurable)
|
||||
// Success message
|
||||
const message = successMessage || getContextualSuccessMessage(info);
|
||||
getAppEvents().publish({
|
||||
type: AppEvents.alertSuccess.name,
|
||||
payload: [t('dashboard-scene.edit-provisioned-dashboard-form.success', 'Dashboard changes saved successfully')],
|
||||
payload: [message],
|
||||
});
|
||||
|
||||
// New dashboard flow
|
||||
if (isNew && resource?.upsert && handlers.onNewDashboardSuccess) {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
handlers.onNewDashboardSuccess(resource.upsert as Resource<Dashboard>);
|
||||
return;
|
||||
// Branch workflow
|
||||
if (workflow === 'branch' && handlers.onBranchSuccess && ref && path) {
|
||||
const branchData = { ref, path, urls };
|
||||
handlers.onBranchSuccess?.(branchData, info, resourceData);
|
||||
}
|
||||
|
||||
// Write workflow
|
||||
handlers.onWriteSuccess?.();
|
||||
if (workflow === 'write' && handlers.onWriteSuccess) {
|
||||
handlers.onWriteSuccess(info, resourceData);
|
||||
}
|
||||
|
||||
handlers.onDismiss?.();
|
||||
}
|
||||
}, [request, workflow, handlers, isNew, dashboard]);
|
||||
}, [request, workflow, handlers, successMessage, repository, resourceType]);
|
||||
}
|
||||
|
||||
function getContextualSuccessMessage(info: ProvisionedOperationInfo): string {
|
||||
const { resourceType } = info;
|
||||
|
||||
switch (resourceType) {
|
||||
case 'dashboard':
|
||||
return t('provisioned-resource-request-handler-dashboard', 'Dashboard saved successfully');
|
||||
case 'folder':
|
||||
return t('provisioned-resource-request-handler-folder', 'Folder created successfully');
|
||||
default:
|
||||
return t('provisioned-resource-request-handler', 'Resource saved successfully');
|
||||
}
|
||||
}
|
||||
|
||||
export type { ResourceType, ProvisionedOperationInfo, RequestHandlers, ResourceConfig };
|
||||
|
||||
@@ -3563,12 +3563,12 @@
|
||||
"tags-column": "Tags"
|
||||
},
|
||||
"delete-provisioned-folder-form": {
|
||||
"alert-folder-deleted-successfully": "Folder deleted successfully",
|
||||
"api-error": "Failed to delete folder",
|
||||
"button-cancel": "Cancel",
|
||||
"button-delete": "Delete",
|
||||
"button-deleting": "Deleting...",
|
||||
"delete-warning": "This will delete this folder and all its descendants. In total, this will affect:"
|
||||
"delete-warning": "This will delete this folder and all its descendants. In total, this will affect:",
|
||||
"success-message": "Folder deleted successfully"
|
||||
},
|
||||
"descendant-count": {
|
||||
"title-unable-to-retrieve-descendant-information": "Unable to retrieve descendant information"
|
||||
@@ -3611,7 +3611,6 @@
|
||||
},
|
||||
"new-provisioned-folder-form": {
|
||||
"alert-error-creating-folder": "Error creating folder",
|
||||
"alert-folder-created-successfully": "Folder created successfully",
|
||||
"button-create": "Create",
|
||||
"button-creating": "Creating...",
|
||||
"cancel": "Cancel",
|
||||
@@ -5645,6 +5644,7 @@
|
||||
"delete-read-only-file-message": "This dashboard cannot be deleted directly from Grafana because the repository is read-only. To delete this dashboard, please remove the file from your Git repository.",
|
||||
"deleting": "Deleting...",
|
||||
"drawer-title": "Delete Provisioned Dashboard",
|
||||
"success-message": "Dashboard deleted successfully",
|
||||
"title-this-repository-is-read-only": "This repository is read only"
|
||||
},
|
||||
"description-label": {
|
||||
@@ -5657,9 +5657,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit-provisioned-dashboard-form": {
|
||||
"success": "Dashboard changes saved successfully"
|
||||
},
|
||||
"email-list": {
|
||||
"aria-label-emailmenu": "Toggle email menu"
|
||||
},
|
||||
@@ -5818,6 +5815,7 @@
|
||||
"usage-count_other": "Used on {{count}} dashboards"
|
||||
},
|
||||
"move-provisioned-dashboard-form": {
|
||||
"alert-error-moving-dashboard": "Error moving dashboard",
|
||||
"api-error": "Failed to move dashboard",
|
||||
"cancel-action": "Cancel",
|
||||
"current-file-not-found": "Current dashboard file could not be found",
|
||||
@@ -5827,6 +5825,7 @@
|
||||
"move-action": "Move dashboard",
|
||||
"move-read-only-message": "This dashboard cannot be moved directly from Grafana because the repository is read-only. To move this dashboard, please move the file in your Git repository.",
|
||||
"moving": "Moving...",
|
||||
"success-message": "Dashboard moved successfully",
|
||||
"target-path-label": "Target path",
|
||||
"title-this-repository-is-read-only": "This repository is read only"
|
||||
},
|
||||
@@ -11015,6 +11014,9 @@
|
||||
"title-created-branch-in-repo": "A new resource has been created in a branch in {{repoType}}.",
|
||||
"title-loaded-pull-request-in-repo": "This resource is loaded from the branch you just created in {{repoType}} and it is only visible to you"
|
||||
},
|
||||
"provisioned-resource-request-handler": "Resource saved successfully",
|
||||
"provisioned-resource-request-handler-dashboard": "Dashboard saved successfully",
|
||||
"provisioned-resource-request-handler-folder": "Folder created successfully",
|
||||
"provisioning": {
|
||||
"banner": {
|
||||
"message": "This feature is currently under active development. For the best experience and latest improvements, we recommend using the <2>nightly build</2> of Grafana."
|
||||
|
||||
Reference in New Issue
Block a user