fix(editor): Make AI transform node read only in executions view (#12970)

This commit is contained in:
Elias Meire 2025-02-03 08:50:24 +01:00 committed by GitHub
parent 1a915239c6
commit ce1deb8aea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 37 additions and 25 deletions

View file

@ -2,7 +2,7 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mount } from '@vue/test-utils';
import { createTestingPinia } from '@pinia/testing';
import ButtonParameter from '@/components/ButtonParameter/ButtonParameter.vue';
import ButtonParameter, { type Props } from '@/components/ButtonParameter/ButtonParameter.vue';
import { useNDVStore } from '@/stores/ndv.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { usePostHog } from '@/stores/posthog.store';
@ -20,7 +20,7 @@ vi.mock('@/composables/useI18n');
vi.mock('@/composables/useToast');
describe('ButtonParameter', () => {
const defaultProps = {
const defaultProps: Props = {
parameter: {
name: 'testParam',
displayName: 'Test Parameter',
@ -38,6 +38,7 @@ describe('ButtonParameter', () => {
},
} as INodeProperties,
value: '',
isReadOnly: false,
path: 'testPath',
};
@ -78,9 +79,9 @@ describe('ButtonParameter', () => {
} as any);
});
const mountComponent = (props = defaultProps) => {
const mountComponent = (props: Partial<Props> = {}) => {
return mount(ButtonParameter, {
props,
props: { ...defaultProps, ...props },
global: {
plugins: [createTestingPinia()],
},
@ -134,4 +135,10 @@ describe('ButtonParameter', () => {
expect(useToast().showMessage).toHaveBeenCalled();
});
it('disables input and button when in read only mode', async () => {
const wrapper = mountComponent({ isReadOnly: true });
expect(wrapper.find('textarea').attributes('disabled')).toBeDefined();
expect(wrapper.find('button').attributes('disabled')).toBeDefined();
});
});

View file

@ -14,7 +14,6 @@ import {
getTextareaCursorPosition,
} from './utils';
import { useTelemetry } from '@/composables/useTelemetry';
import { useUIStore } from '@/stores/ui.store';
import { propertyNameFromExpression } from '../../utils/mappingUtils';
@ -24,11 +23,13 @@ const emit = defineEmits<{
valueChanged: [value: IUpdateInformation];
}>();
const props = defineProps<{
export type Props = {
parameter: INodeProperties;
value: string;
path: string;
}>();
isReadOnly?: boolean;
};
const props = defineProps<Props>();
const { activeNode } = useNDVStore();
@ -48,8 +49,7 @@ const buttonLabel = computed(
() => props.parameter.typeOptions?.buttonConfig?.label ?? props.parameter.displayName,
);
const isSubmitEnabled = computed(() => {
if (!hasExecutionData.value) return false;
if (!prompt.value) return false;
if (!hasExecutionData.value || !prompt.value || props.isReadOnly) return false;
const maxlength = inputFieldMaxLength.value;
if (maxlength && prompt.value.length > maxlength) return false;
@ -156,16 +156,6 @@ function onPromptInput(inputValue: string) {
});
}
function useDarkBackdrop(): string {
const theme = useUIStore().appliedTheme;
if (theme === 'light') {
return 'background-color: var(--color-background-xlight);';
} else {
return 'background-color: var(--color-background-light);';
}
}
onMounted(() => {
parentNodes.value = getParentNodes();
});
@ -213,8 +203,11 @@ async function updateCursorPositionOnMouseMove(event: MouseEvent, activeDrop: bo
color="text-dark"
>
</n8n-input-label>
<div :class="$style.inputContainer" :hidden="!hasInputField">
<div :class="$style.meta" :style="useDarkBackdrop()">
<div
:class="[$style.inputContainer, { [$style.disabled]: isReadOnly }]"
:hidden="!hasInputField"
>
<div :class="$style.meta">
<span
v-if="inputFieldMaxLength"
v-show="prompt.length > 1"
@ -240,6 +233,7 @@ async function updateCursorPositionOnMouseMove(event: MouseEvent, activeDrop: bo
:rows="6"
:maxlength="inputFieldMaxLength"
:placeholder="parameter.placeholder"
:disabled="isReadOnly"
@input="onPromptInput"
@mousemove="updateCursorPositionOnMouseMove($event, activeDrop)"
@mouseleave="cleanTextareaRowsData"
@ -279,12 +273,19 @@ async function updateCursorPositionOnMouseMove(event: MouseEvent, activeDrop: bo
.input * {
border: 1.5px transparent !important;
}
.input {
border-radius: var(--border-radius-base);
}
.input textarea {
font-size: var(--font-size-2xs);
padding-bottom: var(--spacing-2xl);
font-family: var(--font-family);
resize: none;
margin: 0;
}
.intro {
font-weight: var(--font-weight-bold);
font-size: var(--font-size-2xs);
@ -300,14 +301,13 @@ async function updateCursorPositionOnMouseMove(event: MouseEvent, activeDrop: bo
position: absolute;
padding-bottom: var(--spacing-2xs);
padding-top: var(--spacing-2xs);
margin: 1px;
margin-right: var(--spacing-s);
bottom: 0;
bottom: 2px;
left: var(--spacing-xs);
right: var(--spacing-xs);
gap: 10px;
gap: var(--spacing-2xs);
align-items: end;
z-index: 1;
background-color: var(--color-foreground-xlight);
* {
font-size: var(--font-size-2xs);
@ -334,4 +334,9 @@ async function updateCursorPositionOnMouseMove(event: MouseEvent, activeDrop: bo
border: 1.5px solid var(--color-success) !important;
cursor: grabbing;
}
.disabled {
.meta {
background-color: var(--fill-disabled);
}
}
</style>