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 { describe, it, expect, vi, beforeEach } from 'vitest';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { createTestingPinia } from '@pinia/testing'; 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 { useNDVStore } from '@/stores/ndv.store';
import { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsStore } from '@/stores/workflows.store';
import { usePostHog } from '@/stores/posthog.store'; import { usePostHog } from '@/stores/posthog.store';
@ -20,7 +20,7 @@ vi.mock('@/composables/useI18n');
vi.mock('@/composables/useToast'); vi.mock('@/composables/useToast');
describe('ButtonParameter', () => { describe('ButtonParameter', () => {
const defaultProps = { const defaultProps: Props = {
parameter: { parameter: {
name: 'testParam', name: 'testParam',
displayName: 'Test Parameter', displayName: 'Test Parameter',
@ -38,6 +38,7 @@ describe('ButtonParameter', () => {
}, },
} as INodeProperties, } as INodeProperties,
value: '', value: '',
isReadOnly: false,
path: 'testPath', path: 'testPath',
}; };
@ -78,9 +79,9 @@ describe('ButtonParameter', () => {
} as any); } as any);
}); });
const mountComponent = (props = defaultProps) => { const mountComponent = (props: Partial<Props> = {}) => {
return mount(ButtonParameter, { return mount(ButtonParameter, {
props, props: { ...defaultProps, ...props },
global: { global: {
plugins: [createTestingPinia()], plugins: [createTestingPinia()],
}, },
@ -134,4 +135,10 @@ describe('ButtonParameter', () => {
expect(useToast().showMessage).toHaveBeenCalled(); 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, getTextareaCursorPosition,
} from './utils'; } from './utils';
import { useTelemetry } from '@/composables/useTelemetry'; import { useTelemetry } from '@/composables/useTelemetry';
import { useUIStore } from '@/stores/ui.store';
import { propertyNameFromExpression } from '../../utils/mappingUtils'; import { propertyNameFromExpression } from '../../utils/mappingUtils';
@ -24,11 +23,13 @@ const emit = defineEmits<{
valueChanged: [value: IUpdateInformation]; valueChanged: [value: IUpdateInformation];
}>(); }>();
const props = defineProps<{ export type Props = {
parameter: INodeProperties; parameter: INodeProperties;
value: string; value: string;
path: string; path: string;
}>(); isReadOnly?: boolean;
};
const props = defineProps<Props>();
const { activeNode } = useNDVStore(); const { activeNode } = useNDVStore();
@ -48,8 +49,7 @@ const buttonLabel = computed(
() => props.parameter.typeOptions?.buttonConfig?.label ?? props.parameter.displayName, () => props.parameter.typeOptions?.buttonConfig?.label ?? props.parameter.displayName,
); );
const isSubmitEnabled = computed(() => { const isSubmitEnabled = computed(() => {
if (!hasExecutionData.value) return false; if (!hasExecutionData.value || !prompt.value || props.isReadOnly) return false;
if (!prompt.value) return false;
const maxlength = inputFieldMaxLength.value; const maxlength = inputFieldMaxLength.value;
if (maxlength && prompt.value.length > maxlength) return false; 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(() => { onMounted(() => {
parentNodes.value = getParentNodes(); parentNodes.value = getParentNodes();
}); });
@ -213,8 +203,11 @@ async function updateCursorPositionOnMouseMove(event: MouseEvent, activeDrop: bo
color="text-dark" color="text-dark"
> >
</n8n-input-label> </n8n-input-label>
<div :class="$style.inputContainer" :hidden="!hasInputField"> <div
<div :class="$style.meta" :style="useDarkBackdrop()"> :class="[$style.inputContainer, { [$style.disabled]: isReadOnly }]"
:hidden="!hasInputField"
>
<div :class="$style.meta">
<span <span
v-if="inputFieldMaxLength" v-if="inputFieldMaxLength"
v-show="prompt.length > 1" v-show="prompt.length > 1"
@ -240,6 +233,7 @@ async function updateCursorPositionOnMouseMove(event: MouseEvent, activeDrop: bo
:rows="6" :rows="6"
:maxlength="inputFieldMaxLength" :maxlength="inputFieldMaxLength"
:placeholder="parameter.placeholder" :placeholder="parameter.placeholder"
:disabled="isReadOnly"
@input="onPromptInput" @input="onPromptInput"
@mousemove="updateCursorPositionOnMouseMove($event, activeDrop)" @mousemove="updateCursorPositionOnMouseMove($event, activeDrop)"
@mouseleave="cleanTextareaRowsData" @mouseleave="cleanTextareaRowsData"
@ -279,12 +273,19 @@ async function updateCursorPositionOnMouseMove(event: MouseEvent, activeDrop: bo
.input * { .input * {
border: 1.5px transparent !important; border: 1.5px transparent !important;
} }
.input {
border-radius: var(--border-radius-base);
}
.input textarea { .input textarea {
font-size: var(--font-size-2xs); font-size: var(--font-size-2xs);
padding-bottom: var(--spacing-2xl); padding-bottom: var(--spacing-2xl);
font-family: var(--font-family); font-family: var(--font-family);
resize: none; resize: none;
margin: 0;
} }
.intro { .intro {
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
font-size: var(--font-size-2xs); font-size: var(--font-size-2xs);
@ -300,14 +301,13 @@ async function updateCursorPositionOnMouseMove(event: MouseEvent, activeDrop: bo
position: absolute; position: absolute;
padding-bottom: var(--spacing-2xs); padding-bottom: var(--spacing-2xs);
padding-top: var(--spacing-2xs); padding-top: var(--spacing-2xs);
margin: 1px; bottom: 2px;
margin-right: var(--spacing-s);
bottom: 0;
left: var(--spacing-xs); left: var(--spacing-xs);
right: var(--spacing-xs); right: var(--spacing-xs);
gap: 10px; gap: var(--spacing-2xs);
align-items: end; align-items: end;
z-index: 1; z-index: 1;
background-color: var(--color-foreground-xlight);
* { * {
font-size: var(--font-size-2xs); 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; border: 1.5px solid var(--color-success) !important;
cursor: grabbing; cursor: grabbing;
} }
.disabled {
.meta {
background-color: var(--fill-disabled);
}
}
</style> </style>