mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
feat(editor): Add "Stop Test" button to stop running evaluations (#17328)
This commit is contained in:
@@ -3144,6 +3144,7 @@
|
||||
"evaluation.listRuns.error.unknownError": "Execution error{description}",
|
||||
"evaluation.listRuns.error.cantFetchTestRuns": "Couldn’t fetch test runs",
|
||||
"evaluation.listRuns.error.cantStartTestRun": "Couldn’t start test run",
|
||||
"evaluation.listRuns.error.cantStopTestRun": "Couldn’t stop test run",
|
||||
"evaluation.listRuns.error.unknownError.description": "Click for more details",
|
||||
"evaluation.listRuns.error.evaluationTriggerNotFound": "Evaluation trigger missing",
|
||||
"evaluation.listRuns.error.evaluationTriggerNotConfigured": "Evaluation trigger is not configured",
|
||||
@@ -3171,6 +3172,7 @@
|
||||
"evaluation.runDetail.error.noMetricsCollected": "No 'Set metrics' node executed",
|
||||
"evaluation.runDetail.error.partialCasesFailed": "Finished with errors",
|
||||
"evaluation.runTest": "Run Test",
|
||||
"evaluation.stopTest": "Stop Test",
|
||||
"evaluation.cancelTestRun": "Cancel Test Run",
|
||||
"evaluation.notImplemented": "This feature is not implemented yet!",
|
||||
"evaluation.viewDetails": "View Details",
|
||||
|
||||
@@ -5,11 +5,11 @@ export interface TestRunRecord {
|
||||
id: string;
|
||||
workflowId: string;
|
||||
status: 'new' | 'running' | 'completed' | 'error' | 'cancelled' | 'warning' | 'success';
|
||||
metrics?: Record<string, number>;
|
||||
metrics?: Record<string, number> | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
runAt: string;
|
||||
completedAt: string;
|
||||
completedAt: string | null;
|
||||
errorCode?: string;
|
||||
errorDetails?: Record<string, unknown>;
|
||||
finalResult?: 'success' | 'error' | 'warning';
|
||||
|
||||
@@ -14,7 +14,6 @@ export const useEvaluationStore = defineStore(
|
||||
() => {
|
||||
// State
|
||||
const loadingTestRuns = ref(false);
|
||||
const fetchedAll = ref(false);
|
||||
const testRunsById = ref<Record<string, TestRunRecord>>({});
|
||||
const testCaseExecutionsById = ref<Record<string, TestCaseExecutionRecord>>({});
|
||||
const pollingTimeouts = ref<Record<string, NodeJS.Timeout>>({});
|
||||
@@ -170,7 +169,6 @@ export const useEvaluationStore = defineStore(
|
||||
|
||||
return {
|
||||
// State
|
||||
fetchedAll,
|
||||
testRunsById,
|
||||
testCaseExecutionsById,
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import RunsSection from '@/components/Evaluations.ee/ListRuns/RunsSection.vue';
|
||||
import { useEvaluationStore } from '@/stores/evaluation.store.ee';
|
||||
@@ -18,6 +18,9 @@ const toast = useToast();
|
||||
const evaluationStore = useEvaluationStore();
|
||||
|
||||
const selectedMetric = ref<string>('');
|
||||
const cancellingTestRun = ref<boolean>(false);
|
||||
|
||||
const runningTestRun = computed(() => runs.value.find((run) => run.status === 'running'));
|
||||
|
||||
async function runTest() {
|
||||
try {
|
||||
@@ -33,6 +36,22 @@ async function runTest() {
|
||||
}
|
||||
}
|
||||
|
||||
async function stopTest() {
|
||||
if (!runningTestRun.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
cancellingTestRun.value = true;
|
||||
await evaluationStore.cancelTestRun(runningTestRun.value.workflowId, runningTestRun.value.id);
|
||||
// we don't reset cancellingTestRun flag here, because we want the button to stay disabled
|
||||
// until the "running" testRun is updated and cancelled in the list of test runs
|
||||
} catch (error) {
|
||||
toast.showError(error, locale.baseText('evaluation.listRuns.error.cantStopTestRun'));
|
||||
cancellingTestRun.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const runs = computed(() => {
|
||||
const testRuns = Object.values(evaluationStore.testRunsById ?? {}).filter(
|
||||
({ workflowId }) => workflowId === props.name,
|
||||
@@ -44,29 +63,36 @@ const runs = computed(() => {
|
||||
}));
|
||||
});
|
||||
|
||||
const isRunning = computed(() => runs.value.some((run) => run.status === 'running'));
|
||||
const isRunTestEnabled = computed(() => !isRunning.value);
|
||||
watch(runningTestRun, (run) => {
|
||||
if (!run) {
|
||||
// reset to ensure next run can be stopped
|
||||
cancellingTestRun.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.evaluationsView">
|
||||
<div :class="$style.header">
|
||||
<N8nTooltip :disabled="isRunTestEnabled" :placement="'left'">
|
||||
<N8nButton
|
||||
:disabled="!isRunTestEnabled"
|
||||
:class="$style.runTestButton"
|
||||
size="small"
|
||||
data-test-id="run-test-button"
|
||||
:label="locale.baseText('evaluation.runTest')"
|
||||
type="primary"
|
||||
@click="runTest"
|
||||
/>
|
||||
<template #content>
|
||||
<template v-if="isRunning">
|
||||
{{ locale.baseText('evaluation.testIsRunning') }}
|
||||
</template>
|
||||
</template>
|
||||
</N8nTooltip>
|
||||
<N8nButton
|
||||
v-if="runningTestRun"
|
||||
:disabled="cancellingTestRun"
|
||||
:class="$style.runOrStopTestButton"
|
||||
size="small"
|
||||
data-test-id="stop-test-button"
|
||||
:label="locale.baseText('evaluation.stopTest')"
|
||||
type="secondary"
|
||||
@click="stopTest"
|
||||
/>
|
||||
<N8nButton
|
||||
v-else
|
||||
:class="$style.runOrStopTestButton"
|
||||
size="small"
|
||||
data-test-id="run-test-button"
|
||||
:label="locale.baseText('evaluation.runTest')"
|
||||
type="primary"
|
||||
@click="runTest"
|
||||
/>
|
||||
</div>
|
||||
<div :class="$style.wrapper">
|
||||
<div :class="$style.content">
|
||||
@@ -112,7 +138,7 @@ const isRunTestEnabled = computed(() => !isRunning.value);
|
||||
padding-left: 58px;
|
||||
}
|
||||
|
||||
.runTestButton {
|
||||
.runOrStopTestButton {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import { useEvaluationStore } from '@/stores/evaluation.store.ee';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import type { TestRunRecord } from '@/api/evaluation.ee';
|
||||
import { waitFor } from '@testing-library/vue';
|
||||
// import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
|
||||
vi.mock('vue-router', () => {
|
||||
const push = vi.fn();
|
||||
@@ -98,5 +97,53 @@ describe('EvaluationsView', () => {
|
||||
expect(evaluationStore.startTestRun).toHaveBeenCalledWith('workflow-id');
|
||||
expect(evaluationStore.fetchTestRuns).toHaveBeenCalledWith('workflow-id');
|
||||
});
|
||||
|
||||
it('should display stop button when a test is running', async () => {
|
||||
const evaluationStore = mockedStore(useEvaluationStore);
|
||||
evaluationStore.testRunsById = {
|
||||
run1: {
|
||||
id: 'run1',
|
||||
workflowId: 'workflow-id',
|
||||
status: 'running',
|
||||
runAt: '2023-01-01',
|
||||
createdAt: '2023-01-01',
|
||||
updatedAt: '2023-01-01',
|
||||
completedAt: null,
|
||||
metrics: {},
|
||||
},
|
||||
};
|
||||
|
||||
const { getByTestId } = renderComponent();
|
||||
|
||||
await waitFor(() => expect(getByTestId('stop-test-button')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should call cancelTestRun when stop button is clicked', async () => {
|
||||
const evaluationStore = mockedStore(useEvaluationStore);
|
||||
evaluationStore.cancelTestRun.mockResolvedValue({ success: true });
|
||||
|
||||
evaluationStore.testRunsById = {
|
||||
run1: {
|
||||
id: 'run1',
|
||||
workflowId: 'workflow-id',
|
||||
status: 'running',
|
||||
runAt: '2023-01-01',
|
||||
createdAt: '2023-01-01',
|
||||
updatedAt: '2023-01-01',
|
||||
completedAt: null,
|
||||
metrics: {},
|
||||
},
|
||||
};
|
||||
|
||||
const { getByTestId } = renderComponent();
|
||||
|
||||
await waitFor(() => expect(getByTestId('stop-test-button')).toBeInTheDocument());
|
||||
|
||||
await userEvent.click(getByTestId('stop-test-button'));
|
||||
expect(getByTestId('stop-test-button')).toBeDisabled();
|
||||
expect(evaluationStore.cancelTestRun).toHaveBeenCalledWith('workflow-id', 'run1');
|
||||
|
||||
expect(getByTestId('stop-test-button')).toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user