feat(editor): Move the check rule and delete actions in the agent check row (no-changelog) (#41170)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Emre Caglar 2026-10-16 10:26:51 +00:00 committed by GitHub
parent fffea4c0c0
commit a94f5dc8b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 109 additions and 77 deletions

View File

@ -435,6 +435,36 @@ describe('AgentEvalTryRow', () => {
expect(getByTestId('row-delete-check')).toBeDisabled();
});
it.each([
['a passing case, after "Run check"', 'pass', 'row-run-check'],
['a case that needs work, after "Actually fine"', 'work', 'row-actually-fine'],
] as const)(
'puts delete at the end of the actions for %s',
async (_label, status, lastAction) => {
const user = userEvent.setup();
const { getByTestId } = renderWithRule({ testId: 'row', status, output: 'y' });
await user.click(getByTestId('row-toggle'));
const action = getByTestId(lastAction);
const deleteButton = getByTestId('row-delete-check');
expect(action.compareDocumentPosition(deleteButton)).toBe(Node.DOCUMENT_POSITION_FOLLOWING);
expect(getByTestId('row-edit-rule').compareDocumentPosition(action)).toBe(
Node.DOCUMENT_POSITION_FOLLOWING,
);
},
);
it('keeps delete available while the rule is being edited', async () => {
const user = userEvent.setup();
const { getByTestId, queryByTestId } = renderWithRule({ testId: 'row' });
await user.click(getByTestId('row-toggle'));
await user.click(getByTestId('row-edit-rule'));
expect(queryByTestId('row-edit-rule')).not.toBeInTheDocument();
expect(getByTestId('row-delete-check')).toBeEnabled();
});
});
describe('practice run banner', () => {

View File

@ -16,6 +16,7 @@ import {
N8nInput,
N8nText,
N8nTimeAgo,
N8nTooltip,
type TextColor,
} from '@n8n/design-system';
import { useI18n, type BaseTextKey } from '@n8n/i18n';
@ -270,44 +271,11 @@ watch(
:class="$style.sample"
:data-test-id="testId && `${testId}-placeholder`"
>
<div :class="$style.whatToCheck">
<div :class="$style.whatToCheckHeader">
<N8nText v-if="whatToCheck" bold color="text-dark" size="medium">{{
<div v-if="whatToCheck" :class="$style.whatToCheck">
<div :class="$style.whatToCheckBody">
<N8nText bold color="text-dark" size="medium">{{
i18n.baseText('instanceAi.testAgentPreview.rule')
}}</N8nText>
<div v-if="!editingWhatToCheck" :class="$style.whatToCheckActions">
<!-- Only the rule is editable; a case with no criteria column can still be deleted. -->
<N8nButton
v-if="whatToCheck"
variant="outline"
size="small"
icon-only
:disabled="disabled || runningCheck"
:aria-label="i18n.baseText('instanceAi.testAgentPreview.editRule')"
:data-test-id="testId && `${testId}-edit-rule`"
@click="onStartEditWhatToCheck"
>
<template #icon>
<N8nIcon icon="pencil" size="small" />
</template>
</N8nButton>
<N8nButton
variant="outline"
size="small"
icon-only
:disabled="disabled"
:aria-label="i18n.baseText('instanceAi.testAgentPreview.deleteCheck.label')"
:data-test-id="testId && `${testId}-delete-check`"
@click="onDeleteCheck"
>
<template #icon>
<N8nIcon icon="trash-2" size="small" />
</template>
</N8nButton>
</div>
</div>
<template v-if="whatToCheck">
<N8nText v-if="!editingWhatToCheck" color="text-dark" size="medium">{{
whatToCheck
}}</N8nText>
@ -338,7 +306,21 @@ watch(
{{ i18n.baseText('generic.cancel') }}
</N8nButton>
</div>
</template>
</div>
<N8nButton
v-if="!editingWhatToCheck"
variant="outline"
size="small"
icon-only
:disabled="disabled || runningCheck"
:aria-label="i18n.baseText('instanceAi.testAgentPreview.editRule')"
:data-test-id="testId && `${testId}-edit-rule`"
@click="onStartEditWhatToCheck"
>
<template #icon>
<N8nIcon icon="pencil" size="small" />
</template>
</N8nButton>
</div>
<N8nText bold color="text-dark" size="medium">{{
i18n.baseText('instanceAi.testAgentPreview.conversation')
@ -395,32 +377,33 @@ watch(
@keydown.ctrl.enter="onSaveCheck"
/>
</template>
<div :class="$style.correctionActions">
<N8nButton
v-if="!hideRevise"
variant="solid"
size="small"
:disabled="disabled || !suggestion.trim()"
:loading="savingCheck"
:data-test-id="testId && `${testId}-save-check`"
@click="onSaveCheck"
>
{{ i18n.baseText('instanceAi.testAgentPreview.saveCorrection') }}
</N8nButton>
<N8nButton
variant="ghost"
size="small"
:disabled="disabled || savingCheck"
:data-test-id="testId && `${testId}-actually-fine`"
@click="onActuallyFine"
>
{{ i18n.baseText('instanceAi.testAgentPreview.actuallyFine') }}
</N8nButton>
</div>
</template>
<template v-else>
<div :class="$style.correctionActions">
<div :class="$style.correctionActions">
<div :class="$style.primaryActions">
<template v-if="needsCorrection">
<N8nButton
v-if="!hideRevise"
variant="solid"
size="small"
:disabled="disabled || !suggestion.trim()"
:loading="savingCheck"
:data-test-id="testId && `${testId}-save-check`"
@click="onSaveCheck"
>
{{ i18n.baseText('instanceAi.testAgentPreview.saveCorrection') }}
</N8nButton>
<N8nButton
variant="ghost"
size="small"
:disabled="disabled || savingCheck"
:data-test-id="testId && `${testId}-actually-fine`"
@click="onActuallyFine"
>
{{ i18n.baseText('instanceAi.testAgentPreview.actuallyFine') }}
</N8nButton>
</template>
<N8nButton
v-else
size="small"
variant="outline"
:disabled="disabled"
@ -431,7 +414,26 @@ watch(
{{ i18n.baseText('instanceAi.testAgentPreview.runCheck') }}
</N8nButton>
</div>
</template>
<!-- Ghost, so it never outranks Run check or Actually fine. -->
<N8nTooltip
:content="i18n.baseText('instanceAi.testAgentPreview.deleteCheck.label')"
placement="top-end"
>
<N8nButton
variant="ghost"
size="medium"
icon-only
:disabled="disabled"
:aria-label="i18n.baseText('instanceAi.testAgentPreview.deleteCheck.label')"
:data-test-id="testId && `${testId}-delete-check`"
@click="onDeleteCheck"
>
<template #icon>
<N8nIcon icon="trash-2" size="small" />
</template>
</N8nButton>
</N8nTooltip>
</div>
</div>
</div>
</template>
@ -472,23 +474,17 @@ watch(
.whatToCheck {
display: flex;
flex-direction: column;
gap: var(--spacing--5xs);
margin-bottom: var(--spacing--2xs);
}
.whatToCheckHeader {
display: flex;
align-items: center;
align-items: flex-start;
justify-content: space-between;
gap: var(--spacing--2xs);
gap: var(--spacing--sm);
padding-bottom: var(--spacing--lg);
}
.whatToCheckActions {
.whatToCheckBody {
display: flex;
align-items: center;
gap: var(--spacing--3xs);
margin-left: auto;
flex-direction: column;
flex: 1;
min-width: 0;
}
.whatToCheckEdit {
@ -514,7 +510,7 @@ watch(
.sample {
border-left: var(--border);
padding: var(--spacing--2xs) var(--spacing--sm) var(--spacing--sm) 32px;
padding: var(--spacing--lg) var(--spacing--sm) var(--spacing--sm) 32px;
margin-left: var(--spacing--xs);
}
@ -529,10 +525,16 @@ watch(
}
.correctionActions {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: var(--spacing--xs);
}
.primaryActions {
display: flex;
align-items: center;
gap: var(--spacing--2xs);
margin-top: var(--spacing--2xs);
}
.practiceBanner {