fix(editor): Prevent evaluations tab crash on unsaved workflows (#30744)

This commit is contained in:
Luca Mattiazzi 2026-05-20 11:55:07 +02:00 committed by GitHub
parent d5d619c452
commit 3ee618b35b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 50 additions and 3 deletions

View File

@ -18,6 +18,10 @@ import { EVALUATION_NODE_TYPE, EVALUATION_TRIGGER_NODE_TYPE, NodeHelpers } from
import { mockNodeTypeDescription } from '@/__tests__/mocks';
import type { SourceControlPreferences } from '@/features/integrations/sourceControl.ee/sourceControl.types';
const { showError } = vi.hoisted(() => ({
showError: vi.fn(),
}));
vi.mock('vue-router', () => ({
useRoute: () => ({
query: {},
@ -38,7 +42,7 @@ vi.mock('@/app/composables/useTelemetry', () => {
vi.mock('@/app/composables/useToast', () => ({
useToast: () => ({
showError: vi.fn(),
showError,
showMessage: vi.fn(),
}),
}));
@ -92,6 +96,8 @@ describe('EvaluationsRootView', () => {
createTestingPinia();
vi.clearAllMocks();
mockedStore(useWorkflowsStore).isWorkflowSaved = { [mockWorkflow.id]: true };
vi.spyOn(NodeHelpers, 'getNodeParameters').mockReturnValue({
assignments: {
assignments: [
@ -125,6 +131,24 @@ describe('EvaluationsRootView', () => {
expect(evaluationStore.fetchTestRuns).toHaveBeenCalledWith(mockWorkflow.id);
});
it('should not fetch test runs for an unsaved workflow', async () => {
const workflowsStore = mockedStore(useWorkflowsStore);
const usageStore = mockedStore(useUsageStore);
const evaluationStore = mockedStore(useEvaluationStore);
workflowsStore.isWorkflowSaved = {};
usageStore.getLicenseInfo.mockResolvedValue(undefined);
evaluationStore.fetchTestRuns.mockRejectedValue(new Error('Workflow not found'));
renderComponent({ props: { workflowId: mockWorkflow.id } });
await flushPromises();
expect(usageStore.getLicenseInfo).toHaveBeenCalled();
expect(evaluationStore.fetchTestRuns).not.toHaveBeenCalled();
expect(showError).not.toHaveBeenCalled();
});
it('should load test data', async () => {
const evaluationStore = mockedStore(useEvaluationStore);
evaluationStore.fetchTestRuns.mockResolvedValue(mockTestRuns);

View File

@ -7,6 +7,7 @@ import { useToast } from '@/app/composables/useToast';
import { useI18n } from '@n8n/i18n';
import { useEvaluationStore } from '../evaluation.store';
import { useSourceControlStore } from '@/features/integrations/sourceControl.ee/sourceControl.store';
import { useWorkflowsStore } from '@/app/stores/workflows.store';
import { computed, watch } from 'vue';
import EvaluationsPaywall from '../components/Paywall/EvaluationsPaywall.vue';
@ -19,6 +20,7 @@ const props = defineProps<{
const usageStore = useUsageStore();
const evaluationStore = useEvaluationStore();
const workflowsStore = useWorkflowsStore();
const telemetry = useTelemetry();
const toast = useToast();
const locale = useI18n();
@ -32,6 +34,8 @@ const isProtectedEnvironment = computed(() => {
return sourceControlStore.preferences.branchReadOnly;
});
const workflowIsSaved = computed(() => workflowsStore.isWorkflowSaved[props.workflowId] === true);
const runs = computed(() => {
return Object.values(evaluationStore.testRunsById ?? {}).filter(
({ workflowId }) => workflowId === props.workflowId,
@ -46,6 +50,8 @@ const showWizard = computed(() => !hasRuns.value);
// Method to run a test - will be used by the SetupWizard component
async function runTest() {
if (!workflowIsSaved.value) return;
try {
await evaluationStore.startTestRun(props.workflowId);
} catch (error) {
@ -67,15 +73,32 @@ const evaluationsQuotaExceeded = computed(() => {
);
});
const { isReady } = useAsyncState(async () => {
async function fetchTestRuns() {
if (!workflowIsSaved.value) return;
try {
await usageStore.getLicenseInfo();
await evaluationStore.fetchTestRuns(props.workflowId);
} catch (error) {
toast.showError(error, locale.baseText('evaluation.listRuns.error.cantFetchTestRuns'));
}
}
const { isReady } = useAsyncState(async () => {
try {
await usageStore.getLicenseInfo();
} catch (error) {
toast.showError(error, locale.baseText('evaluation.listRuns.error.cantFetchTestRuns'));
}
await fetchTestRuns();
}, undefined);
watch(workflowIsSaved, async (isSaved) => {
if (isSaved) {
await fetchTestRuns();
}
});
watch(
isReady,
(ready) => {