2023-07-28 00:51:07 -07:00
|
|
|
import { ElNotification as Notification } from 'element-plus';
|
2024-02-26 06:05:12 -08:00
|
|
|
import type { NotificationHandle, MessageBoxState } from 'element-plus';
|
|
|
|
import type { NotificationOptions } from '@/Interface';
|
2023-11-28 03:15:08 -08:00
|
|
|
import { sanitizeHtml } from '@/utils/htmlUtils';
|
2023-04-18 03:41:55 -07:00
|
|
|
import { useTelemetry } from '@/composables/useTelemetry';
|
2023-05-15 09:41:13 -07:00
|
|
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
2023-12-19 06:10:03 -08:00
|
|
|
import { useUIStore } from '@/stores/ui.store';
|
2023-04-18 03:41:55 -07:00
|
|
|
import { useI18n } from './useI18n';
|
|
|
|
import { useExternalHooks } from './useExternalHooks';
|
2023-12-19 06:10:03 -08:00
|
|
|
import { VIEWS } from '@/constants';
|
2024-07-12 01:13:17 -07:00
|
|
|
import type { ApplicationError } from 'n8n-workflow';
|
2024-10-25 07:56:47 -07:00
|
|
|
import { useStyles } from './useStyles';
|
2024-11-13 02:05:19 -08:00
|
|
|
import { useCanvasStore } from '@/stores/canvas.store';
|
2024-11-05 03:52:52 -08:00
|
|
|
import { useSettingsStore } from '@/stores/settings.store';
|
2023-04-18 03:41:55 -07:00
|
|
|
|
2024-07-12 01:13:17 -07:00
|
|
|
export interface NotificationErrorWithNodeAndDescription extends ApplicationError {
|
2024-02-26 06:05:12 -08:00
|
|
|
node: {
|
|
|
|
name: string;
|
|
|
|
};
|
|
|
|
description: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
const stickyNotificationQueue: NotificationHandle[] = [];
|
2023-04-18 03:41:55 -07:00
|
|
|
|
|
|
|
export function useToast() {
|
|
|
|
const telemetry = useTelemetry();
|
|
|
|
const workflowsStore = useWorkflowsStore();
|
2023-12-19 06:10:03 -08:00
|
|
|
const uiStore = useUIStore();
|
2023-04-18 03:41:55 -07:00
|
|
|
const externalHooks = useExternalHooks();
|
2023-07-28 00:51:07 -07:00
|
|
|
const i18n = useI18n();
|
2024-11-05 03:52:52 -08:00
|
|
|
const settingsStore = useSettingsStore();
|
2024-10-25 07:56:47 -07:00
|
|
|
const { APP_Z_INDEXES } = useStyles();
|
2024-11-13 02:05:19 -08:00
|
|
|
const canvasStore = useCanvasStore();
|
2024-10-25 07:56:47 -07:00
|
|
|
|
|
|
|
const messageDefaults: Partial<Omit<NotificationOptions, 'message'>> = {
|
|
|
|
dangerouslyUseHTMLString: false,
|
|
|
|
position: 'bottom-right',
|
|
|
|
zIndex: APP_Z_INDEXES.TOASTS, // above NDV and modal overlays
|
2024-11-13 02:05:19 -08:00
|
|
|
offset: settingsStore.isAiAssistantEnabled || workflowsStore.isChatPanelOpen ? 64 : 0,
|
2024-10-25 07:56:47 -07:00
|
|
|
appendTo: '#app-grid',
|
|
|
|
customClass: 'content-toast',
|
|
|
|
};
|
2023-04-18 03:41:55 -07:00
|
|
|
|
2024-02-27 01:39:45 -08:00
|
|
|
function showMessage(messageData: Partial<NotificationOptions>, track = true) {
|
2024-08-08 07:28:51 -07:00
|
|
|
const { message, title } = messageData;
|
|
|
|
const params = { ...messageDefaults, ...messageData };
|
|
|
|
|
2024-11-13 02:05:19 -08:00
|
|
|
params.offset = +canvasStore.panelHeight;
|
|
|
|
|
2024-08-08 07:28:51 -07:00
|
|
|
if (typeof message === 'string') {
|
|
|
|
params.message = sanitizeHtml(message);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof title === 'string') {
|
|
|
|
params.title = sanitizeHtml(title);
|
|
|
|
}
|
2023-04-18 03:41:55 -07:00
|
|
|
|
2024-08-08 07:28:51 -07:00
|
|
|
const notification = Notification(params);
|
2023-04-18 03:41:55 -07:00
|
|
|
|
2024-08-08 07:28:51 -07:00
|
|
|
if (params.duration === 0) {
|
2023-04-18 03:41:55 -07:00
|
|
|
stickyNotificationQueue.push(notification);
|
|
|
|
}
|
|
|
|
|
2024-08-08 07:28:51 -07:00
|
|
|
if (params.type === 'error' && track) {
|
2023-04-18 03:41:55 -07:00
|
|
|
telemetry.track('Instance FE emitted error', {
|
2024-08-08 07:28:51 -07:00
|
|
|
error_title: params.title,
|
|
|
|
error_message: params.message,
|
|
|
|
caused_by_credential: causedByCredential(params.message as string),
|
2023-04-18 03:41:55 -07:00
|
|
|
workflow_id: workflowsStore.workflowId,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return notification;
|
|
|
|
}
|
|
|
|
|
|
|
|
function showToast(config: {
|
|
|
|
title: string;
|
2023-10-19 05:02:59 -07:00
|
|
|
message: NotificationOptions['message'];
|
2023-04-18 03:41:55 -07:00
|
|
|
onClick?: () => void;
|
|
|
|
onClose?: () => void;
|
|
|
|
duration?: number;
|
|
|
|
customClass?: string;
|
|
|
|
closeOnClick?: boolean;
|
2023-07-28 00:51:07 -07:00
|
|
|
type?: MessageBoxState['type'];
|
2023-05-22 06:09:29 -07:00
|
|
|
dangerouslyUseHTMLString?: boolean;
|
2023-04-18 03:41:55 -07:00
|
|
|
}) {
|
|
|
|
// eslint-disable-next-line prefer-const
|
2024-02-26 06:05:12 -08:00
|
|
|
let notification: NotificationHandle;
|
2023-04-18 03:41:55 -07:00
|
|
|
if (config.closeOnClick) {
|
|
|
|
const cb = config.onClick;
|
|
|
|
config.onClick = () => {
|
|
|
|
if (notification) {
|
|
|
|
notification.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cb) {
|
|
|
|
cb();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
notification = showMessage({
|
|
|
|
title: config.title,
|
|
|
|
message: config.message,
|
|
|
|
onClick: config.onClick,
|
|
|
|
onClose: config.onClose,
|
|
|
|
duration: config.duration,
|
|
|
|
customClass: config.customClass,
|
|
|
|
type: config.type,
|
2023-05-22 06:09:29 -07:00
|
|
|
dangerouslyUseHTMLString: config.dangerouslyUseHTMLString ?? true,
|
2023-04-18 03:41:55 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
return notification;
|
|
|
|
}
|
|
|
|
|
2024-02-26 06:05:12 -08:00
|
|
|
function collapsableDetails({ description, node }: NotificationErrorWithNodeAndDescription) {
|
2023-04-18 03:41:55 -07:00
|
|
|
if (!description) return '';
|
|
|
|
|
|
|
|
const errorDescription =
|
|
|
|
description.length > 500 ? `${description.slice(0, 500)}...` : description;
|
|
|
|
|
|
|
|
return `
|
|
|
|
<br>
|
|
|
|
<br>
|
|
|
|
<details>
|
|
|
|
<summary
|
|
|
|
style="color: #ff6d5a; font-weight: bold; cursor: pointer;"
|
|
|
|
>
|
|
|
|
${i18n.baseText('showMessage.showDetails')}
|
|
|
|
</summary>
|
|
|
|
<p>${node.name}: ${errorDescription}</p>
|
|
|
|
</details>
|
|
|
|
`;
|
|
|
|
}
|
|
|
|
|
|
|
|
function showError(e: Error | unknown, title: string, message?: string) {
|
2024-02-27 01:39:45 -08:00
|
|
|
const error = e as NotificationErrorWithNodeAndDescription;
|
2023-04-18 03:41:55 -07:00
|
|
|
const messageLine = message ? `${message}<br/>` : '';
|
|
|
|
showMessage(
|
|
|
|
{
|
|
|
|
title,
|
|
|
|
message: `
|
|
|
|
${messageLine}
|
|
|
|
<i>${error.message}</i>
|
2024-02-27 01:39:45 -08:00
|
|
|
${collapsableDetails(error)}`,
|
2023-04-18 03:41:55 -07:00
|
|
|
type: 'error',
|
|
|
|
duration: 0,
|
2024-08-08 07:28:51 -07:00
|
|
|
dangerouslyUseHTMLString: true,
|
2023-04-18 03:41:55 -07:00
|
|
|
},
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
|
2023-05-10 08:10:03 -07:00
|
|
|
void externalHooks.run('showMessage.showError', {
|
2023-04-18 03:41:55 -07:00
|
|
|
title,
|
|
|
|
message,
|
|
|
|
errorMessage: error.message,
|
|
|
|
});
|
|
|
|
|
|
|
|
telemetry.track('Instance FE emitted error', {
|
|
|
|
error_title: title,
|
|
|
|
error_description: message,
|
|
|
|
error_message: error.message,
|
2023-05-15 09:41:13 -07:00
|
|
|
caused_by_credential: causedByCredential(error.message),
|
2023-04-18 03:41:55 -07:00
|
|
|
workflow_id: workflowsStore.workflowId,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-02-26 06:05:12 -08:00
|
|
|
function showAlert(config: NotificationOptions): NotificationHandle {
|
2024-08-14 05:59:11 -07:00
|
|
|
return Notification({
|
|
|
|
...config,
|
|
|
|
});
|
2023-05-15 09:41:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function causedByCredential(message: string | undefined) {
|
2024-10-02 04:31:22 -07:00
|
|
|
if (!message || typeof message !== 'string') return false;
|
2023-05-15 09:41:13 -07:00
|
|
|
|
|
|
|
return message.includes('Credentials for') && message.includes('are not set');
|
|
|
|
}
|
|
|
|
|
|
|
|
function clearAllStickyNotifications() {
|
|
|
|
stickyNotificationQueue.forEach((notification) => {
|
|
|
|
if (notification) {
|
|
|
|
notification.close();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
stickyNotificationQueue.length = 0;
|
|
|
|
}
|
|
|
|
|
2023-12-19 06:10:03 -08:00
|
|
|
// Pick up and display notifications for the given list of views
|
|
|
|
function showNotificationForViews(views: VIEWS[]) {
|
|
|
|
const notifications: NotificationOptions[] = [];
|
|
|
|
views.forEach((view) => {
|
2024-07-03 12:11:40 -07:00
|
|
|
notifications.push(...(uiStore.pendingNotificationsForViews[view] ?? []));
|
2023-12-19 06:10:03 -08:00
|
|
|
});
|
|
|
|
if (notifications.length) {
|
|
|
|
notifications.forEach(async (notification) => {
|
|
|
|
// Notifications show on top of each other without this timeout
|
|
|
|
setTimeout(() => {
|
|
|
|
showMessage(notification);
|
|
|
|
}, 5);
|
|
|
|
});
|
|
|
|
// Clear the queue once all notifications are shown
|
|
|
|
uiStore.setNotificationsForView(VIEWS.WORKFLOW, []);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-18 03:41:55 -07:00
|
|
|
return {
|
|
|
|
showMessage,
|
|
|
|
showToast,
|
|
|
|
showError,
|
2023-05-15 09:41:13 -07:00
|
|
|
showAlert,
|
|
|
|
clearAllStickyNotifications,
|
2023-12-19 06:10:03 -08:00
|
|
|
showNotificationForViews,
|
2023-04-18 03:41:55 -07:00
|
|
|
};
|
|
|
|
}
|