Provisioning: Centralise app notifications (#103290)

* Provisioning: Centralise app event notifications

* Remove test started notification

* Remove unused import
This commit is contained in:
Alex Khomenko
2025-04-03 12:34:38 +03:00
committed by GitHub
parent feed071815
commit 1ef4a1a4ab
8 changed files with 156 additions and 104 deletions
+149 -13
View File
@@ -1,3 +1,7 @@
import { notifyApp } from 'app/core/actions';
import { createSuccessNotification, createErrorNotification } from 'app/core/copy/appNotification';
import { t } from 'app/core/internationalization';
import {
generatedAPI,
JobSpec,
@@ -13,26 +17,158 @@ import { createOnCacheEntryAdded } from './utils/createOnCacheEntryAdded';
export const provisioningAPI = generatedAPI.enhanceEndpoints({
endpoints: {
listJob(endpoint) {
listJob: {
// Do not include 'watch' in the first query, so we can get the initial list of jobs
// and then start watching for changes
endpoint.query = ({ watch, ...queryArg }) => ({
query: ({ watch, ...queryArg }) => ({
url: `/jobs`,
params: queryArg,
});
endpoint.onCacheEntryAdded = createOnCacheEntryAdded<JobSpec, JobStatus, Job, JobList>('jobs');
}),
onCacheEntryAdded: createOnCacheEntryAdded<JobSpec, JobStatus, Job, JobList>('jobs'),
},
listRepository(endpoint) {
endpoint.query = ({ watch, ...queryArg }) => ({
listRepository: {
query: ({ watch, ...queryArg }) => ({
url: `/repositories`,
params: queryArg,
});
endpoint.onCacheEntryAdded = createOnCacheEntryAdded<
RepositorySpec,
RepositoryStatus,
Repository,
RepositoryList
>('repositories');
}),
onCacheEntryAdded: createOnCacheEntryAdded<RepositorySpec, RepositoryStatus, Repository, RepositoryList>(
'repositories'
),
},
deleteRepository: {
onQueryStarted: async (_, { queryFulfilled, dispatch }) => {
try {
await queryFulfilled;
dispatch(
notifyApp(
createSuccessNotification(
t(
'provisioning.delete-repository-button.success-repository-deleted',
'Repository settings queued for deletion'
)
)
)
);
} catch (e) {
if (e instanceof Error) {
dispatch(
notifyApp(
createErrorNotification(
t('provisioning.delete-repository-button.error-repository-delete', 'Failed to delete repository'),
e
)
)
);
}
}
},
},
deletecollectionRepository: {
onQueryStarted: async (_, { queryFulfilled, dispatch }) => {
try {
await queryFulfilled;
dispatch(
notifyApp(
createSuccessNotification(
t('provisioning.home-page.success-all-repositories-deleted', 'All configured repositories deleted')
)
)
);
} catch (e) {
if (e instanceof Error) {
dispatch(
notifyApp(
createErrorNotification(
t('provisioning.home-page.error-delete-all-repositories', 'Failed to delete all repositories'),
e
)
)
);
}
}
},
},
createRepositoryTest: {
onQueryStarted: async (_, { queryFulfilled, dispatch }) => {
try {
await queryFulfilled;
} catch (e) {
if (e instanceof Error) {
dispatch(notifyApp(createErrorNotification('Error testing repository', e)));
}
}
},
},
createRepositoryJobs: {
onQueryStarted: async (_, { queryFulfilled, dispatch }) => {
try {
await queryFulfilled;
dispatch(
notifyApp(createSuccessNotification(t('provisioning.sync-repository.success-pull-started', 'Pull started')))
);
} catch (e) {
if (e instanceof Error) {
dispatch(
notifyApp(
createErrorNotification(
t('provisioning.sync-repository.error-pulling-resources', 'Error pulling resources'),
e
)
)
);
}
}
},
},
createRepository: {
onQueryStarted: async (_, { queryFulfilled, dispatch }) => {
try {
await queryFulfilled;
dispatch(
notifyApp(
createSuccessNotification(
t('provisioning.config-form.alert-repository-settings-saved', 'Repository settings saved')
)
)
);
} catch (e) {
if (e instanceof Error) {
dispatch(
notifyApp(
createErrorNotification(
t('provisioning.config-form.error-save-repository', 'Failed to save repository settings'),
e
)
)
);
}
}
},
},
replaceRepository: {
onQueryStarted: async (_, { queryFulfilled, dispatch }) => {
try {
await queryFulfilled;
dispatch(
notifyApp(
createSuccessNotification(
t('provisioning.config-form.alert-repository-settings-updated', 'Repository settings updated')
)
)
);
} catch (e) {
if (e instanceof Error) {
dispatch(
notifyApp(
createErrorNotification(
t('provisioning.config-form.error-save-repository', 'Failed to save repository settings'),
e
)
)
);
}
}
},
},
},
});
@@ -2,8 +2,6 @@ import { useEffect, useMemo, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom-v5-compat';
import { AppEvents } from '@grafana/data';
import { getAppEvents } from '@grafana/runtime';
import {
Button,
Combobox,
@@ -47,8 +45,6 @@ export function getWorkflowOptions(type?: 'github' | 'local'): Array<ComboboxOpt
return opts.filter((opt) => opt.value === 'write'); // only write
}
const appEvents = getAppEvents();
export function getDefaultValues(repository?: RepositorySpec): RepositoryFormData {
if (!repository) {
return {
@@ -109,11 +105,6 @@ export function ConfigForm({ data }: ConfigFormProps) {
useEffect(() => {
if (request.isSuccess) {
const formData = getValues();
appEvents.publish({
type: AppEvents.alertSuccess.name,
payload: [t('provisioning.config-form.alert-repository-settings-saved', 'Repository settings saved')],
});
reset(formData);
setTimeout(() => {
navigate('/admin/provisioning');
+2 -15
View File
@@ -1,7 +1,5 @@
import { useEffect, useMemo, useState } from 'react';
import { useMemo, useState } from 'react';
import { AppEvents } from '@grafana/data';
import { getAppEvents } from '@grafana/runtime';
import { Alert, ConfirmModal, Stack, Tab, TabContent, TabsBar } from '@grafana/ui';
import { useDeletecollectionRepositoryMutation, useGetFrontendSettingsQuery } from 'app/api/clients/provisioning';
import { Page } from 'app/core/components/Page/Page';
@@ -12,8 +10,6 @@ import GettingStartedPage from './GettingStarted/GettingStartedPage';
import { FolderRepositoryList } from './Shared/FolderRepositoryList';
import { useRepositoryList } from './hooks';
const appEvents = getAppEvents();
enum TabSelection {
Repositories = 'repositories',
GettingStarted = 'getting-started',
@@ -22,7 +18,7 @@ enum TabSelection {
export default function HomePage() {
const [items, isLoading] = useRepositoryList({ watch: true });
const settings = useGetFrontendSettingsQuery();
const [deleteAll, deleteAllResult] = useDeletecollectionRepositoryMutation();
const [deleteAll] = useDeletecollectionRepositoryMutation();
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [activeTab, setActiveTab] = useState<TabSelection>(TabSelection.Repositories);
@@ -42,15 +38,6 @@ export default function HomePage() {
[]
);
useEffect(() => {
if (deleteAllResult.isSuccess) {
appEvents.publish({
type: AppEvents.alertSuccess.name,
payload: [t('provisioning.home-page.success-all-repositories-deleted', 'All configured repositories deleted')],
});
}
}, [deleteAllResult.isSuccess]);
// Early return for onboarding
if (!items?.length && !isLoading) {
return <GettingStartedPage items={items ?? []} />;
@@ -1,7 +1,3 @@
import { useEffect } from 'react';
import { AppEvents } from '@grafana/data';
import { getAppEvents } from '@grafana/runtime';
import { Button, Spinner } from '@grafana/ui';
import { Repository, useCreateRepositoryTestMutation } from 'app/api/clients/provisioning';
import { Trans } from 'app/core/internationalization';
@@ -14,21 +10,6 @@ export function CheckRepository({ repository }: Props) {
const [testRepo, testQuery] = useCreateRepositoryTestMutation();
const name = repository.metadata?.name;
useEffect(() => {
const appEvents = getAppEvents();
if (testQuery.isSuccess) {
appEvents.publish({
type: AppEvents.alertSuccess.name,
payload: ['Test started'],
});
} else if (testQuery.isError) {
appEvents.publish({
type: AppEvents.alertError.name,
payload: ['Error testing repository', testQuery.error],
});
}
}, [testQuery.error, testQuery.isError, testQuery.isSuccess]);
const onClick = () => {
if (!name) {
return;
@@ -1,14 +1,10 @@
import { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom-v5-compat';
import { AppEvents } from '@grafana/data';
import { getAppEvents } from '@grafana/runtime';
import { ConfirmModal, IconButton } from '@grafana/ui';
import { useDeleteRepositoryMutation } from 'app/api/clients/provisioning';
import { t } from 'app/core/internationalization';
const appEvents = getAppEvents();
interface Props {
name: string;
redirectTo?: string;
@@ -21,15 +17,6 @@ export function DeleteRepositoryButton({ name, redirectTo }: Props) {
useEffect(() => {
if (request.isSuccess) {
appEvents.publish({
type: AppEvents.alertSuccess.name,
payload: [
t(
'provisioning.delete-repository-button.success-repository-deleted',
'Repository settings queued for deletion'
),
],
});
setShowModal(false);
if (redirectTo) {
navigate(redirectTo);
@@ -1,8 +1,6 @@
import { useEffect, useState } from 'react';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom-v5-compat';
import { AppEvents } from '@grafana/data';
import { getAppEvents } from '@grafana/runtime';
import { Button, ConfirmModal } from '@grafana/ui';
import { Repository, useCreateRepositoryJobsMutation } from 'app/api/clients/provisioning';
import { Trans, t } from 'app/core/internationalization';
@@ -19,21 +17,6 @@ export function SyncRepository({ repository }: Props) {
const navigate = useNavigate();
const name = repository.metadata?.name;
useEffect(() => {
const appEvents = getAppEvents();
if (jobQuery.isSuccess) {
appEvents.publish({
type: AppEvents.alertSuccess.name,
payload: [t('provisioning.sync-repository.success-pull-started', 'Pull started')],
});
} else if (jobQuery.isError) {
appEvents.publish({
type: AppEvents.alertError.name,
payload: [t('provisioning.sync-repository.error-pulling-resources', 'Error pulling resources'), jobQuery.error],
});
}
}, [jobQuery.error, jobQuery.isError, jobQuery.isSuccess]);
const onClick = () => {
if (!name) {
return;
@@ -94,19 +94,9 @@ export function WizardContent({
const handleRepositoryDeletion = async (name: string) => {
try {
await deleteRepository({ name });
appEvents.publish({
type: AppEvents.alertSuccess.name,
payload: [t('provisioning.wizard-content.success-repository-deleted', 'Repository deleted')],
});
// Wait before redirecting to ensure deletion is indexed
setTimeout(() => navigate(PROVISIONING_URL), 1500);
} catch (error) {
appEvents.publish({
type: AppEvents.alertError.name,
payload: [
t('provisioning.wizard-content.error-failed-to-delete', 'Failed to delete repository. Please try again.'),
],
});
setIsCancelling(false);
}
};
@@ -168,10 +158,6 @@ export function WizardContent({
const newName = saveRequest.data?.metadata?.name;
if (newName) {
setValue('repositoryName', newName);
appEvents.publish({
type: AppEvents.alertSuccess.name,
payload: [t('provisioning.wizard-content.success-repository-saved', 'Repository saved')],
});
handleStatusChange(true);
}
} else if (saveRequest.isError) {
+4 -3
View File
@@ -5054,6 +5054,7 @@
},
"config-form": {
"alert-repository-settings-saved": "Repository settings saved",
"alert-repository-settings-updated": "Repository settings updated",
"button-save": "Save",
"button-saving": "Saving...",
"description-branch": "Create a branch (and pull request) for changes",
@@ -5064,6 +5065,7 @@
"description-workflows-makes-repository": "No workflows makes the repository read only",
"description-write": "Allow writing updates to the remote repository",
"error-required": "This field is required.",
"error-save-repository": "Failed to save repository settings",
"error-valid-github-url": "Please enter a valid GitHub repository URL",
"label-automatic-pulling": "Automatic pulling",
"label-branch": "Branch",
@@ -5137,6 +5139,7 @@
"delete-repository-button": {
"button-delete": "Delete",
"confirm-delete-repository": "Are you sure you want to delete the repository config?",
"error-repository-delete": "Failed to delete repository",
"success-repository-deleted": "Repository settings queued for deletion",
"title-delete-repository": "Delete repository config",
"tooltip-delete-this-repository": "Delete this repository"
@@ -5235,6 +5238,7 @@
"button-delete-repositories": "Delete repositories",
"configured-repositories-while-running-legacy-storage": "Configured repositories will not work while running legacy storage.",
"confirm-delete-repositories": "Are you sure you want to delete all configured repositories? This action cannot be undone.",
"error-delete-all-repositories": "Failed to delete all repositories",
"remove-all-configured-repositories": "Remove all configured repositories",
"subtitle": "View and manage your configured repositories",
"success-all-repositories-deleted": "All configured repositories deleted",
@@ -5403,10 +5407,7 @@
"button-cancel": "Cancel",
"button-cancelling": "Cancelling...",
"button-submitting": "Submitting...",
"error-failed-to-delete": "Failed to delete repository. Please try again.",
"error-instance-repository-exists": "Instance repository already exists",
"success-repository-deleted": "Repository deleted",
"success-repository-saved": "Repository saved",
"title-repository-verification-failed": "Repository verification failed"
}
},